1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct NoteId(Uuid);
10
11impl NoteId {
12 #[must_use]
14 pub fn new() -> Self {
15 Self(Uuid::new_v4())
16 }
17
18 #[must_use]
20 pub const fn from_uuid(uuid: Uuid) -> Self {
21 Self(uuid)
22 }
23
24 #[must_use]
26 pub const fn as_uuid(&self) -> &Uuid {
27 &self.0
28 }
29}
30
31impl Default for NoteId {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl std::fmt::Display for NoteId {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 write!(f, "{}", self.0)
40 }
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct Note {
46 pub id: NoteId,
48
49 pub content: String,
51
52 pub frame: Option<i64>,
54
55 pub created_at: DateTime<Utc>,
57
58 pub created_by: Option<String>,
60
61 pub modified_at: DateTime<Utc>,
63
64 pub reply_to: Option<NoteId>,
66}
67
68impl Note {
69 #[must_use]
71 pub fn new(content: impl Into<String>) -> Self {
72 let now = Utc::now();
73 Self {
74 id: NoteId::new(),
75 content: content.into(),
76 frame: None,
77 created_at: now,
78 created_by: None,
79 modified_at: now,
80 reply_to: None,
81 }
82 }
83
84 #[must_use]
86 pub fn at_frame(content: impl Into<String>, frame: i64) -> Self {
87 let mut note = Self::new(content);
88 note.frame = Some(frame);
89 note
90 }
91
92 #[must_use]
94 pub fn reply_to(content: impl Into<String>, reply_to: NoteId) -> Self {
95 let mut note = Self::new(content);
96 note.reply_to = Some(reply_to);
97 note
98 }
99
100 pub fn set_frame(&mut self, frame: i64) {
102 self.frame = Some(frame);
103 self.modified_at = Utc::now();
104 }
105
106 pub fn set_created_by(&mut self, user: impl Into<String>) {
108 self.created_by = Some(user.into());
109 }
110
111 pub fn set_content(&mut self, content: impl Into<String>) {
113 self.content = content.into();
114 self.modified_at = Utc::now();
115 }
116
117 #[must_use]
119 pub const fn is_reply(&self) -> bool {
120 self.reply_to.is_some()
121 }
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct Annotation {
127 pub id: NoteId,
129
130 pub note: Note,
132
133 pub frame: i64,
135
136 pub annotation_type: AnnotationType,
138
139 pub data: AnnotationData,
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
145pub enum AnnotationType {
146 Drawing,
148 Arrow,
150 Rectangle,
152 Circle,
154 Text,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub enum AnnotationData {
161 Path(Vec<(f64, f64)>),
163 Arrow {
165 start: (f64, f64),
167 end: (f64, f64),
169 },
170 Rectangle {
172 x: f64,
174 y: f64,
176 width: f64,
178 height: f64,
180 },
181 Circle {
183 cx: f64,
185 cy: f64,
187 radius: f64,
189 },
190 Text {
192 x: f64,
194 y: f64,
196 text: String,
198 },
199}
200
201impl Annotation {
202 #[must_use]
204 pub fn new(
205 note: Note,
206 frame: i64,
207 annotation_type: AnnotationType,
208 data: AnnotationData,
209 ) -> Self {
210 Self {
211 id: NoteId::new(),
212 note,
213 frame,
214 annotation_type,
215 data,
216 }
217 }
218
219 #[must_use]
221 pub fn arrow(note: Note, frame: i64, start: (f64, f64), end: (f64, f64)) -> Self {
222 Self::new(
223 note,
224 frame,
225 AnnotationType::Arrow,
226 AnnotationData::Arrow { start, end },
227 )
228 }
229
230 #[must_use]
232 pub fn rectangle(note: Note, frame: i64, x: f64, y: f64, width: f64, height: f64) -> Self {
233 Self::new(
234 note,
235 frame,
236 AnnotationType::Rectangle,
237 AnnotationData::Rectangle {
238 x,
239 y,
240 width,
241 height,
242 },
243 )
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_note_creation() {
253 let note = Note::new("This is a test note");
254 assert_eq!(note.content, "This is a test note");
255 assert!(note.frame.is_none());
256 assert!(!note.is_reply());
257 }
258
259 #[test]
260 fn test_note_at_frame() {
261 let note = Note::at_frame("Frame note", 100);
262 assert_eq!(note.frame, Some(100));
263 }
264
265 #[test]
266 fn test_note_reply() {
267 let original = Note::new("Original note");
268 let reply = Note::reply_to("Reply", original.id);
269 assert!(reply.is_reply());
270 assert_eq!(reply.reply_to, Some(original.id));
271 }
272
273 #[test]
274 fn test_annotation() {
275 let note = Note::new("Arrow pointing here");
276 let annotation = Annotation::arrow(note, 100, (10.0, 20.0), (100.0, 200.0));
277 assert_eq!(annotation.frame, 100);
278 assert_eq!(annotation.annotation_type, AnnotationType::Arrow);
279 }
280}