From 9015da48761247370a35f636ca4c798cba5d297e Mon Sep 17 00:00:00 2001 From: vcoppe Date: Sun, 4 Oct 2026 11:07:12 +0200 Subject: [PATCH] fixes --- .../src/engine/command/edit/metadata.rs | 7 ++- .../engine/src/engine/command/edit/style.rs | 7 ++- .../engine/src/engine/command/file/delete.rs | 7 ++- .../src/engine/command/file/duplicate.rs | 7 ++- gpx-rs/engine/src/engine/command/file/load.rs | 44 ++++++++++++++++++- .../command/tools/splice_trackpoints.rs | 14 +++++- .../src/engine/derived/coordinates_cache.rs | 7 ++- .../src/engine/derived/file_structure.rs | 7 ++- .../engine/derived/file_structure_cache.rs | 14 +++++- gpx-rs/engine/src/engine/engine.rs | 11 ++++- gpx-rs/wasm/src/lib.rs | 5 ++- .../file-list/sortable-file-list.ts | 6 ++- website/src/lib/engine.ts | 5 ++- 13 files changed, 122 insertions(+), 19 deletions(-) diff --git a/gpx-rs/engine/src/engine/command/edit/metadata.rs b/gpx-rs/engine/src/engine/command/edit/metadata.rs index 4009d1cc0..72fa76d4d 100644 --- a/gpx-rs/engine/src/engine/command/edit/metadata.rs +++ b/gpx-rs/engine/src/engine/command/edit/metadata.rs @@ -68,7 +68,12 @@ mod tests { fn test_metadata_applies_to_selected_track() { let mut fx = Fixture::default(); let data = std::fs::read("data/with_tracks.gpx").unwrap(); - crate::Load { data: &data }.apply(&mut fx.state()).unwrap(); + crate::Load { + data: &data, + name: "file", + } + .apply(&mut fx.state()) + .unwrap(); let file = fx.files.values().next().unwrap().clone(); fx.selection = Selection::Track { file_id: file.id, diff --git a/gpx-rs/engine/src/engine/command/edit/style.rs b/gpx-rs/engine/src/engine/command/edit/style.rs index 009f98d94..fcef0d2c8 100644 --- a/gpx-rs/engine/src/engine/command/edit/style.rs +++ b/gpx-rs/engine/src/engine/command/edit/style.rs @@ -41,7 +41,12 @@ mod tests { fn test_style_applies_to_all_tracks_of_selected_file_and_keeps_unset_fields() { let mut fx = Fixture::default(); let data = std::fs::read("data/with_style.gpx").unwrap(); - crate::Load { data: &data }.apply(&mut fx.state()).unwrap(); + crate::Load { + data: &data, + name: "file", + } + .apply(&mut fx.state()) + .unwrap(); let id = *fx.files.keys().next().unwrap(); let before = fx.files[&id].clone(); assert!(!before.trk.is_empty()); diff --git a/gpx-rs/engine/src/engine/command/file/delete.rs b/gpx-rs/engine/src/engine/command/file/delete.rs index d420168bc..9099e9bcc 100644 --- a/gpx-rs/engine/src/engine/command/file/delete.rs +++ b/gpx-rs/engine/src/engine/command/file/delete.rs @@ -98,7 +98,12 @@ mod tests { fn loaded() -> (Fixture, FileId) { let mut fx = Fixture::default(); let data = std::fs::read("data/with_tracks_and_segments.gpx").unwrap(); - Load { data: &data }.apply(&mut fx.state()).unwrap(); + Load { + data: &data, + name: "file", + } + .apply(&mut fx.state()) + .unwrap(); let id = fx.order.0[0]; (fx, id) } diff --git a/gpx-rs/engine/src/engine/command/file/duplicate.rs b/gpx-rs/engine/src/engine/command/file/duplicate.rs index 709f93ccf..4be58e267 100644 --- a/gpx-rs/engine/src/engine/command/file/duplicate.rs +++ b/gpx-rs/engine/src/engine/command/file/duplicate.rs @@ -211,7 +211,12 @@ mod tests { fn loaded() -> (Fixture, FileId) { let mut fx = Fixture::default(); let data = std::fs::read("data/with_tracks_and_segments.gpx").unwrap(); - Load { data: &data }.apply(&mut fx.state()).unwrap(); + Load { + data: &data, + name: "file", + } + .apply(&mut fx.state()) + .unwrap(); let id = fx.order.0[0]; (fx, id) } diff --git a/gpx-rs/engine/src/engine/command/file/load.rs b/gpx-rs/engine/src/engine/command/file/load.rs index 141257c63..8522e1371 100644 --- a/gpx-rs/engine/src/engine/command/file/load.rs +++ b/gpx-rs/engine/src/engine/command/file/load.rs @@ -3,11 +3,18 @@ use crate::{Apply, CommandError, State, parse, produce}; #[derive(Debug)] pub struct Load<'a> { pub data: &'a [u8], + /// Name of the file when the data has none (typically the name of the file on disk, without + /// its extension). + pub name: &'a str, } impl Apply for Load<'_> { fn apply(self, state: &mut State) -> Result<(), CommandError> { - let file = parse(self.data).map_err(|err| CommandError::InvalidData(err.to_string()))?; + let mut file = + parse(self.data).map_err(|err| CommandError::InvalidData(err.to_string()))?; + if file.info.name.trim().is_empty() { + file.info.name = self.name.to_owned(); + } produce(state, |_| vec![file]); Ok(()) } @@ -23,7 +30,14 @@ mod tests { fn test_load() { let mut fx = Fixture::default(); let data = std::fs::read("data/with_tracks.gpx").unwrap(); - assert!(Load { data: &data }.apply(&mut fx.state()).is_ok()); + assert!( + Load { + data: &data, + name: "file", + } + .apply(&mut fx.state()) + .is_ok() + ); assert_eq!(fx.files.len(), 1); let file = fx.files.values().next().unwrap(); assert!(!file.trk.is_empty()); @@ -38,6 +52,7 @@ mod tests { let result = Load { data: b"", + name: "file", } .apply(&mut fx.state()); assert!(matches!(result, Err(CommandError::InvalidData(_)))); @@ -45,4 +60,29 @@ mod tests { assert_eq!(fx.order.0.len(), 1); assert_eq!(fx.selected_files(), selected); } + + fn load_with_name(data: &[u8]) -> String { + let mut fx = Fixture::default(); + Load { + data, + name: "from disk", + } + .apply(&mut fx.state()) + .unwrap(); + fx.files.values().next().unwrap().info.name.clone() + } + + #[test] + fn test_load_keeps_the_name_of_the_file() { + let data = br#"in the file"#; + assert_eq!(load_with_name(data), "in the file"); + } + + #[test] + fn test_load_without_name_uses_the_given_name() { + let without = br#""#; + assert_eq!(load_with_name(without), "from disk"); + let empty = br#" "#; + assert_eq!(load_with_name(empty), "from disk"); + } } diff --git a/gpx-rs/engine/src/engine/command/tools/splice_trackpoints.rs b/gpx-rs/engine/src/engine/command/tools/splice_trackpoints.rs index e45cdfc75..d439d1531 100644 --- a/gpx-rs/engine/src/engine/command/tools/splice_trackpoints.rs +++ b/gpx-rs/engine/src/engine/command/tools/splice_trackpoints.rs @@ -106,7 +106,12 @@ mod tests { fn loaded() -> (Fixture, FileId) { let mut fx = Fixture::default(); let data = std::fs::read("data/with_tracks_and_segments.gpx").unwrap(); - Load { data: &data }.apply(&mut fx.state()).unwrap(); + Load { + data: &data, + name: "file", + } + .apply(&mut fx.state()) + .unwrap(); let id = fx.order.0[0]; let trk = &fx.files[&id].trk[0]; fx.selection = Selection::TrackSegment { @@ -282,7 +287,12 @@ mod tests { fn test_splice_on_multiple_files_uses_last_in_file_order() { let (mut fx, first) = loaded(); let data = std::fs::read("data/with_tracks_and_segments.gpx").unwrap(); - Load { data: &data }.apply(&mut fx.state()).unwrap(); + Load { + data: &data, + name: "file", + } + .apply(&mut fx.state()) + .unwrap(); let second = fx.order.0[1]; fx.selection = Selection::File { file_ids: HashSet::from([second, first]), diff --git a/gpx-rs/engine/src/engine/derived/coordinates_cache.rs b/gpx-rs/engine/src/engine/derived/coordinates_cache.rs index 461c6166f..6c5772c75 100644 --- a/gpx-rs/engine/src/engine/derived/coordinates_cache.rs +++ b/gpx-rs/engine/src/engine/derived/coordinates_cache.rs @@ -73,7 +73,12 @@ mod tests { fn loaded() -> (Fixture, FileId) { let mut fx = Fixture::default(); let data = std::fs::read("data/with_tracks_and_segments.gpx").unwrap(); - Load { data: &data }.apply(&mut fx.state()).unwrap(); + Load { + data: &data, + name: "file", + } + .apply(&mut fx.state()) + .unwrap(); let id = fx.order.0[0]; (fx, id) } diff --git a/gpx-rs/engine/src/engine/derived/file_structure.rs b/gpx-rs/engine/src/engine/derived/file_structure.rs index 613ab4214..df68afa5c 100644 --- a/gpx-rs/engine/src/engine/derived/file_structure.rs +++ b/gpx-rs/engine/src/engine/derived/file_structure.rs @@ -100,7 +100,12 @@ mod tests { fn test_structure_of_a_file() { let mut fx = Fixture::default(); let data = std::fs::read("data/with_tracks_and_segments.gpx").unwrap(); - Load { data: &data }.apply(&mut fx.state()).unwrap(); + Load { + data: &data, + name: "file", + } + .apply(&mut fx.state()) + .unwrap(); let file = &fx.files[&fx.order.0[0]]; let node = FileStructure::new(file); assert_eq!( diff --git a/gpx-rs/engine/src/engine/derived/file_structure_cache.rs b/gpx-rs/engine/src/engine/derived/file_structure_cache.rs index 0ab33fbb9..e13668146 100644 --- a/gpx-rs/engine/src/engine/derived/file_structure_cache.rs +++ b/gpx-rs/engine/src/engine/derived/file_structure_cache.rs @@ -41,8 +41,18 @@ mod tests { fn test_only_files_of_the_diff_are_recomputed() { let mut fx = Fixture::default(); let data = std::fs::read("data/with_tracks_and_segments.gpx").unwrap(); - Load { data: &data }.apply(&mut fx.state()).unwrap(); - Load { data: &data }.apply(&mut fx.state()).unwrap(); + Load { + data: &data, + name: "file", + } + .apply(&mut fx.state()) + .unwrap(); + Load { + data: &data, + name: "file", + } + .apply(&mut fx.state()) + .unwrap(); let (a, b) = (fx.order.0[0], fx.order.0[1]); let mut cache = FileStructureCache::default(); diff --git a/gpx-rs/engine/src/engine/engine.rs b/gpx-rs/engine/src/engine/engine.rs index 83301158c..018f955e0 100644 --- a/gpx-rs/engine/src/engine/engine.rs +++ b/gpx-rs/engine/src/engine/engine.rs @@ -164,7 +164,13 @@ mod tests { fn load(engine: &mut Engine, path: &str) -> bool { let data = std::fs::read(path).unwrap(); - edit(engine, Command::Load(Load { data: &data })) + edit( + engine, + Command::Load(Load { + data: &data, + name: "file", + }), + ) } fn selected(engine: &Engine) -> Vec { @@ -213,7 +219,8 @@ mod tests { assert!(!edit( &mut engine, Command::Load(Load { - data: b"" + data: b"", + name: "file", }) )); assert!(engine.stack.current().is_none()); diff --git a/gpx-rs/wasm/src/lib.rs b/gpx-rs/wasm/src/lib.rs index dff664211..7658d26da 100644 --- a/gpx-rs/wasm/src/lib.rs +++ b/gpx-rs/wasm/src/lib.rs @@ -149,9 +149,10 @@ pub fn new_file(name: &str) -> bool { edit(Command::New(engine::New { name })) } +/// `name`: the name of the file when the data has none (the name on disk, without extension). #[wasm_bindgen] -pub fn load_file(data: &[u8]) -> bool { - edit(Command::Load(engine::Load { data })) +pub fn load_file(data: &[u8], name: &str) -> bool { + edit(Command::Load(engine::Load { data, name })) } #[wasm_bindgen] diff --git a/website/src/lib/components/file-list/sortable-file-list.ts b/website/src/lib/components/file-list/sortable-file-list.ts index 221279bc4..91327d4dc 100644 --- a/website/src/lib/components/file-list/sortable-file-list.ts +++ b/website/src/lib/components/file-list/sortable-file-list.ts @@ -85,7 +85,11 @@ export class SortableFileList { this._unsubscribes.push( engine.selection.subscribe(() => tick().then(() => this.updateFromSelection())) ); - this._unsubscribes.push(engine.order.subscribe(() => this.updateFromFileOrder())); + // after the elements are rendered: sorting a list that does not have the new files yet + // would put them at the beginning + this._unsubscribes.push( + engine.order.subscribe(() => tick().then(() => this.updateFromFileOrder())) + ); } /** The node of a child of the list. */ diff --git a/website/src/lib/engine.ts b/website/src/lib/engine.ts index c1dd5b0d5..75beae061 100644 --- a/website/src/lib/engine.ts +++ b/website/src/lib/engine.ts @@ -147,8 +147,9 @@ class Engine { return this.run((w) => w.new_file(name)); } - loadFile(data: Uint8Array) { - return this.run((w) => w.load_file(data)); + /** `name` is the name of the file when the data has none (the name on disk, without extension). */ + loadFile(data: Uint8Array, name: string) { + return this.run((w) => w.load_file(data, name)); } duplicate() {