Skip to content

Commit 074bd55

Browse files
committed
prefill base image from name
1 parent f0ee6f7 commit 074bd55

File tree

3 files changed

+427
-3
lines changed

3 files changed

+427
-3
lines changed

src/dialogs/create_distrobox_dialog.rs

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pub enum FileRowSelection {
2222
Folder,
2323
}
2424
mod imp {
25+
use crate::dialogs::create_distrobox_helpers::split_repo_tag_digest;
26+
2527
use super::*;
2628

2729
#[derive(Default, Properties)]
@@ -37,6 +39,7 @@ mod imp {
3739
pub image_row: adw::ActionRow,
3840
pub images_model: gtk::StringList,
3941
pub selected_image: RefCell<String>,
42+
pub prefill_generation: std::cell::Cell<u64>,
4043
pub home_row_expander: adw::ExpanderRow,
4144
#[property(get, set, nullable)]
4245
pub home_folder: RefCell<Option<String>>,
@@ -154,7 +157,22 @@ mod imp {
154157
#[weak]
155158
obj,
156159
move |_| {
157-
let picker = obj.build_image_picker_view();
160+
// Read the current subtitle and derive an initial search string
161+
let subtitle: String = obj.imp().image_row.property("subtitle");
162+
let default_sub = gettext("Select an image...");
163+
164+
let initial_search_repo: &str = split_repo_tag_digest(
165+
if subtitle == default_sub {
166+
""
167+
} else {
168+
&subtitle
169+
},
170+
).0;
171+
// A repo is docker.io/library/xyz by default, we only want to search by 'xyz'
172+
let initial_search = initial_search_repo.rsplit('/').next().unwrap_or(initial_search_repo);
173+
174+
175+
let picker = obj.build_image_picker_view(Some(initial_search));
158176
obj.imp().navigation_view.push(&picker);
159177
}
160178
));
@@ -229,6 +247,54 @@ mod imp {
229247

230248
self.content.append(&create_btn);
231249

250+
// Prefill wiring: debounce name changes to suggest an image when user hasn't interacted
251+
let obj_for_prefill = self.obj().clone();
252+
let name_row = obj_for_prefill.imp().name_row.clone();
253+
name_row.connect_changed(clone!(
254+
#[weak]
255+
obj_for_prefill,
256+
move |entry| {
257+
let imp = obj_for_prefill.imp();
258+
let prefill_gen = imp.prefill_generation.get().wrapping_add(1);
259+
imp.prefill_generation.set(prefill_gen);
260+
let text = entry.text().to_string();
261+
let obj_inner = obj_for_prefill.clone();
262+
glib::MainContext::ref_thread_default().spawn_local(clone!(
263+
#[weak]
264+
obj_inner,
265+
async move {
266+
glib::timeout_future(std::time::Duration::from_millis(300)).await;
267+
let imp = obj_inner.imp();
268+
if imp.prefill_generation.get() != prefill_gen {
269+
return;
270+
}
271+
// don't prefill if cloning from a source
272+
if imp.clone_src.borrow().is_some() {
273+
return;
274+
}
275+
if text.is_empty() {
276+
if imp.selected_image.borrow().is_empty() {
277+
imp.image_row.set_subtitle(&gettext("Select an image..."));
278+
}
279+
} else {
280+
let candidates = imp.images_model.snapshot().into_iter().filter_map(|item| {
281+
item.downcast::<gtk::StringObject>().ok().map(|sobj| sobj.string().to_string())
282+
}).collect::<Vec<_>>();
283+
284+
let (_filter, suggested_opt) =
285+
crate::dialogs::create_distrobox_helpers::derive_image_prefill(&text, Some(&candidates));
286+
if let Some(suggested) = suggested_opt {
287+
// set subtitle as tentative prefill (do not overwrite confirmed selection)
288+
if imp.selected_image.borrow().is_empty() {
289+
imp.image_row.set_subtitle(&suggested);
290+
}
291+
}
292+
}
293+
}
294+
));
295+
}
296+
));
297+
232298
// Create page for assemble from file
233299
let assemble_page = gtk::Box::new(gtk::Orientation::Vertical, 12);
234300
assemble_page.set_margin_start(12);
@@ -486,7 +552,7 @@ impl CreateDistroboxDialog {
486552
row
487553
}
488554

489-
pub fn build_image_picker_view(&self) -> adw::NavigationPage {
555+
pub fn build_image_picker_view(&self, initial_search: Option<&str>) -> adw::NavigationPage {
490556
let view = adw::ToolbarView::new();
491557

492558
let header = adw::HeaderBar::new();
@@ -495,6 +561,9 @@ impl CreateDistroboxDialog {
495561
let search_entry = gtk::SearchEntry::new();
496562
search_entry.set_placeholder_text(Some(&gettext("Search image...")));
497563
search_entry.set_hexpand(true);
564+
if let Some(text) = initial_search {
565+
search_entry.set_text(text);
566+
}
498567

499568
header.set_title_widget(Some(&search_entry));
500569

@@ -536,6 +605,9 @@ impl CreateDistroboxDialog {
536605
let child: &ImageRowItem = child.and_downcast_ref().unwrap();
537606
child.set_image(&image);
538607

608+
// TODO: Consider doing an availability check (remote / lazy-download)
609+
// to determine if an image is actually accessible, not just in the
610+
// downloaded tags set.
539611
let is_downloaded = obj.imp().downloaded_tags.borrow().contains(image.as_str());
540612
child.set_is_downloaded(is_downloaded);
541613
});
@@ -673,7 +745,21 @@ impl CreateDistroboxDialog {
673745

674746
pub async fn extract_create_args(&self) -> Result<CreateArgs, Error> {
675747
let imp = self.imp();
676-
let image = imp.selected_image.borrow().clone();
748+
let image = {
749+
let sel = imp.selected_image.borrow();
750+
if sel.is_empty() {
751+
// fallback to the action row subtitle (tentative prefill)
752+
let subtitle: String = imp.image_row.property("subtitle");
753+
let default_sub = gettext("Select an image...");
754+
if subtitle.is_empty() || subtitle == default_sub {
755+
String::new()
756+
} else {
757+
subtitle
758+
}
759+
} else {
760+
sel.clone()
761+
}
762+
};
677763
if image.is_empty() && imp.clone_src.borrow().is_none() {
678764
return Err(Error::InvalidField(
679765
"image".into(),

0 commit comments

Comments
 (0)