mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-28 03:57:44 -06:00
Merge pull request #863 from itrycodinganditsbad/fix-748
Save the active comp and open comp tabs in project files (#748)
This commit is contained in:
@@ -128,7 +128,7 @@ fn clear_recent_presets(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
|
||||
fn save_copy_xml(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let path = str_p(p, "path").ok_or_else(|| bad("file.saveCopyAsXml", "missing `path` (.ecprojx)"))?;
|
||||
let xml = crate::xml_project::to_xml(&s.project.to_file_json()?).map_err(EngineError::Other)?;
|
||||
let xml = crate::xml_project::to_xml(&s.project.to_file_json_with_view(&s.saved_view())?).map_err(EngineError::Other)?;
|
||||
s.services.write_file(path, xml.as_bytes()).map_err(|e| EngineError::Other(format!("cannot write {path}: {e}")))?;
|
||||
s.toast(format!("Saved an XML copy to {path}"));
|
||||
Ok(json!({"path": path, "bytes": xml.len()}))
|
||||
|
||||
@@ -47,6 +47,7 @@ pub(crate) fn open(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Project::from_json_repaired(&text).map_err(|e| EngineError::Other(format!("{path} is not a project EffectCraft can open: {e}")))?;
|
||||
resolve_relative_footage(&mut proj, path, s.services.as_ref());
|
||||
s.replace_project(proj, Some(path.to_string()));
|
||||
s.restore_view(&effectcraft_project::saved_view(&text));
|
||||
s.note_project_path(path);
|
||||
// Values past the limits (a corrupt or hostile file) were brought within them (#788).
|
||||
if !repaired.is_empty() {
|
||||
@@ -97,7 +98,7 @@ fn resolve_relative_footage(p: &mut Project, project_path: &str, services: &dyn
|
||||
/// project opened from an XML copy saves as XML again). Fails, writing nothing, when the project
|
||||
/// wouldn't read back.
|
||||
pub(crate) fn file_text(s: &Session, path: &str) -> Result<String> {
|
||||
let json = s.project.to_file_json()?;
|
||||
let json = s.project.to_file_json_with_view(&s.saved_view())?;
|
||||
if crate::xml_project::is_xml_path(path) { crate::xml_project::to_xml(&json).map_err(EngineError::Other) } else { Ok(json) }
|
||||
}
|
||||
|
||||
|
||||
@@ -859,6 +859,26 @@ impl Session {
|
||||
self.events.push(Event::OpenComp(id));
|
||||
}
|
||||
|
||||
/// The open compositions, as a project file records them ([`effectcraft_project::SavedView`]).
|
||||
pub fn saved_view(&self) -> effectcraft_project::SavedView {
|
||||
effectcraft_project::SavedView { active_comp: self.state.active_comp, open_comps: self.state.open_comps.clone() }
|
||||
}
|
||||
|
||||
/// Reopen the compositions a project file was saved with (#748). Ids that no longer name a
|
||||
/// comp are skipped; with none left the project stays on the comp [`Session::replace_project`]
|
||||
/// opened (its first), as files from before the view was saved do.
|
||||
pub fn restore_view(&mut self, view: &effectcraft_project::SavedView) {
|
||||
let mut open: Vec<ItemId> = Vec::new();
|
||||
for id in view.open_comps.iter().copied().filter(|id| self.project.comp(*id).is_some()) {
|
||||
if !open.contains(&id) {
|
||||
open.push(id);
|
||||
}
|
||||
}
|
||||
let Some(active) = view.active_comp.filter(|id| self.project.comp(*id).is_some()).or_else(|| open.first().copied()) else { return };
|
||||
self.state.open_comps = open;
|
||||
self.open_comp(active);
|
||||
}
|
||||
|
||||
pub fn toast(&mut self, msg: impl Into<String>) {
|
||||
self.events.push(Event::Toast { message: msg.into(), error: false });
|
||||
}
|
||||
|
||||
@@ -628,3 +628,61 @@ fn new_text_rejects_malformed_positions_without_creating_a_layer() {
|
||||
s.execute("layer.newText", json!({"text": "No explicit position"})).unwrap();
|
||||
assert_eq!(s.active_comp().unwrap().layers.len(), 2);
|
||||
}
|
||||
|
||||
/// The active comp and the open comp tabs are saved with the project and restored on open (#748).
|
||||
#[test]
|
||||
fn reopening_a_project_restores_its_active_comp() {
|
||||
use effectcraft_project::ItemId;
|
||||
let dir = std::env::temp_dir().join(format!("ec-test-748-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join("view.ecproj").to_string_lossy().to_string();
|
||||
let mut s = Session::default();
|
||||
let a = s.execute("comp.new", json!({"name": "CompA"})).unwrap()["comp"].as_u64().unwrap();
|
||||
let b = s.execute("comp.new", json!({"name": "CompB"})).unwrap()["comp"].as_u64().unwrap();
|
||||
let c = s.execute("comp.new", json!({"name": "CompC"})).unwrap()["comp"].as_u64().unwrap();
|
||||
s.execute("comp.open", json!({"comp": "CompB"})).unwrap();
|
||||
s.state.open_comps.retain(|id| id.0 != c);
|
||||
s.execute("file.saveAs", json!({"path": path})).unwrap();
|
||||
let mut s2 = Session::default();
|
||||
s2.execute("file.open", json!({"path": path})).unwrap();
|
||||
assert_eq!(s2.state.active_comp, Some(ItemId(b)));
|
||||
assert_eq!(s2.state.open_comps, vec![ItemId(a), ItemId(b)]);
|
||||
assert!(!s2.is_dirty());
|
||||
|
||||
// A file from before the view was saved opens on its first comp, as it always did.
|
||||
let text = std::fs::read_to_string(&path).unwrap();
|
||||
let mut old: serde_json::Value = serde_json::from_str(&text).unwrap();
|
||||
let obj = old.as_object_mut().unwrap();
|
||||
obj.remove("activeComp");
|
||||
obj.remove("openComps");
|
||||
std::fs::write(&path, serde_json::to_string_pretty(&old).unwrap()).unwrap();
|
||||
let mut s3 = Session::default();
|
||||
s3.execute("file.open", json!({"path": path})).unwrap();
|
||||
assert_eq!((s3.state.active_comp, s3.state.open_comps.clone()), (Some(ItemId(a)), vec![ItemId(a)]));
|
||||
|
||||
// Ids that no longer name a comp (edited by hand, or written by another tool) fall back.
|
||||
obj_set(&mut old, json!(999), json!([999, b, "x"]));
|
||||
std::fs::write(&path, serde_json::to_string_pretty(&old).unwrap()).unwrap();
|
||||
let mut s4 = Session::default();
|
||||
s4.execute("file.open", json!({"path": path})).unwrap();
|
||||
assert_eq!((s4.state.active_comp, s4.state.open_comps.clone()), (Some(ItemId(b)), vec![ItemId(b)]));
|
||||
obj_set(&mut old, json!(999), json!([]));
|
||||
std::fs::write(&path, serde_json::to_string_pretty(&old).unwrap()).unwrap();
|
||||
let mut s5 = Session::default();
|
||||
s5.execute("file.open", json!({"path": path})).unwrap();
|
||||
assert_eq!((s5.state.active_comp, s5.state.open_comps.clone()), (Some(ItemId(a)), vec![ItemId(a)]));
|
||||
|
||||
// XML copies carry it too.
|
||||
let xml = dir.join("view.ecprojx").to_string_lossy().to_string();
|
||||
s.execute("file.saveAs", json!({"path": xml})).unwrap();
|
||||
let mut s6 = Session::default();
|
||||
s6.execute("file.open", json!({"path": xml})).unwrap();
|
||||
assert_eq!(s6.state.active_comp, Some(ItemId(b)));
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
|
||||
fn obj_set(v: &mut serde_json::Value, active: serde_json::Value, open: serde_json::Value) {
|
||||
let o = v.as_object_mut().unwrap();
|
||||
o.insert("activeComp".into(), active);
|
||||
o.insert("openComps".into(), open);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user