G2: Save a Copy starts from "<name> copy" (#2788)

This commit is contained in:
Jonas Neubauer
2026-11-01 20:44:57 +01:00
parent cccd5cba8f
commit 4dac9d04fc
2 changed files with 13 additions and 11 deletions
+9 -8
View File
@@ -553,17 +553,18 @@ pub(super) fn file_dialog(app: &mut EffectcraftApp, id: &str, params: &Value) ->
}
// 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(),
}
// Save a Copy adds ` copy` (`suffix`): it never writes over the open project's file.
let project_as = |suffix: &str, ext: &str| -> std::borrow::Cow<'static, str> {
let Some(path) = app.session.path.as_deref().map(std::path::Path::new) else { return format!("Untitled Project{suffix}.{ext}").into() };
let stem = path.file_stem().map(|s| s.to_string_lossy().into_owned()).unwrap_or_else(|| "Untitled Project".into());
path.with_file_name(format!("{stem}{suffix}.{ext}")).to_string_lossy().into_owned().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(project_as("ecproj"))),
"file.saveAs" | "file.saveCopy" => ("path", Ask::Save(project_as("ecproj"))),
"file.save" if app.session.path.is_none() => ("path", Ask::Save(project_as("", "ecproj"))),
"file.saveAs" => ("path", Ask::Save(project_as("", "ecproj"))),
"file.saveCopy" => ("path", Ask::Save(project_as(" copy", "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())),
@@ -584,7 +585,7 @@ pub(super) fn file_dialog(app: &mut EffectcraftApp, id: &str, params: &Value) ->
"file.replaceFootage" => ("path", Ask::Import),
"file.relinkFootage" => ("folder", Ask::Folder),
"file.collectFiles" => ("folder", Ask::Save("Collected Files".into())),
"file.saveCopyAsXml" => ("path", Ask::Save(project_as("ecprojx"))),
"file.saveCopyAsXml" => ("path", Ask::Save(project_as("", "ecprojx"))),
"keys.rpfCameraImport" => ("path", Ask::Open(&["json", "csv", "txt"])),
"essential.exportTemplate" => ("path", Ask::Save("Template.ectemplate".into())),
"essential.importTemplate" => ("path", Ask::Open(&["ectemplate"])),
+4 -3
View File
@@ -299,8 +299,8 @@ 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.
/// #2788: Save As, Save a Copy (`<name> 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;
@@ -326,7 +326,8 @@ fn save_as_dialogs_start_from_the_open_project() {
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]")]);
// Save a Copy names another file: it would refuse to write over the open project.
assert_eq!(*asked.borrow(), ["project Intro.ecproj".to_string(), "project Intro copy.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();