Make "Choose folder" an actual folder picker
It previously fetched the folder list and threw it away into a status line — a button that looked like it worked and did not. Now it opens a browsable picker: click a folder to descend, ".." to go back, "Use this folder" to select, "Cancel" to leave the root unchanged. Descends one level per click because that is what the backend supports: Depth: infinity is frequently disabled server-side and prohibitively expensive where it is not (ARCH §8.4). The chosen root persists immediately on confirm, so it survives a crash before the library is opened. Confirming at the account root is allowed — a user may legitimately keep everything at the top level — and cancelling leaves any previous selection untouched, which a test asserts. Verified against nextcloud.tourolle.paris at both depths: 30 folders at the root, 21 year-folders inside PhotosRaw. 19 launch tests, 38 in dr-ui.
This commit is contained in:
@@ -66,6 +66,20 @@ pub fn render(window: &AppWindow, controller: &LaunchController) {
|
||||
window.set_launch_status(status.unwrap_or_default().into());
|
||||
window.set_launch_error(m.error.clone().unwrap_or_default().into());
|
||||
|
||||
// Folder picker.
|
||||
let browsing = m.browser.is_some();
|
||||
window.set_launch_browsing(browsing);
|
||||
if let Some(b) = &m.browser {
|
||||
window.set_launch_browse_path(b.path.clone().into());
|
||||
window.set_launch_browse_loading(b.loading);
|
||||
let entries: Vec<slint::SharedString> = b
|
||||
.entries
|
||||
.iter()
|
||||
.map(|e| slint::SharedString::from(e.as_str()))
|
||||
.collect();
|
||||
window.set_launch_browse_entries(slint::ModelRc::new(slint::VecModel::from(entries)));
|
||||
}
|
||||
|
||||
let labels: Vec<slint::SharedString> = m
|
||||
.formats
|
||||
.iter()
|
||||
@@ -148,9 +162,9 @@ where
|
||||
let ctl = controller.clone();
|
||||
window.on_launch_choose_folder(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
ctl.model.borrow_mut().busy("Listing folders…");
|
||||
ctl.model.borrow_mut().open_browser();
|
||||
render(&w, &ctl);
|
||||
spawn_folder_list(w.as_weak(), ctl.clone());
|
||||
spawn_folder_list(w.as_weak(), ctl.clone(), String::new());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -168,6 +182,66 @@ where
|
||||
});
|
||||
}
|
||||
|
||||
// --- folder picker navigation ---------------------------------------
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = controller.clone();
|
||||
window.on_launch_browse_into(move |name| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let target = {
|
||||
let m = ctl.model.borrow();
|
||||
m.browser.as_ref().map(|b| b.child_path(&name))
|
||||
};
|
||||
if let Some(path) = target {
|
||||
ctl.model.borrow_mut().browse_to(path.clone());
|
||||
render(&w, &ctl);
|
||||
spawn_folder_list(w.as_weak(), ctl.clone(), path);
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = controller.clone();
|
||||
window.on_launch_browse_up(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let parent = {
|
||||
let m = ctl.model.borrow();
|
||||
m.browser.as_ref().and_then(|b| b.parent_path())
|
||||
};
|
||||
if let Some(path) = parent {
|
||||
ctl.model.borrow_mut().browse_to(path.clone());
|
||||
render(&w, &ctl);
|
||||
spawn_folder_list(w.as_weak(), ctl.clone(), path);
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = controller.clone();
|
||||
window.on_launch_browse_confirm(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let chosen = ctl.model.borrow_mut().choose_current_folder();
|
||||
if let Some(session) = chosen {
|
||||
// Persist immediately: a chosen root must survive a crash
|
||||
// before the library is opened.
|
||||
if let Err(e) = ctl.store.update(&session) {
|
||||
log::warn!("saving library root: {e}");
|
||||
}
|
||||
log::info!("library root set to /{}", session.root);
|
||||
}
|
||||
render(&w, &ctl);
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = controller.clone();
|
||||
window.on_launch_browse_cancel(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
ctl.model.borrow_mut().close_browser();
|
||||
render(&w, &ctl);
|
||||
});
|
||||
}
|
||||
|
||||
// --- copy the login URL ---------------------------------------------
|
||||
{
|
||||
let ctl = controller.clone();
|
||||
@@ -301,7 +375,7 @@ fn poll_channel(
|
||||
}
|
||||
|
||||
/// List top-level folders so one can be chosen as the library root.
|
||||
fn spawn_folder_list(weak: slint::Weak<AppWindow>, ctl: Rc<LaunchController>) {
|
||||
fn spawn_folder_list(weak: slint::Weak<AppWindow>, ctl: Rc<LaunchController>, path: String) {
|
||||
let Some(session) = ctl.model.borrow().session().cloned() else {
|
||||
return;
|
||||
};
|
||||
@@ -329,7 +403,7 @@ fn spawn_folder_list(weak: slint::Weak<AppWindow>, ctl: Rc<LaunchController>) {
|
||||
};
|
||||
rt.block_on(async {
|
||||
match NextcloudBackend::new(&creds, &user_id) {
|
||||
Ok(b) => match b.list(&RemotePath::root(), None).await {
|
||||
Ok(b) => match b.list(&RemotePath::new(&path), None).await {
|
||||
Ok(entries) => {
|
||||
let mut dirs: Vec<String> = entries
|
||||
.iter()
|
||||
|
||||
Reference in New Issue
Block a user