Repository navigation
feat(ui): autosave unsaved work and offer it back after a crash #896
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1891578
13131a5
d9c96f4
93b8a8a
f76627b
895321b
c1ba9d0
a5c08b2
401bdce
8597b85
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| //! Recovery copies (#873) and portable projects (#886): a project saved with paths relative to its | ||
| //! folder, then reopened, gives a recovery copy whose linked files still resolve when the copy is | ||
| //! opened from the app's recovery folder. | ||
| #[path = "portable_projects/common.rs"] | ||
| #[allow(dead_code)] | ||
| mod common; | ||
|
|
||
| use common::{Fixture, add_stream, sample_file, session}; | ||
| use serde_json::json; | ||
|
|
||
| /// `path` as it is spelled inside project JSON. | ||
| fn in_json(path: &std::path::Path) -> String { | ||
| let quoted = serde_json::to_string(&path.display().to_string()).unwrap(); | ||
| quoted.trim_matches('"').to_string() | ||
| } | ||
|
|
||
| #[test] | ||
| fn a_recovery_copy_keeps_linked_files_absolute() { | ||
| let f = Fixture::new("recovery"); | ||
| let data = f.file("data/places.geojson"); | ||
| sample_file(&data, "Cities"); | ||
| let project = f.file("maps/atlas.rgis"); | ||
| let mut s = session(); | ||
| add_stream(&mut s, &data, None); | ||
| s.run("project.saveAs", &json!({"path": project})).unwrap(); | ||
| // The saved file is portable: the data's path is relative to the project's folder. | ||
| let saved = std::fs::read_to_string(&project).unwrap(); | ||
| assert!(!saved.contains(&in_json(&data)), "saved relative"); | ||
|
|
||
| // Reopened, then copied for recovery: the copy names the data by its full path... | ||
| let mut opened = session(); | ||
| opened.run("project.open", &json!({"path": project})).unwrap(); | ||
| let copy = opened.project.clone().recovery_text().unwrap(); | ||
| assert!(copy.contains(&in_json(&data)), "the copy keeps the data's full path"); | ||
| // ...so opened from elsewhere, as recovery does, it reads the same file. | ||
| let mut recovered = session(); | ||
| recovered.run("project.openText", &json!({"text": copy})).unwrap(); | ||
| let live = serde_json::to_string(&recovered.project).unwrap(); | ||
| assert!(live.contains(&in_json(&data))); | ||
| assert_eq!(recovered.map().layers.len(), opened.map().layers.len()); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,8 @@ | |
| //! `{"width": w, "height": h}` on the first line sets the size (default | ||
| //! 1480×900 at 2×; `"dt": seconds` there sets each frame's length, default 0.25, and a | ||
| //! short one such as 0.016 catches the map mid-motion). The window opens the World Atlas sample. | ||
| //! `"recovery": "/abs/dir"` on the first line gives the run that recovery folder, as the desktop | ||
| //! app has one (Settings › Autosave; a copy left there is offered by the recovery dialog). | ||
| //! | ||
| //! Add `"ribbonSplit": true` to the first line for a compact reusable split-button showcase | ||
| //! instead of the app. Its "Disable arrows", "Disable enclosing UI", and "Long two-line labels" | ||
|
|
@@ -130,6 +132,10 @@ fn main() { | |
| let ribbon_split = lines.first().and_then(|l| l.get("ribbonSplit")).and_then(Value::as_bool).unwrap_or(false); | ||
| let mut split_showcase = RibbonSplitShowcase::default(); | ||
| let mut app = RustGisApp::with_sample(); | ||
| if let Some(dir) = lines.first().and_then(|l| l.get("recovery")).and_then(Value::as_str) { | ||
| let store = rustgis_ui_egui::FolderStore::open(std::path::Path::new(dir)).expect("recovery folder"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Left as is on purpose.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Answered on #896 (comment): |
||
| app = app.with_services(rustgis_ui_egui::Services { recovery: Some(Box::new(store)), ..Default::default() }); | ||
| } | ||
| // One shared wgpu render state: the app's GPU map backend and the harness's renderer must | ||
| // live on the same device for the map's paint callback to be valid. | ||
| let rs = { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.