use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::DocumentId;
use super::Collaborator;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CollaborationSession {
pub id: String,
pub document_id: DocumentId,
pub participants: Vec<Participant>,
pub started: DateTime<Utc>,
pub status: SessionStatus,
}
impl CollaborationSession {
#[must_use]
pub fn new(id: impl Into<String>, document_id: DocumentId) -> Self {
Self {
id: id.into(),
document_id,
participants: Vec::new(),
started: Utc::now(),
status: SessionStatus::Active,
}
}
pub fn add_participant(&mut self, participant: Participant) {
self.participants.push(participant);
}
pub fn remove_participant(&mut self, user_id: &str) {
self.participants
.retain(|p| p.author.user_id.as_deref() != Some(user_id));
}
#[must_use]
pub fn participant_count(&self) -> usize {
self.participants.len()
}
pub fn end(&mut self) {
self.status = SessionStatus::Ended;
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Participant {
pub author: Collaborator,
pub joined: DateTime<Utc>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<CursorPosition>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub selection: Option<Selection>,
}
impl Participant {
#[must_use]
pub fn new(author: Collaborator) -> Self {
Self {
author,
joined: Utc::now(),
cursor: None,
color: None,
selection: None,
}
}
#[must_use]
pub fn with_cursor(mut self, cursor: CursorPosition) -> Self {
self.cursor = Some(cursor);
self
}
#[must_use]
pub fn with_color(mut self, color: impl Into<String>) -> Self {
self.color = Some(color.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CursorPosition {
pub block_ref: String,
pub offset: usize,
}
impl CursorPosition {
#[must_use]
pub fn new(block_ref: impl Into<String>, offset: usize) -> Self {
Self {
block_ref: block_ref.into(),
offset,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Selection {
pub start: CursorPosition,
pub end: CursorPosition,
}
impl Selection {
#[must_use]
pub fn new(start: CursorPosition, end: CursorPosition) -> Self {
Self { start, end }
}
#[must_use]
pub fn within_block(
block_ref: impl Into<String>,
start_offset: usize,
end_offset: usize,
) -> Self {
let block_ref = block_ref.into();
Self {
start: CursorPosition::new(block_ref.clone(), start_offset),
end: CursorPosition::new(block_ref, end_offset),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SessionStatus {
#[default]
Active,
Paused,
Ended,
}