mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-02 22:42:24 -06:00
G2: Save As starts from the open project's name (#2788)
This commit is contained in:
@@ -546,39 +546,47 @@ pub(super) fn file_dialog(app: &mut EffectcraftApp, id: &str, params: &Value) ->
|
||||
Folder,
|
||||
Import,
|
||||
OpenProject,
|
||||
Save(&'static str),
|
||||
Save(std::borrow::Cow<'static, str>),
|
||||
Open(&'static [&'static str]),
|
||||
/// A movie, or a file with one of these extensions.
|
||||
OpenFootage(&'static [&'static str]),
|
||||
}
|
||||
// Save As, Save a Copy and Save a Copy As XML start from the open project's file, in its
|
||||
// folder, as After Effects does (#2788); an untitled project starts from Untitled Project.
|
||||
let project_as = |ext: &str| -> std::borrow::Cow<'static, str> {
|
||||
match app.session.path.as_deref() {
|
||||
Some(p) => std::path::Path::new(p).with_extension(ext).to_string_lossy().into_owned().into(),
|
||||
None => format!("Untitled Project.{ext}").into(),
|
||||
}
|
||||
};
|
||||
let (key, ask) = match id {
|
||||
"file.import" | "file.importMultiple" => ("paths", Ask::Import),
|
||||
"file.open" => ("path", Ask::OpenProject),
|
||||
"file.save" if app.session.path.is_none() => ("path", Ask::Save("Untitled Project.ecproj")),
|
||||
"file.saveAs" | "file.saveCopy" => ("path", Ask::Save("Untitled Project.ecproj")),
|
||||
"comp.saveFrameAs" => ("path", Ask::Save("Frame.png")),
|
||||
"comp.saveFrameAsPsd" => ("path", Ask::Save("Frame.psd")),
|
||||
"comp.saveFrameAsExr" => ("path", Ask::Save("Frame.exr")),
|
||||
"file.watchFolder" if params.get("stop").is_none() => ("folder", Ask::Save("Watch Folder")),
|
||||
"anim.savePreset" => ("path", Ask::Save("Preset.ecpreset")),
|
||||
"file.save" if app.session.path.is_none() => ("path", Ask::Save(project_as("ecproj"))),
|
||||
"file.saveAs" | "file.saveCopy" => ("path", Ask::Save(project_as("ecproj"))),
|
||||
"comp.saveFrameAs" => ("path", Ask::Save("Frame.png".into())),
|
||||
"comp.saveFrameAsPsd" => ("path", Ask::Save("Frame.psd".into())),
|
||||
"comp.saveFrameAsExr" => ("path", Ask::Save("Frame.exr".into())),
|
||||
"file.watchFolder" if params.get("stop").is_none() => ("folder", Ask::Save("Watch Folder".into())),
|
||||
"anim.savePreset" => ("path", Ask::Save("Preset.ecpreset".into())),
|
||||
"anim.applyPreset" => ("path", Ask::Open(&["ecpreset", "json"])),
|
||||
"view.exportGuides" => ("path", Ask::Save("Guides.json")),
|
||||
"view.exportGuides" => ("path", Ask::Save("Guides.json".into())),
|
||||
"view.importGuides" => ("path", Ask::Open(&["json"])),
|
||||
"camera.exportBlender" => ("path", Ask::Save("Tracked Scene.py")),
|
||||
"file.exportLottie" => ("path", Ask::Save("Animation.json")),
|
||||
"camera.exportBlender" => ("path", Ask::Save("Tracked Scene.py".into())),
|
||||
"file.exportLottie" => ("path", Ask::Save("Animation.json".into())),
|
||||
"file.importLottie" => ("path", Ask::Open(&["json", "lottie"])),
|
||||
"file.importProject" => ("path", Ask::Open(&["ecproj", "ecprojx"])),
|
||||
"file.importTimeline" => ("path", Ask::Open(&["xml", "fcpxml", "otio", "edl", "aaf", "omf"])),
|
||||
"render.saveCurrentPreview" => ("path", Ask::Save("Preview.mp4")),
|
||||
"render.saveCurrentPreview" => ("path", Ask::Save("Preview.mp4".into())),
|
||||
"file.runScript" if params.get("name").is_none() && params.get("recent").is_none() => ("path", Ask::Open(&["jsx", "js", "jsonl", "json", "txt"])),
|
||||
"file.installScript" | "file.installScriptUIPanel" => ("path", Ask::Open(&["jsx", "js"])),
|
||||
"effect.plugins.load" if params.get("folder").is_none() => ("path", Ask::Open(&["wasm", "wat"])),
|
||||
"file.replaceFootage" => ("path", Ask::Import),
|
||||
"file.relinkFootage" => ("folder", Ask::Folder),
|
||||
"file.collectFiles" => ("folder", Ask::Save("Collected Files")),
|
||||
"file.saveCopyAsXml" => ("path", Ask::Save("Untitled Project.ecprojx")),
|
||||
"file.collectFiles" => ("folder", Ask::Save("Collected Files".into())),
|
||||
"file.saveCopyAsXml" => ("path", Ask::Save(project_as("ecprojx"))),
|
||||
"keys.rpfCameraImport" => ("path", Ask::Open(&["json", "csv", "txt"])),
|
||||
"essential.exportTemplate" => ("path", Ask::Save("Template.ectemplate")),
|
||||
"essential.exportTemplate" => ("path", Ask::Save("Template.ectemplate".into())),
|
||||
"essential.importTemplate" => ("path", Ask::Open(&["ectemplate"])),
|
||||
"file.setProxy" => ("path", Ask::OpenFootage(&["png", "jpg", "jpeg", "gif", "webp", "tif", "tiff", "bmp", "tga", "exr"])),
|
||||
_ => return None,
|
||||
@@ -609,7 +617,7 @@ pub(super) fn file_dialog(app: &mut EffectcraftApp, id: &str, params: &Value) ->
|
||||
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),
|
||||
Ask::Save(default) => crate::FileDialog::save(&default),
|
||||
};
|
||||
let tx = app.dialog_answers.0.clone();
|
||||
let answer = crate::DialogAnswer { command: id.to_string(), key, params: params.clone(), paths: Vec::new() };
|
||||
@@ -644,7 +652,7 @@ pub(super) fn file_dialog(app: &mut EffectcraftApp, id: &str, params: &Value) ->
|
||||
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())) };
|
||||
let Some(picked) = app.hooks.save_dialog(&default) else { return Some(Err("no file dialog available (pass `path`)".into())) };
|
||||
picked.map(|s| json!(s))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -299,6 +299,43 @@ fn save_dialogs_offer_the_file_extension() {
|
||||
assert_eq!(*asked.borrow(), ["project Frame.png"]);
|
||||
}
|
||||
|
||||
/// #2788: Save As, Save a Copy and Save a Copy As XML start from the open project's name (in its
|
||||
/// folder), not Untitled Project.
|
||||
#[test]
|
||||
fn save_as_dialogs_start_from_the_open_project() {
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
let mut app = EffectcraftApp::new(effectcraft_engine::Session::default());
|
||||
app.session.execute("comp.new", json!({"name": "A", "width": 64, "height": 36, "frameRate": 30, "duration": 1})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
let dir = std::env::temp_dir().join(format!("ec-save-as-name-{}", std::process::id()));
|
||||
let project = dir.join("Intro.ecproj");
|
||||
app.session.path = Some(project.to_string_lossy().to_string());
|
||||
let asked: Rc<RefCell<Vec<String>>> = Rc::default();
|
||||
let (a, b) = (asked.clone(), asked.clone());
|
||||
app.hooks.pick_save = Some(Box::new(move |name: &str| {
|
||||
a.borrow_mut().push(format!("project {name}"));
|
||||
None
|
||||
}));
|
||||
app.hooks.pick_save_file = Some(Box::new(move |name: &str, ext: &str| {
|
||||
b.borrow_mut().push(format!("file {name} [{ext}]"));
|
||||
None
|
||||
}));
|
||||
for id in ["file.saveAs", "file.saveCopy", "file.saveCopyAsXml"] {
|
||||
invoke(&mut app, &ctx, id, json!({})).unwrap();
|
||||
}
|
||||
let xml = dir.join("Intro.ecprojx").to_string_lossy().to_string();
|
||||
assert_eq!(*asked.borrow(), ["project Intro.ecproj".to_string(), "project Intro.ecproj".into(), format!("file {xml} [ecprojx]")]);
|
||||
// The non-blocking dialog gets the whole path, so it opens in the project's folder.
|
||||
let pending: Arc<Mutex<Vec<crate::FileDialog>>> = Arc::default();
|
||||
let queue = pending.clone();
|
||||
app.hooks.file_dialog_async = Some(Box::new(move |dialog, _done| queue.lock().unwrap().push(dialog)));
|
||||
invoke(&mut app, &ctx, "file.saveAs", json!({})).unwrap();
|
||||
let dialog = pending.lock().unwrap().pop().expect("a dialog was shown");
|
||||
assert_eq!(dialog, crate::FileDialog::Save { default: project.to_string_lossy().to_string(), ext: "ecproj".into(), project: true });
|
||||
}
|
||||
|
||||
/// File ▸ Scripts ▸ Recent Script Files and its key run a script again without a file dialog.
|
||||
#[test]
|
||||
fn recent_script_files_run_without_a_dialog() {
|
||||
|
||||
Reference in New Issue
Block a user