mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-29 20:47:18 -06:00
Merge pull request #2601 from pravdomil/claude/ecproj-compact-opt-in
Project files: opt-in compact .ecproj JSON (Save Compact Project Files)
This commit is contained in:
@@ -152,7 +152,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_with_view(&s.saved_view())?).map_err(EngineError::Other)?;
|
||||
let xml = crate::xml_project::to_xml(&s.project.to_file_json_with_view(&s.saved_view(), false)?).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()}))
|
||||
|
||||
@@ -87,7 +87,8 @@ pub(crate) fn open(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
/// 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_with_view(&s.saved_view())?;
|
||||
let compact = s.prefs.project.compact_project_files && !crate::xml_project::is_xml_path(path);
|
||||
let json = s.project.to_file_json_with_view(&s.saved_view(), compact)?;
|
||||
if crate::xml_project::is_xml_path(path) { crate::xml_project::to_xml(&json).map_err(EngineError::Other) } else { Ok(json) }
|
||||
}
|
||||
|
||||
|
||||
@@ -122,6 +122,11 @@ pub fn pages() -> Vec<Page> {
|
||||
Section("New Project"),
|
||||
s("project.useTemplate", "New Project Loads Template", B, true),
|
||||
s("project.templatePath", "Template Project", Kind::Path, true),
|
||||
Section("Project Files"),
|
||||
s("project.compactProjectFiles", "Save Compact Project Files", B, true),
|
||||
Note(
|
||||
"Saves .ecproj files as compact JSON on one line: smaller and faster to save and open, but hard to read or diff in version control. Off, they are indented, one value per line.",
|
||||
),
|
||||
Section("Auto-Save"),
|
||||
s("autoSave.enabled", "Automatically Save Projects", B, true),
|
||||
s("autoSave.intervalMinutes", "Save Every", Kind::Int(1, 240, "Minutes"), true),
|
||||
|
||||
@@ -62,6 +62,9 @@ page!(ProjectPrefs {
|
||||
/// Open a template project for File ▸ New ▸ New Project.
|
||||
use_template: bool = false,
|
||||
template_path: String = String::new(),
|
||||
/// Write `.ecproj` files as compact JSON on one line (smaller, faster to save and open). Off:
|
||||
/// indented, one value per line, so they read and diff cleanly in version control.
|
||||
compact_project_files: bool = false,
|
||||
});
|
||||
|
||||
page!(AutoSave {
|
||||
|
||||
@@ -389,6 +389,23 @@ fn new_project_template_and_default_renderer() {
|
||||
let _ = std::fs::remove_dir_all(d);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_files_are_indented_unless_compact_is_on() {
|
||||
let d = tmp("compact-project-files");
|
||||
let path = d.join("p.ecproj");
|
||||
let mut s = session_with_comp();
|
||||
s.execute("file.saveAs", json!({"path": path.to_string_lossy()})).unwrap();
|
||||
let pretty = std::fs::read_to_string(&path).unwrap();
|
||||
assert!(pretty.starts_with("{\n \"savedBy\""), "indented by default");
|
||||
s.execute("prefs.set", json!({"key": "project.compactProjectFiles", "value": true})).unwrap();
|
||||
s.execute("file.saveAs", json!({"path": path.to_string_lossy()})).unwrap();
|
||||
let compact = std::fs::read_to_string(&path).unwrap();
|
||||
assert!(!compact.contains('\n'), "one line when Save Compact Project Files is on");
|
||||
let open = |text: &str| effectcraft_project::Project::from_json(text).unwrap();
|
||||
assert_eq!(open(&compact), open(&pretty), "both read back as the same project");
|
||||
let _ = std::fs::remove_dir_all(d);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- shortcuts
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -267,10 +267,11 @@ impl Project {
|
||||
/// (NaN or infinity, written as `null`, and nesting too deep to parse) are refused on every
|
||||
/// save; debug builds also read the text back.
|
||||
pub fn to_file_json(&self) -> Result<String, ProjectError> {
|
||||
self.to_file_json_with_view(&SavedView::default())
|
||||
self.to_file_json_with_view(&SavedView::default(), false)
|
||||
}
|
||||
/// [`Project::to_file_json`] that also records the open compositions ([`SavedView`]).
|
||||
pub fn to_file_json_with_view(&self, view: &SavedView) -> Result<String, ProjectError> {
|
||||
/// [`Project::to_file_json`] that also records the open compositions ([`SavedView`]), on one
|
||||
/// line when `compact` (Settings ▸ Project ▸ Save Compact Project Files).
|
||||
pub fn to_file_json_with_view(&self, view: &SavedView, compact: bool) -> Result<String, ProjectError> {
|
||||
if self.schema > SCHEMA_VERSION {
|
||||
return Err(ProjectError::Invalid(format!(
|
||||
"the project can't be saved: it holds a value a project file can't store (project schema {} is newer than this version supports ({SCHEMA_VERSION}))",
|
||||
@@ -289,7 +290,8 @@ impl Project {
|
||||
let file = File { saved_by: APP_VERSION, view, project: self };
|
||||
file_check::check(&file)
|
||||
.map_err(|e| ProjectError::Invalid(format!("the project can't be saved: it holds a value a project file can't store ({e})")))?;
|
||||
let text = serde_json::to_string_pretty(&file).map_err(|e| ProjectError::Invalid(format!("the project can't be saved: {e}")))?;
|
||||
let text = if compact { serde_json::to_string(&file) } else { serde_json::to_string_pretty(&file) }
|
||||
.map_err(|e| ProjectError::Invalid(format!("the project can't be saved: {e}")))?;
|
||||
#[cfg(debug_assertions)]
|
||||
Project::from_json(&text)
|
||||
.map_err(|e| ProjectError::Invalid(format!("the project can't be saved: it holds a value a project file can't store ({e})")))?;
|
||||
|
||||
@@ -183,7 +183,7 @@ fn project_files_refuse_newer_schemas() {
|
||||
crate::SCHEMA_VERSION
|
||||
);
|
||||
assert_eq!(p.to_file_json().unwrap_err().to_string(), expected);
|
||||
assert_eq!(p.to_file_json_with_view(&crate::SavedView::default()).unwrap_err().to_string(), expected);
|
||||
assert_eq!(p.to_file_json_with_view(&crate::SavedView::default(), false).unwrap_err().to_string(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -197,7 +197,7 @@ fn saved_files_reopen_in_pretty_and_compact_form() {
|
||||
fn project_files_remember_the_open_comps() {
|
||||
let (p, cid) = project_with_layer();
|
||||
let view = crate::SavedView { active_comp: Some(cid), open_comps: vec![cid], comp_times: [(cid, effectcraft_time::Tick(4_237_833_600))].into() };
|
||||
let text = p.to_file_json_with_view(&view).unwrap();
|
||||
let text = p.to_file_json_with_view(&view, false).unwrap();
|
||||
assert_eq!(crate::saved_view(&text), view);
|
||||
assert_eq!(Project::from_json(&text).unwrap(), p, "the view is not part of the project");
|
||||
// Files from before #748 have no view; a garbled one is ignored, not an error.
|
||||
|
||||
@@ -42,6 +42,7 @@ Commands (CLI, MCP, control channel):
|
||||
- `startup.windowGraphics`: Window Graphics (`auto` / `gl`), what the desktop window draws with from the next launch. `auto` lets the platform pick (DirectX 12, Vulkan or Metal); `gl` uses OpenGL, with CPU compositing, for graphics drivers that crash with the others. A launch leaves a `launch-pending` marker in the settings folder until its window has been drawing for a few seconds (or the app quits normally before that); a launch that stops earlier (a graphics driver crashing as the window opens or as its surface is re-created) leaves the marker behind, and the next launch switches to `gl` and says so (not on macOS, which has no OpenGL backend). `WGPU_BACKEND` overrides both.
|
||||
- `project.useTemplate`: New Project Loads Template
|
||||
- `project.templatePath`: Template Project
|
||||
- `project.compactProjectFiles`: Save Compact Project Files (default off). Off: File ▸ Save writes `.ecproj` files as indented JSON, one value per line, easy to read and diff in version control. On: compact JSON on one line, smaller and faster to save and open. Either opens the same; auto-saves, XML copies and template packages are not affected.
|
||||
- `autoSave.enabled`: Automatically Save Projects
|
||||
- `autoSave.intervalMinutes`: Save Every
|
||||
- `autoSave.maxVersions`: Maximum Project Versions
|
||||
|
||||
Reference in New Issue
Block a user