//! Public share page: renders a shared item without login. use crate::i18n; use api_types::FileKind; use leptos::prelude::*; use wasm_bindgen_futures::spawn_local; use crate::api::{self, Me, Mode, RootInfo, ShareInfo, UserInfo}; use crate::components::toast::provide_toast; use crate::editor::EditTarget; use crate::preview::{PreviewTarget, preview_kind}; use crate::router::Location; use crate::views::browser::Browser; use crate::views::file_view::{FileView, UnsupportedTarget}; use crate::views::workspace::FileWorkspace; #[derive(Clone, PartialEq)] enum SharePageState { Loading, NotFound, Expired, Active, } /// The full-page view for a shared *file*: the share page *is* the file. /// The file sits at the (empty) path of its synthetic root. fn file_view_for_share(i: &ShareInfo) -> Option { let kind = i.kind?; let root_id = i.root_id; let name = i.name.clone(); let path = String::new(); match kind { FileKind::Text => Some(FileView::Editor(EditTarget { root_id, path, name, readonly: !i.writable, })), k if preview_kind(k).is_some() => Some(FileView::Preview( PreviewTarget { root_id, path, name, }, preview_kind(k).unwrap(), )), k => Some(FileView::Unsupported(UnsupportedTarget { root_id, path, name, kind: k, })), } } #[component] pub fn ShareView(token: String, loc: ReadSignal) -> impl IntoView { let _ = 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, single_click_open: false, language: None, }), 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)); } // The shared file-view plumbing (topbar, close/save/dirty, selection, // dialogs, Escape). Closing a folder share returns to the listing; // closing a file share re-opens the file itself (there is nothing to // close back to, so the close button and Escape stay disabled as // well). let ws = FileWorkspace::new( me, loc, move || info.get().and_then(|i| file_view_for_share(&i)), move || state.get() == SharePageState::Active, ); let file_view = ws.file_view; let open_file = ws.open_file; let selected = ws.selected; let set_selected = ws.set_selected; let dialog = ws.dialog; let set_dialog = ws.set_dialog; let set_browser_refresh = ws.set_browser_refresh; let topbar_ref = ws.topbar_ref; let topbar_left = ws.topbar_left(); let topbar_right = ws.topbar_right(); let file_content = ws.file_content(); let overlays = ws.overlays(); // A file share opens straight into its viewer. { let info2 = info; let set_fv = open_file; Effect::new(move |_| { if let Some(i) = info2.get() && i.is_file { set_fv.set(file_view_for_share(&i)); } }); } view! {
{topbar_left}
{topbar_right}
{move || match state.get() { SharePageState::Loading => view! {

{i18n::tr(i18n::k::LOADING_SHARE)}

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

{i18n::tr(i18n::k::SHARE_NOT_FOUND)}

{i18n::tr(i18n::k::SHARE_NOT_FOUND_MSG)}

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

{i18n::tr(i18n::k::SHARE_EXPIRED)}

{i18n::tr(i18n::k::SHARE_EXPIRED_MSG)}

} .into_view() .into_any(), SharePageState::Active => { let Some(i) = info.get() else { return { view! {}.into_any() }; }; // File shares render through the full-page file view // (opened by the effect above); only folder shares // get a browser. if i.is_file { view! {} .into_view() .into_any() } else { if me.get().is_none() { return view! {

{i18n::tr(i18n::k::LOADING)}

} .into_view() .into_any(); } view! { } .into_view() .into_any() } } }} {file_content}
{overlays}
} }