* claim: #1801 * Insert SVG: the file picker opens on SVG files (#1801) Every Open went through one host picker whose filters start with designs, CAD files and meshes, so Insert SVG (and Insert DXF) in a sketch opened on STEP, IGES and meshes, and the SVG file had to be found under another filter. The host's pick_open now takes the extensions the command reads and offers them as the picker's first, default filter. Insert SVG and Insert DXF pass their type; every other Open passes none and keeps the list it had. The browser host ignores them (it shows its own file panel). --------- Co-authored-by: MateuszSychXpl <[email protected]>
This commit is contained in:
1 parent
a6ca2e8ca1
commit
c599238653
7 files changed
+46
-14
No files matched your search
@@ -481,7 +481,7 @@ mod web {
|
||||
let panel = files.clone();
|
||||
let services = Services {
|
||||
// Open shows the browser's files (and the computer's, from there).
|
||||
pick_open: Some(Box::new(move || {
|
||||
pick_open: Some(Box::new(move |_: &[&str]| {
|
||||
panel.panel.set(true);
|
||||
Box::pin(std::future::ready(None))
|
||||
})),
|
||||
|
||||
@@ -117,9 +117,15 @@ fn services(cc: &eframe::CreationContext<'_>) -> Services {
|
||||
let open_parent = parent.clone();
|
||||
let program_parent = parent.clone();
|
||||
Services {
|
||||
pick_open: Some(Box::new(move || {
|
||||
let future = open_parent
|
||||
.clone()
|
||||
pick_open: Some(Box::new(move |first: &[&str]| {
|
||||
// The file type the command reads comes first, so the picker opens on it (#1801).
|
||||
let mut dialog = open_parent.clone();
|
||||
if !first.is_empty() {
|
||||
let label = first.iter().map(|e| e.to_uppercase()).collect::<Vec<_>>().join(", ");
|
||||
let both: Vec<String> = first.iter().flat_map(|e| [e.to_lowercase(), e.to_uppercase()]).collect();
|
||||
dialog = dialog.add_filter(label, &both);
|
||||
}
|
||||
let future = dialog
|
||||
.add_filter(
|
||||
"Designs, CAD files and meshes",
|
||||
&[
|
||||
|
||||
@@ -34,10 +34,15 @@ impl SolveApp {
|
||||
}
|
||||
|
||||
pub(crate) fn pick_open(&mut self, complete: impl FnOnce(&mut SolveApp, Option<String>) + 'static) {
|
||||
self.pick_open_for(&[], complete);
|
||||
}
|
||||
|
||||
/// Open a file of this type: the picker offers these extensions first (#1801).
|
||||
pub(crate) fn pick_open_for(&mut self, extensions: &[&str], complete: impl FnOnce(&mut SolveApp, Option<String>) + 'static) {
|
||||
if self.file_picker_busy() {
|
||||
return;
|
||||
}
|
||||
let future = self.services.pick_open.as_ref().map(|pick| pick());
|
||||
let future = self.services.pick_open.as_ref().map(|pick| pick(extensions));
|
||||
self.begin_picker(future, Box::new(complete));
|
||||
}
|
||||
|
||||
@@ -66,9 +71,9 @@ impl SolveApp {
|
||||
self.file_picker = Some(Pending { future, complete });
|
||||
}
|
||||
|
||||
pub(crate) fn pick_open_command(&mut self, command: &str, params: Value) {
|
||||
pub(crate) fn pick_open_command(&mut self, command: &str, params: Value, extensions: &[&str]) {
|
||||
let command = command.to_string();
|
||||
self.pick_open(move |app, path| {
|
||||
self.pick_open_for(extensions, move |app, path| {
|
||||
if let Some(path) = path {
|
||||
let mut params = params;
|
||||
params["path"] = json!(path);
|
||||
@@ -144,7 +149,7 @@ mod tests {
|
||||
if save {
|
||||
app.services.pick_save = Some(Box::new(move |_, _| Box::pin(Delayed(captured.clone()))));
|
||||
} else {
|
||||
app.services.pick_open = Some(Box::new(move || Box::pin(Delayed(captured.clone()))));
|
||||
app.services.pick_open = Some(Box::new(move |_| Box::pin(Delayed(captured.clone()))));
|
||||
}
|
||||
answer
|
||||
}
|
||||
@@ -275,6 +280,25 @@ mod tests {
|
||||
assert!(!crate::documents::any_dirty(&h.app));
|
||||
}
|
||||
|
||||
/// #1801: Insert SVG and Insert DXF ask the host for their own file type first; other
|
||||
/// opens keep the default list.
|
||||
#[test]
|
||||
fn sketch_inserts_ask_for_their_file_type() {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
let asked: Rc<RefCell<Vec<Vec<String>>>> = Rc::default();
|
||||
let log = asked.clone();
|
||||
h.app.services.pick_open = Some(Box::new(move |extensions| {
|
||||
log.borrow_mut().push(extensions.iter().map(|e| e.to_string()).collect());
|
||||
Box::pin(std::future::ready(None))
|
||||
}));
|
||||
h.app.run("sketch.create", json!({"plane": "XY"})).unwrap();
|
||||
for (command, want) in [("sketch.insert_svg", vec!["svg"]), ("sketch.insert_dxf", vec!["dxf"]), ("file.insert_step", vec![])] {
|
||||
h.app.start(command);
|
||||
tick(&mut h.app, &h.ctx);
|
||||
assert_eq!(asked.borrow().last().cloned(), Some(want.iter().map(|e| e.to_string()).collect::<Vec<_>>()), "{command}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn picker_holds_engine_requests_but_answers_window_resize_requests() {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
|
||||
@@ -254,7 +254,9 @@ impl Default for UiState {
|
||||
/// Host services injected by the app so this crate stays portable.
|
||||
#[derive(Default)]
|
||||
pub struct Services {
|
||||
pub pick_open: Option<Box<dyn Fn() -> FilePickerFuture>>,
|
||||
/// Ask for a file to open. The extensions are the file type the command reads, offered as
|
||||
/// the picker's first filter (`&[]`: designs, CAD files and meshes, #1801).
|
||||
pub pick_open: Option<Box<dyn Fn(&[&str]) -> FilePickerFuture>>,
|
||||
pub pick_save: Option<Box<dyn Fn(&str, &[&str]) -> FilePickerFuture>>,
|
||||
/// Open a dialog asking for a program (a slicer); `None` when the host has no such dialog.
|
||||
pub pick_program: Option<Box<dyn Fn() -> FilePickerFuture>>,
|
||||
@@ -736,8 +738,8 @@ impl SolveApp {
|
||||
dim_view::cancel();
|
||||
self.tool = None;
|
||||
self.home.open = false;
|
||||
if matches!(id, "sketch.insert_dxf" | "sketch.insert_svg") {
|
||||
self.pick_open_command(id, json!({}));
|
||||
if let Some(ext) = id.strip_prefix("sketch.insert_").filter(|e| matches!(*e, "dxf" | "svg")) {
|
||||
self.pick_open_command(id, json!({}), &[ext]);
|
||||
return;
|
||||
}
|
||||
if id == "component.insert_design" {
|
||||
|
||||
@@ -144,7 +144,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
ui.add(egui::TextEdit::singleline(&mut d.filter).hint_text(crate::i18n::tr_label("Filter")).desired_width(180.0));
|
||||
ui.separator();
|
||||
if ui.button("Import…").on_hover_text(crate::i18n::tr_label("CSV or JSON: updates existing parameters, adds new ones")).clicked() {
|
||||
app.pick_open_command("parameters.import", json!({}));
|
||||
app.pick_open_command("parameters.import", json!({}), &[]);
|
||||
}
|
||||
for (label, ext) in [("Export CSV…", "csv"), ("Export JSON…", "json")] {
|
||||
if ui.button(label).clicked() {
|
||||
|
||||
@@ -21,5 +21,5 @@ pub fn save(app: &mut SolveApp, slot: &Slot) {
|
||||
}
|
||||
pub fn open(app: &mut SolveApp, slot: &Slot) {
|
||||
let slot = slot.clone();
|
||||
app.services.pick_open = Some(Box::new(move || Box::pin(Manual(slot.clone())) as FilePickerFuture));
|
||||
app.services.pick_open = Some(Box::new(move |_| Box::pin(Manual(slot.clone())) as FilePickerFuture));
|
||||
}
|
||||
@@ -183,7 +183,7 @@ impl Harness {
|
||||
self.app.services.pick_save =
|
||||
Some(Box::new(move |name, _| Box::pin(std::future::ready(Some(dir.join(name).to_string_lossy().into_owned())))));
|
||||
let next = self.next_file.clone();
|
||||
self.app.services.pick_open = Some(Box::new(move || Box::pin(std::future::ready(next.borrow_mut().take()))));
|
||||
self.app.services.pick_open = Some(Box::new(move |_| Box::pin(std::future::ready(next.borrow_mut().take()))));
|
||||
}
|
||||
|
||||
/// Autosave into the scenario's recovery folder (as the desktop app does into the user's).
|
||||
|
||||
Reference in new issue
Block a user