Skip to main content

draw_core/
document.rs

1use serde::{Deserialize, Serialize};
2
3use crate::element::Element;
4use crate::point::ViewState;
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7pub struct Document {
8    pub id: String,
9    pub version: u32,
10    pub name: String,
11    pub elements: Vec<Element>,
12    #[serde(default)]
13    pub view: ViewState,
14    pub created_at: String,
15    pub modified_at: String,
16}
17
18impl Document {
19    pub fn new(name: String) -> Self {
20        let now = chrono::Utc::now().to_rfc3339();
21        Self {
22            id: uuid::Uuid::new_v4().to_string(),
23            version: 1,
24            name,
25            elements: vec![],
26            view: ViewState::default(),
27            created_at: now.clone(),
28            modified_at: now,
29        }
30    }
31
32    pub fn add_element(&mut self, element: Element) {
33        self.touch();
34        self.elements.push(element);
35    }
36
37    pub fn remove_element(&mut self, id: &str) -> Option<Element> {
38        if let Some(idx) = self.elements.iter().position(|e| e.id() == id) {
39            self.touch();
40            Some(self.elements.remove(idx))
41        } else {
42            None
43        }
44    }
45
46    pub fn get_element(&self, id: &str) -> Option<&Element> {
47        self.elements.iter().find(|e| e.id() == id)
48    }
49
50    pub fn get_element_mut(&mut self, id: &str) -> Option<&mut Element> {
51        self.elements.iter_mut().find(|e| e.id() == id)
52    }
53
54    fn touch(&mut self) {
55        self.modified_at = chrono::Utc::now().to_rfc3339();
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use crate::element::{Element, ShapeElement};
63
64    #[test]
65    fn test_new_document() {
66        let doc = Document::new("my drawing".to_string());
67        assert_eq!(doc.name, "my drawing");
68        assert_eq!(doc.version, 1);
69        assert!(doc.elements.is_empty());
70        assert!(!doc.id.is_empty());
71    }
72
73    #[test]
74    fn test_add_element() {
75        let mut doc = Document::new("test".to_string());
76        let elem = Element::Rectangle(ShapeElement::new("r1".to_string(), 0.0, 0.0, 10.0, 10.0));
77        doc.add_element(elem);
78        assert_eq!(doc.elements.len(), 1);
79        assert_eq!(doc.elements[0].id(), "r1");
80    }
81
82    #[test]
83    fn test_remove_element() {
84        let mut doc = Document::new("test".to_string());
85        let elem = Element::Rectangle(ShapeElement::new("r1".to_string(), 0.0, 0.0, 10.0, 10.0));
86        let modified_before = doc.modified_at.clone();
87        doc.add_element(elem);
88
89        // Remove existing
90        let removed = doc.remove_element("r1");
91        assert!(removed.is_some());
92        assert_eq!(removed.unwrap().id(), "r1");
93        assert!(doc.elements.is_empty());
94
95        // Remove non-existing should not touch
96        let modified_after_remove = doc.modified_at.clone();
97        let removed = doc.remove_element("nonexistent");
98        assert!(removed.is_none());
99        // modified_at should not change when element not found
100        assert_eq!(doc.modified_at, modified_after_remove);
101        let _ = modified_before;
102    }
103
104    #[test]
105    fn test_json_roundtrip() {
106        use crate::element::{FreeDrawElement, LineElement, TextElement};
107        use crate::point::Point;
108
109        let mut doc = Document::new("roundtrip test".to_string());
110        doc.add_element(Element::Rectangle(ShapeElement::new(
111            "r1".to_string(),
112            10.0,
113            20.0,
114            100.0,
115            50.0,
116        )));
117        doc.add_element(Element::Ellipse(ShapeElement::new(
118            "e1".to_string(),
119            0.0,
120            0.0,
121            80.0,
122            60.0,
123        )));
124        doc.add_element(Element::Line(LineElement::new(
125            "l1".to_string(),
126            5.0,
127            5.0,
128            vec![Point::new(0.0, 0.0), Point::new(50.0, 50.0)],
129        )));
130        doc.add_element(Element::FreeDraw(FreeDrawElement::new(
131            "fd1".to_string(),
132            1.0,
133            2.0,
134            vec![
135                Point::new(0.0, 0.0),
136                Point::new(3.0, 4.0),
137                Point::new(6.0, 1.0),
138            ],
139        )));
140        doc.add_element(Element::Text(TextElement::new(
141            "t1".to_string(),
142            0.0,
143            0.0,
144            "hello\nworld".to_string(),
145        )));
146
147        let json = serde_json::to_string_pretty(&doc).unwrap();
148        let deserialized: Document = serde_json::from_str(&json).unwrap();
149        assert_eq!(doc, deserialized);
150    }
151
152    #[test]
153    fn test_get_element() {
154        let mut doc = Document::new("test".to_string());
155        doc.add_element(Element::Rectangle(ShapeElement::new(
156            "r1".to_string(),
157            5.0,
158            10.0,
159            20.0,
160            30.0,
161        )));
162        assert!(doc.get_element("r1").is_some());
163        assert!(doc.get_element("nonexistent").is_none());
164    }
165}