mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-02 22:42:24 -06:00
Merge PR #2792
This commit is contained in:
Generated
+11
@@ -1094,6 +1094,16 @@ name = "craft-docpath"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0"
|
||||
|
||||
[[package]]
|
||||
name = "craft-ecproj-format"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=d74959fb9caca1c5fd393b4351026885b5ba0cc8#d74959fb9caca1c5fd393b4351026885b5ba0cc8"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "craft-fonts-build"
|
||||
version = "0.1.0"
|
||||
@@ -1783,6 +1793,7 @@ dependencies = [
|
||||
name = "effectcraft-project"
|
||||
version = "0.20.0"
|
||||
dependencies = [
|
||||
"craft-ecproj-format",
|
||||
"effectcraft-color",
|
||||
"effectcraft-geom",
|
||||
"effectcraft-keyframe",
|
||||
|
||||
@@ -14,6 +14,7 @@ repository = "https://github.com/storytold/effectcraft"
|
||||
craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", version = "=0.1.0", default-features = false, features = ["read"] }
|
||||
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" }
|
||||
craft-quit = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0" }
|
||||
craft-ecproj-format = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "d74959fb9caca1c5fd393b4351026885b5ba0cc8" }
|
||||
# craft-update: app-specific pretend versions apply to startup and manual checks.
|
||||
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", features = ["egui"] }
|
||||
# internal
|
||||
|
||||
@@ -14,6 +14,7 @@ effectcraft-time = { workspace = true }
|
||||
effectcraft-geom = { workspace = true }
|
||||
effectcraft-color = { workspace = true }
|
||||
effectcraft-keyframe = { workspace = true }
|
||||
craft-ecproj-format = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
@@ -272,26 +272,13 @@ impl Project {
|
||||
/// [`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}))",
|
||||
self.schema
|
||||
)));
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
struct File<'a> {
|
||||
#[serde(rename = "savedBy")]
|
||||
saved_by: &'a str,
|
||||
#[serde(flatten)]
|
||||
view: &'a SavedView,
|
||||
#[serde(flatten)]
|
||||
project: &'a Project,
|
||||
}
|
||||
let file = File { saved_by: APP_VERSION, view, project: self };
|
||||
craft_ecproj_format::check_schema(self.schema)
|
||||
.map_err(|e| ProjectError::Invalid(format!("the project can't be saved: it holds a value a project file can't store ({e})")))?;
|
||||
let file = craft_ecproj_format::File::new(APP_VERSION, view, 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 = 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}")))?;
|
||||
let text =
|
||||
if compact { file.to_json_compact() } else { file.to_json() }.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})")))?;
|
||||
@@ -312,9 +299,7 @@ impl Project {
|
||||
/// hostile file still opens, within bounds, and the user can be told what changed.
|
||||
pub fn from_json_repaired(s: &str) -> Result<(Project, Vec<String>), ProjectError> {
|
||||
let mut p: Project = serde_json::from_str(s).map_err(|e| ProjectError::Invalid(e.to_string()))?;
|
||||
if p.schema > SCHEMA_VERSION {
|
||||
return Err(ProjectError::Invalid(format!("project schema {} is newer than this version supports ({SCHEMA_VERSION})", p.schema)));
|
||||
}
|
||||
craft_ecproj_format::check_schema(p.schema).map_err(|e| ProjectError::Invalid(e.to_string()))?;
|
||||
p.fix_next_id();
|
||||
let mut notes = vec![];
|
||||
for item in p.items.values_mut() {
|
||||
|
||||
@@ -4,7 +4,7 @@ use super::{BTreeMap, ColorSpace, Deserialize, Label, Serialize, Tick};
|
||||
/// The project file schema. 2: effects on layers without a source rectangle (shape, text)
|
||||
/// measure positions from the top-left of their comp-sized bounds (effect space), not from
|
||||
/// the layer's origin; projects from schema 1 are converted when opened (#227).
|
||||
pub const SCHEMA_VERSION: u32 = 2;
|
||||
pub use craft_ecproj_format::SCHEMA_VERSION;
|
||||
|
||||
/// The first schema in which effect positions are in effect space (see [`SCHEMA_VERSION`]).
|
||||
pub const SCHEMA_EFFECT_SPACE: u32 = 2;
|
||||
@@ -12,14 +12,14 @@ pub const SCHEMA_EFFECT_SPACE: u32 = 2;
|
||||
/// The EffectCraft version, written into project files as `savedBy`.
|
||||
pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
/// The EffectCraft version that wrote a project file (its `savedBy`), when it says.
|
||||
/// The EffectCraft version that wrote a project file (its `savedBy`), when it says, also for a
|
||||
/// project whose schema is newer than [`SCHEMA_VERSION`].
|
||||
pub fn saved_by(text: &str) -> Option<String> {
|
||||
#[derive(Deserialize)]
|
||||
struct Meta {
|
||||
#[serde(rename = "savedBy", default)]
|
||||
saved_by: Option<String>,
|
||||
match craft_ecproj_format::Head::read(text) {
|
||||
Ok(head) => head.saved_by,
|
||||
Err(craft_ecproj_format::ReadError::TooNew { saved_by, .. }) => saved_by,
|
||||
Err(_) => None,
|
||||
}
|
||||
serde_json::from_str::<Meta>(text).ok()?.saved_by
|
||||
}
|
||||
|
||||
/// The compositions that were open when a project file was saved and their current times
|
||||
|
||||
@@ -162,6 +162,8 @@ fn project_files_name_their_version_and_always_read_back() {
|
||||
assert!(text.trim_start().starts_with("{\n \"savedBy\""), "the version comes first");
|
||||
assert_eq!(Project::from_json(&text).unwrap(), p);
|
||||
assert_eq!(crate::saved_by(&p.to_json()), None, "plain JSON (worker hand-offs) has none");
|
||||
assert_eq!(crate::saved_by(r#"{"savedBy": "999.0.0", "schema": 3}"#).as_deref(), Some("999.0.0"), "a newer schema still names its version");
|
||||
assert_eq!(crate::saved_by(r#"[2, {}, {}, 1]"#), None, "the array form has none");
|
||||
// JSON can't hold NaN: saving refuses instead of writing a file that can't be opened.
|
||||
p.comp_mut(cid).unwrap().layers[0].props.prop_mut("transform/opacity").unwrap().value = Value::Scalar(f64::NAN);
|
||||
let e = p.to_file_json().unwrap_err().to_string();
|
||||
@@ -193,6 +195,22 @@ fn saved_files_reopen_in_pretty_and_compact_form() {
|
||||
assert_eq!(Project::from_json(&p.to_json_compact()).unwrap(), p, "compact JSON");
|
||||
}
|
||||
|
||||
/// A project file read back and saved again is the same text, indented and compact (Save Compact
|
||||
/// Project Files), with its saved view.
|
||||
#[test]
|
||||
fn project_files_save_again_byte_for_byte() {
|
||||
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() };
|
||||
for compact in [false, true] {
|
||||
let text = p.to_file_json_with_view(&view, compact).unwrap();
|
||||
let prefix = if compact { "{\"savedBy\":\"" } else { "{\n \"savedBy\": \"" };
|
||||
assert!(text.starts_with(&format!("{prefix}{}\"", crate::APP_VERSION)), "compact {compact}: {text}");
|
||||
assert_eq!(text.contains('\n'), !compact);
|
||||
let again = Project::from_json(&text).unwrap().to_file_json_with_view(&crate::saved_view(&text), compact).unwrap();
|
||||
assert_eq!(again, text, "compact {compact}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_files_remember_the_open_comps() {
|
||||
let (p, cid) = project_with_layer();
|
||||
|
||||
Reference in New Issue
Block a user