File dialogs: offer every movie type the importer reads (#1701)

Import and Set Proxy build their filters from the shared movie list, so
they offer AVI; the web build's file picker gets the same list.
This commit is contained in:
tylerjenningsw
2026-10-17 11:35:05 -04:00
parent e26b4af5c0
commit 93a9295125
2 changed files with 26 additions and 13 deletions
+14 -5
View File
@@ -1337,6 +1337,8 @@ fn file_dialog(app: &mut EffectcraftApp, id: &str, params: &Value) -> Option<Res
OpenProject,
Save(&'static str),
Open(&'static [&'static str]),
/// A movie, or a file with one of these extensions.
OpenFootage(&'static [&'static str]),
}
let (key, ask) = match id {
"file.import" | "file.importMultiple" => ("paths", Ask::Import),
@@ -1366,7 +1368,7 @@ fn file_dialog(app: &mut EffectcraftApp, id: &str, params: &Value) -> Option<Res
"keys.rpfCameraImport" => ("path", Ask::Open(&["json", "csv", "txt"])),
"essential.exportTemplate" => ("path", Ask::Save("Template.ectemplate")),
"essential.importTemplate" => ("path", Ask::Open(&["ectemplate"])),
"file.setProxy" => ("path", Ask::Open(&["mp4", "mov", "m4v", "mkv", "webm", "mxf", "png", "jpg", "jpeg", "gif", "webp", "tif", "tiff", "bmp", "exr"])),
"file.setProxy" => ("path", Ask::OpenFootage(&["png", "jpg", "jpeg", "gif", "webp", "tif", "tiff", "bmp", "exr"])),
_ => return None,
};
if params.get(key).is_some()
@@ -1377,19 +1379,22 @@ fn file_dialog(app: &mut EffectcraftApp, id: &str, params: &Value) -> Option<Res
{
return None;
}
// Importable files besides movies.
const MEDIA: &[&str] = &[
"mp4", "mov", "m4v", "mkv", "webm", "mxf", "png", "jpg", "jpeg", "gif", "webp", "tif", "tiff", "bmp", "exr", "dpx", "cin", "wav", "aif", "aiff", "mp3",
"flac", "ogg", "opus", "svg", "pdf", "ai", "eps", "psd", "psb", "tga", "hdr", "gltf", "glb", "obj", "stl", "fbx", "json", "csv", "tsv",
"png", "jpg", "jpeg", "gif", "webp", "tif", "tiff", "bmp", "exr", "dpx", "cin", "wav", "aif", "aiff", "mp3", "flac", "ogg", "opus", "svg", "pdf", "ai",
"eps", "psd", "psb", "tga", "hdr", "gltf", "glb", "obj", "stl", "fbx", "json", "csv", "tsv",
];
let footage = |more: &[&'static str]| effectcraft_project::VIDEO_EXTENSIONS.iter().chain(more).copied().collect::<Vec<_>>();
// A host whose modal dialogs would run a nested event loop inside this frame (macOS: a
// drag over the window then re-entered winit's event handler and aborted, #511) shows the
// dialog without blocking; the command continues when the answer arrives.
if let Some(spawn) = app.hooks.file_dialog_async.as_ref() {
let request = match ask {
Ask::Folder => crate::FileDialog::Folder,
Ask::Import => crate::FileDialog::Open { exts: MEDIA.iter().map(|e| e.to_string()).collect(), many: key == "paths" },
Ask::Import => crate::FileDialog::Open { exts: footage(MEDIA).iter().map(|e| e.to_string()).collect(), many: key == "paths" },
Ask::OpenProject => crate::FileDialog::OpenProject,
Ask::Open(exts) => crate::FileDialog::Open { exts: exts.iter().map(|e| e.to_string()).collect(), many: false },
Ask::OpenFootage(more) => crate::FileDialog::Open { exts: footage(more).iter().map(|e| e.to_string()).collect(), many: false },
Ask::Save(default) => crate::FileDialog::save(default),
};
let tx = app.dialog_answers.0.clone();
@@ -1409,7 +1414,7 @@ fn file_dialog(app: &mut EffectcraftApp, id: &str, params: &Value) -> Option<Res
}
Ask::Import => {
let Some(f) = app.hooks.pick_files.as_ref() else { return Some(Err("no file dialog available (pass `paths`)".into())) };
picked_value(key, f(MEDIA))
picked_value(key, f(&footage(MEDIA)))
}
Ask::OpenProject => {
let Some(f) = app.hooks.pick_open_project.as_ref() else { return Some(Err("no file dialog available (pass `path`)".into())) };
@@ -1419,6 +1424,10 @@ fn file_dialog(app: &mut EffectcraftApp, id: &str, params: &Value) -> Option<Res
let Some(f) = app.hooks.pick_files.as_ref() else { return Some(Err("no file dialog available (pass `path`)".into())) };
f(exts).first().map(|s| json!(s))
}
Ask::OpenFootage(more) => {
let Some(f) = app.hooks.pick_files.as_ref() else { return Some(Err("no file dialog available (pass `path`)".into())) };
f(&footage(more)).first().map(|s| json!(s))
}
Ask::Save(default) => {
let Some(picked) = app.hooks.save_dialog(default) else { return Some(Err("no file dialog available (pass `path`)".into())) };
picked.map(|s| json!(s))
+12 -8
View File
@@ -205,10 +205,10 @@ fn relink_restores_decoded_pixels_and_invalidates_viewer_content() {
std::fs::remove_dir_all(dir).unwrap();
}
/// #767: File ▸ Import ▸ File… offers MXF footage (OP1a / OP-Atom from broadcast cameras and
/// Avid), which the Media Browser lists and the importer reads.
/// #767, #1701: File ▸ Import ▸ File… and the proxy dialog offer every movie type the importer
/// reads (MXF from broadcast cameras and Avid, AVI, …), the list the Media Browser goes by too.
#[test]
fn import_dialog_offers_mxf() {
fn import_and_proxy_dialogs_offer_every_movie_type() {
use std::cell::RefCell;
use std::rc::Rc;
@@ -219,11 +219,15 @@ fn import_dialog_offers_mxf() {
*seen.borrow_mut() = exts.iter().map(|e| e.to_string()).collect();
Vec::new()
}));
let result = effectcraft_ui_egui::menus::invoke(&mut app, &egui::Context::default(), "file.import", json!({})).unwrap();
assert!(result.is_null(), "cancelled: nothing imported");
let offered = offered.borrow();
assert!(offered.iter().any(|e| e == "mxf"), "offered {offered:?}");
assert!(offered.iter().any(|e| e == "mov"));
for (command, still) in [("file.import", "psd"), ("file.setProxy", "png")] {
let result = effectcraft_ui_egui::menus::invoke(&mut app, &egui::Context::default(), command, json!({})).unwrap();
assert!(result.is_null(), "{command} cancelled: nothing done");
let offered = offered.borrow();
for ext in effectcraft_project::VIDEO_EXTENSIONS.iter().chain([&still]) {
assert!(offered.iter().any(|e| e == ext), "{command} offers {ext}: {offered:?}");
}
assert!(offered.iter().any(|e| e == "avi"), "{command}: {offered:?}");
}
}
/// #1059: opening the demo from the start screen and immediately pressing Spacebar previews