use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct NoteId(Uuid);
impl NoteId {
#[must_use]
pub fn new() -> Self {
Self(Uuid::new_v4())
}
#[must_use]
pub const fn from_uuid(uuid: Uuid) -> Self {
Self(uuid)
}
#[must_use]
pub const fn as_uuid(&self) -> &Uuid {
&self.0
}
}
impl Default for NoteId {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for NoteId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Note {
pub id: NoteId,
pub content: String,
pub frame: Option<i64>,
pub created_at: DateTime<Utc>,
pub created_by: Option<String>,
pub modified_at: DateTime<Utc>,
pub reply_to: Option<NoteId>,
}
impl Note {
#[must_use]
pub fn new(content: impl Into<String>) -> Self {
let now = Utc::now();
Self {
id: NoteId::new(),
content: content.into(),
frame: None,
created_at: now,
created_by: None,
modified_at: now,
reply_to: None,
}
}
#[must_use]
pub fn at_frame(content: impl Into<String>, frame: i64) -> Self {
let mut note = Self::new(content);
note.frame = Some(frame);
note
}
#[must_use]
pub fn reply_to(content: impl Into<String>, reply_to: NoteId) -> Self {
let mut note = Self::new(content);
note.reply_to = Some(reply_to);
note
}
pub fn set_frame(&mut self, frame: i64) {
self.frame = Some(frame);
self.modified_at = Utc::now();
}
pub fn set_created_by(&mut self, user: impl Into<String>) {
self.created_by = Some(user.into());
}
pub fn set_content(&mut self, content: impl Into<String>) {
self.content = content.into();
self.modified_at = Utc::now();
}
#[must_use]
pub const fn is_reply(&self) -> bool {
self.reply_to.is_some()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Annotation {
pub id: NoteId,
pub note: Note,
pub frame: i64,
pub annotation_type: AnnotationType,
pub data: AnnotationData,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AnnotationType {
Drawing,
Arrow,
Rectangle,
Circle,
Text,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AnnotationData {
Path(Vec<(f64, f64)>),
Arrow {
start: (f64, f64),
end: (f64, f64),
},
Rectangle {
x: f64,
y: f64,
width: f64,
height: f64,
},
Circle {
cx: f64,
cy: f64,
radius: f64,
},
Text {
x: f64,
y: f64,
text: String,
},
}
impl Annotation {
#[must_use]
pub fn new(
note: Note,
frame: i64,
annotation_type: AnnotationType,
data: AnnotationData,
) -> Self {
Self {
id: NoteId::new(),
note,
frame,
annotation_type,
data,
}
}
#[must_use]
pub fn arrow(note: Note, frame: i64, start: (f64, f64), end: (f64, f64)) -> Self {
Self::new(
note,
frame,
AnnotationType::Arrow,
AnnotationData::Arrow { start, end },
)
}
#[must_use]
pub fn rectangle(note: Note, frame: i64, x: f64, y: f64, width: f64, height: f64) -> Self {
Self::new(
note,
frame,
AnnotationType::Rectangle,
AnnotationData::Rectangle {
x,
y,
width,
height,
},
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_note_creation() {
let note = Note::new("This is a test note");
assert_eq!(note.content, "This is a test note");
assert!(note.frame.is_none());
assert!(!note.is_reply());
}
#[test]
fn test_note_at_frame() {
let note = Note::at_frame("Frame note", 100);
assert_eq!(note.frame, Some(100));
}
#[test]
fn test_note_reply() {
let original = Note::new("Original note");
let reply = Note::reply_to("Reply", original.id);
assert!(reply.is_reply());
assert_eq!(reply.reply_to, Some(original.id));
}
#[test]
fn test_annotation() {
let note = Note::new("Arrow pointing here");
let annotation = Annotation::arrow(note, 100, (10.0, 20.0), (100.0, 200.0));
assert_eq!(annotation.frame, 100);
assert_eq!(annotation.annotation_type, AnnotationType::Arrow);
}
}