//! Modal dialogs: text prompts (new folder / rename), confirmations (delete), //! overwrite conflict warnings, and the move/copy destination picker. use leptos::prelude::*; use wasm_bindgen_futures::spawn_local; use web_sys::MouseEvent; use crate::api::{self, Entry, RootInfo}; use crate::components::icon::{Icon, IconName}; /// Which mutating operation the destination picker is for. #[derive(Clone, Copy, PartialEq)] pub enum Op { Move, Copy, } impl Op { pub fn verb(self) -> &'static str { match self { Op::Move => "Move", Op::Copy => "Copy", } } } /// The single active dialog. The browser renders whichever is `Some`. #[derive(Clone)] pub enum Dialog { /// Text input (new folder, rename). Prompt { title: String, label: String, initial: String, submit: String, on_submit: Callback, }, /// Simple confirmation (delete). Confirm { title: String, message: String, submit: String, danger: bool, on_submit: Callback<()>, }, /// Overwrite warning listing the conflicting files. Conflict { title: String, files: Vec, on_submit: Callback<()>, }, /// Move/copy destination picker. Picker { op: Op, entry: Entry, roots: Vec, source_root: i64, source_dir: String, on_pick: Callback<(i64, String)>, }, } /// Split a "/"-separated directory path into its non-empty segments. fn dir_segs(dir: &str) -> Vec { dir.split('/') .filter(|s| !s.is_empty()) .map(|s| s.to_string()) .collect() } #[component] pub fn DialogView( dialog: ReadSignal>, set_dialog: WriteSignal>, ) -> impl IntoView { view! { {move || { let Some(d) = dialog.get() else { return view! {}.into_any(); }; let close = Callback::new(move |_| set_dialog.set(None)); match &d { Dialog::Prompt { title, label, initial, submit, on_submit, } => { let (title, label, initial, submit, on_submit, close) = ( title.clone(), label.clone(), initial.clone(), submit.clone(), on_submit.clone(), close.clone(), ); view! { } .into_view() .into_any() } Dialog::Confirm { title, message, submit, danger, on_submit, } => { let (title, message, submit, danger, on_submit, close) = ( title.clone(), message.clone(), submit.clone(), *danger, on_submit.clone(), close.clone(), ); view! { } .into_view() .into_any() } Dialog::Conflict { title, files, on_submit, } => { let (title, files, on_submit, close) = ( title.clone(), files.clone(), on_submit.clone(), close.clone(), ); view! { } .into_view() .into_any() } Dialog::Picker { op, entry, roots, source_root, source_dir, on_pick, } => { let (op, entry, roots, source_root, source_dir, on_pick, close) = ( *op, entry.clone(), roots.clone(), *source_root, source_dir.clone(), on_pick.clone(), close.clone(), ); view! { } .into_view() .into_any() } } }} } } // --------------------------------------------------------------------------- // Prompt (text input) // --------------------------------------------------------------------------- #[component] fn PromptDialog( title: String, label: String, initial: String, submit: String, on_submit: Callback, on_close: Callback<()>, ) -> impl IntoView { let input_id = "fbng-prompt-input"; let id = input_id.to_string(); let id2 = id.clone(); let submit_cb = on_submit; view! { } } // --------------------------------------------------------------------------- // Confirm // --------------------------------------------------------------------------- #[component] fn ConfirmDialog( title: String, message: String, submit: String, danger: bool, on_submit: Callback<()>, on_close: Callback<()>, ) -> impl IntoView { view! { } } // --------------------------------------------------------------------------- // Conflict (overwrite warning) // --------------------------------------------------------------------------- #[component] fn ConflictDialog( title: String, files: Vec, on_submit: Callback<()>, on_close: Callback<()>, ) -> impl IntoView { let shown: Vec = files.iter().take(20).cloned().collect(); let extra = files.len().saturating_sub(shown.len()); view! { } } // --------------------------------------------------------------------------- // Picker (move / copy destination) // --------------------------------------------------------------------------- #[component] fn PickerDialog( op: Op, entry: Entry, roots: Vec, source_root: i64, source_dir: String, on_pick: Callback<(i64, String)>, on_close: Callback<()>, ) -> impl IntoView { let (sel_root, set_sel_root) = signal(source_root); let (sel_dir, set_sel_dir) = signal(source_dir.clone()); let (entries, set_entries) = signal(Vec::::new()); // Load the subfolders of the selected directory whenever it changes. { let root_sig = sel_root; let dir_sig = sel_dir; let set_ent = set_entries; Effect::new(move |_| { let rid = root_sig.get(); let dir = dir_sig.get(); set_ent.set(Vec::new()); spawn_local(async move { match api::list_files(rid, &dir).await { Ok(r) => set_ent.set( r.entries .into_iter() .filter(|e| e.is_dir) .collect(), ), Err(_) => set_ent.set(Vec::new()), } }); }); } let sel_root2 = sel_root; let sel_dir2 = sel_dir; let set_dir = set_sel_dir; let roots_by_id = roots.clone(); view! { } }