mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-25 02:47:02 -06:00
Clicking a version in Version history shows it in place of the document, read-only, with a banner to restore it or go back to the current version (clicking Current version or closing the panel goes back too). Edits, undo and gestures are refused while a version is shown; saving, closing and restoring end the preview first, so the document and its unsaved state are never touched by it. New commands version.preview and version.exitPreview; version.list marks the shown version. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
100 lines
4.3 KiB
Rust
100 lines
4.3 KiB
Rust
//! Viewing a version on the canvas: read-only, restore from it, back to the current version, and
|
|
//! the document (and its unsaved state) untouched by the preview.
|
|
|
|
use serde_json::json;
|
|
|
|
use crate::params::id_of;
|
|
use crate::tools::{PointerEvent, PointerKind};
|
|
use crate::{EngineError, Session};
|
|
|
|
fn rect(s: &mut Session, x: f64) -> figcraft_doc::NodeId {
|
|
id_of(&s.execute("shape.rectangle", &json!({"x": x, "y": 0, "width": 10, "height": 10})).unwrap()["id"]).unwrap()
|
|
}
|
|
|
|
fn count(s: &Session) -> usize {
|
|
s.doc().unwrap().node_count()
|
|
}
|
|
|
|
/// A session with versions "One" (one box) and "Two" (two), then a third box and a selection.
|
|
fn setup() -> (Session, figcraft_doc::NodeId) {
|
|
let mut s = Session::new();
|
|
rect(&mut s, 0.0);
|
|
s.execute("file.saveVersion", &json!({"name": "One"})).unwrap();
|
|
rect(&mut s, 20.0);
|
|
s.execute("file.saveVersion", &json!({"name": "Two"})).unwrap();
|
|
let r = rect(&mut s, 40.0);
|
|
s.execute("select.set", &json!({"ids": [r]})).unwrap();
|
|
(s, r)
|
|
}
|
|
|
|
#[test]
|
|
fn preview_shows_the_version_read_only_and_goes_back() {
|
|
let (mut s, r) = setup();
|
|
let three = count(&s);
|
|
let undo_depth = s.ds().unwrap().history.undo.len();
|
|
let v = s.execute("version.preview", &json!({"index": 0})).unwrap();
|
|
assert_eq!(v["name"], "One");
|
|
assert_eq!(count(&s), three - 2);
|
|
assert!(s.selection().is_empty());
|
|
let l = s.execute("version.list", &json!({})).unwrap();
|
|
assert_eq!(l[0]["previewing"], true);
|
|
assert!(l[1].get("previewing").is_none());
|
|
// Edits are refused and leave it as it was; so do undo and gestures.
|
|
let err = s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 5, "height": 5})).unwrap_err();
|
|
assert!(matches!(err, EngineError::Disabled(..)), "{err:?}");
|
|
assert_eq!(count(&s), three - 2);
|
|
assert!(s.execute("edit.undo", &json!({})).is_err());
|
|
assert_eq!(s.ds().unwrap().history.undo.len(), undo_depth, "history untouched");
|
|
assert!(s.begin_interaction("Move").is_err());
|
|
let _ = s.pointer(&PointerEvent::at(PointerKind::Down, 5.0, 5.0, 1.0));
|
|
let _ = s.pointer(&PointerEvent::at(PointerKind::Move, 50.0, 50.0, 1.0));
|
|
let _ = s.pointer(&PointerEvent::at(PointerKind::Up, 50.0, 50.0, 1.0));
|
|
assert_eq!(count(&s), three - 2);
|
|
// Switching to another version, then back to the current one.
|
|
s.execute("version.preview", &json!({"index": 1})).unwrap();
|
|
assert_eq!(count(&s), three - 1);
|
|
assert_eq!(s.execute("version.exitPreview", &json!({})).unwrap()["ended"], true);
|
|
assert_eq!(count(&s), three);
|
|
assert_eq!(s.selection(), vec![r], "selection comes back");
|
|
assert_eq!(s.execute("version.exitPreview", &json!({})).unwrap()["ended"], false);
|
|
// Editing works again.
|
|
rect(&mut s, 60.0);
|
|
assert_eq!(count(&s), three + 1);
|
|
}
|
|
|
|
#[test]
|
|
fn restore_from_a_preview_is_one_undo_step() {
|
|
let (mut s, _) = setup();
|
|
let three = count(&s);
|
|
s.execute("version.preview", &json!({"index": 0})).unwrap();
|
|
s.execute("version.restore", &json!({"index": 0})).unwrap();
|
|
assert!(s.ds().unwrap().preview.is_none());
|
|
assert_eq!(count(&s), three - 2);
|
|
s.execute("edit.undo", &json!({})).unwrap();
|
|
assert_eq!(count(&s), three, "undo goes back to the document, not the preview");
|
|
}
|
|
|
|
#[test]
|
|
fn preview_keeps_the_unsaved_state_and_saving_saves_the_document() {
|
|
let dir = std::env::temp_dir().join(format!("figcraft-vpreview-{}", std::process::id()));
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
let path = dir.join("preview.figcraft").to_string_lossy().to_string();
|
|
let (mut s, _) = setup();
|
|
let three = count(&s);
|
|
s.execute("file.save", &json!({"path": path})).unwrap();
|
|
s.execute("version.preview", &json!({"index": 0})).unwrap();
|
|
assert!(!s.ds().unwrap().is_dirty(), "looking at a version isn't a change");
|
|
// Saving ends the preview and saves the document itself.
|
|
s.execute("file.save", &json!({})).unwrap();
|
|
assert!(s.ds().unwrap().preview.is_none());
|
|
let mut t = Session::empty();
|
|
t.execute("file.open", &json!({"path": path})).unwrap();
|
|
assert_eq!(count(&t), three);
|
|
// Deleting the version being viewed ends the preview.
|
|
s.execute("version.preview", &json!({"index": 1})).unwrap();
|
|
s.execute("version.delete", &json!({"index": 1})).unwrap();
|
|
assert!(s.ds().unwrap().preview.is_none());
|
|
assert_eq!(count(&s), three);
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
}
|