Make a folder from the server browser

The in-app browser that chooses a library folder on the server could
only open folders that already existed, so a library, or an export
destination, that was not on the server yet had to be made in the
Nextcloud web page first. It now has "New folder": a name, then MKCOL,
then the parent listed again and the new folder walked into — a folder
somebody has just named is the one they mean to choose.

The listing is the server's rather than the name inserted locally: the
server may have normalised or refused it. A name with a slash, "..",
or nothing at all is refused before any request, because a folder
typed with a slash in it is a path the user did not mean.

remote_folders holds the two WebDAV round trips (list, make) off the UI
thread, with the answer delivered through a Slint timer, so the album
sheet can use the same browser.
This commit is contained in:
2026-09-26 14:13:53 -04:00
parent 6683c14b40
commit 2eb06b1064
5 changed files with 272 additions and 4 deletions
+2
View File
@@ -320,6 +320,7 @@ export component AppWindow inherits Window {
callback launch-browse-up();
callback launch-browse-confirm();
callback launch-browse-cancel();
callback launch-browse-make(string);
// --- library grid (FR-CAT-4) ---
//
@@ -1374,6 +1375,7 @@ in property <bool> panel-visible: true;
browse-up() => { root.launch-browse-up(); }
browse-confirm() => { root.launch-browse-confirm(); }
browse-cancel() => { root.launch-browse-cancel(); }
browse-make(name) => { root.launch-browse-make(name); }
}
// The library view sits between the launch screen and develop: a library
+50 -4
View File
@@ -124,6 +124,11 @@ export component LaunchScreen inherits Rectangle {
callback browse-up();
callback browse-confirm();
callback browse-cancel();
/// Make a folder here, by name, and go into it.
callback browse-make(string);
// Whether the picker is asking for a new folder's name.
property <bool> naming-folder: false;
background: Theme.ground;
@@ -393,10 +398,51 @@ export component LaunchScreen inherits Rectangle {
}
// Current location, so it is always clear what
// "Use this folder" would select.
Value {
text: root.browse-path == "" ? "/" : "/" + root.browse-path;
overflow: elide;
// "Use this folder" would select — and beside it the way
// to make one, for a library that is not on the server
// yet.
HorizontalLayout {
spacing: Theme.gap;
Value {
text: root.browse-path == "" ? "/" : "/" + root.browse-path;
overflow: elide;
horizontal-stretch: 1;
min-width: 0px;
vertical-alignment: center;
}
FormButton {
text: @tr("New folder");
enabled: !root.browse-loading && !root.naming-folder;
clicked => { root.naming-folder = true; }
}
}
if root.naming-folder: HorizontalLayout {
spacing: Theme.gap;
new-name := Field {
horizontal-stretch: 1;
label: @tr("New folder name");
placeholder: @tr("Folder name");
accepted(t) => {
if (t != "") {
root.browse-make(t);
root.naming-folder = false;
}
}
init => { self.take-focus(); }
}
FormButton {
text: @tr("Make");
enabled: new-name.text != "";
clicked => {
root.browse-make(new-name.text);
root.naming-folder = false;
}
}
FormButton {
text: @tr("Cancel");
clicked => { root.naming-folder = false; }
}
}
// Not a `Panel`: this box overlays three mutually