use imbl::{HashMap, Vector};
use logisheets_base::{CellId, PersonId, SheetId};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Person {
pub display_name: String,
pub user_id: Option<String>,
pub provider_id: Option<String>,
pub guid: String,
}
#[derive(Debug, Clone)]
pub struct PersonInput {
pub display_name: String,
pub user_id: Option<String>,
pub provider_id: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Mention {
pub person: PersonId,
pub start: usize,
pub len: usize,
pub mention_id: String,
}
#[derive(Debug, Clone)]
pub struct CommentNote {
pub id: String,
pub person: PersonId,
pub dt: String,
pub text: String,
pub parent: Option<String>,
pub mentions: Vector<Mention>,
pub resolved: bool,
}
#[derive(Debug, Clone)]
pub struct PersonManager {
next_id: PersonId,
persons: HashMap<PersonId, Person>,
by_guid: HashMap<String, PersonId>,
}
impl Default for PersonManager {
fn default() -> Self {
PersonManager {
next_id: 0,
persons: HashMap::new(),
by_guid: HashMap::new(),
}
}
}
impl PersonManager {
fn identity_key(user_id: &Option<String>, provider_id: &Option<String>) -> Option<String> {
user_id
.as_ref()
.map(|u| format!("{}\u{0}{}", provider_id.clone().unwrap_or_default(), u))
}
fn find_existing(&self, input: &PersonInput) -> Option<PersonId> {
match Self::identity_key(&input.user_id, &input.provider_id) {
Some(key) => self.persons.iter().find_map(|(id, p)| {
if Self::identity_key(&p.user_id, &p.provider_id) == Some(key.clone()) {
Some(*id)
} else {
None
}
}),
None => self.persons.iter().find_map(|(id, p)| {
if p.user_id.is_none() && p.display_name == input.display_name {
Some(*id)
} else {
None
}
}),
}
}
pub fn get_or_register(&mut self, input: PersonInput) -> PersonId {
if let Some(id) = self.find_existing(&input) {
return id;
}
let guid = format!("{{{}}}", uuid::Uuid::new_v4());
self.insert_with_guid(guid, input)
}
pub fn register_with_guid(&mut self, guid: String, input: PersonInput) -> PersonId {
if let Some(id) = self.by_guid.get(&guid) {
return *id;
}
self.insert_with_guid(guid, input)
}
fn insert_with_guid(&mut self, guid: String, input: PersonInput) -> PersonId {
let id = self.next_id;
self.next_id += 1;
let person = Person {
display_name: input.display_name,
user_id: input.user_id,
provider_id: input.provider_id,
guid: guid.clone(),
};
self.persons.insert(id, person);
self.by_guid.insert(guid, id);
id
}
pub fn get(&self, id: &PersonId) -> Option<&Person> {
self.persons.get(id)
}
pub fn get_by_guid(&self, guid: &str) -> Option<PersonId> {
self.by_guid.get(guid).copied()
}
pub fn iter(&self) -> impl Iterator<Item = (&PersonId, &Person)> {
self.persons.iter()
}
}
#[derive(Debug, Clone, Default)]
pub struct SheetComments {
pub threads: HashMap<CellId, Vector<CommentNote>>,
}
impl SheetComments {
pub fn new() -> Self {
SheetComments {
threads: HashMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct Comments {
pub persons: PersonManager,
pub data: HashMap<SheetId, SheetComments>,
}
impl Default for Comments {
fn default() -> Self {
Comments {
persons: PersonManager::default(),
data: HashMap::new(),
}
}
}
impl Comments {
pub fn get_thread(&self, sheet_id: &SheetId, cell_id: &CellId) -> Option<&Vector<CommentNote>> {
self.data.get(sheet_id)?.threads.get(cell_id)
}
pub fn get_person(&self, person_id: &PersonId) -> Option<&Person> {
self.persons.get(person_id)
}
pub fn add_note(&mut self, sheet_id: SheetId, cell_id: CellId, note: CommentNote) {
let sheet = self.data.entry(sheet_id).or_insert_with(SheetComments::new);
sheet
.threads
.entry(cell_id)
.or_insert_with(Vector::new)
.push_back(note);
}
fn locate(&self, sheet_id: &SheetId, comment_id: &str) -> Option<(CellId, usize)> {
let sheet = self.data.get(sheet_id)?;
for (cell_id, thread) in sheet.threads.iter() {
if let Some(idx) = thread.iter().position(|n| n.id == comment_id) {
return Some((*cell_id, idx));
}
}
None
}
pub fn edit_note(
&mut self,
sheet_id: SheetId,
comment_id: &str,
text: String,
mentions: Vector<Mention>,
) -> bool {
let Some((cell_id, idx)) = self.locate(&sheet_id, comment_id) else {
return false;
};
let Some(sheet) = self.data.get_mut(&sheet_id) else {
return false;
};
let Some(thread) = sheet.threads.get_mut(&cell_id) else {
return false;
};
if let Some(note) = thread.get_mut(idx) {
note.text = text;
note.mentions = mentions;
return true;
}
false
}
pub fn set_resolved(&mut self, sheet_id: SheetId, comment_id: &str, resolved: bool) -> bool {
let Some((cell_id, idx)) = self.locate(&sheet_id, comment_id) else {
return false;
};
let Some(sheet) = self.data.get_mut(&sheet_id) else {
return false;
};
let Some(thread) = sheet.threads.get_mut(&cell_id) else {
return false;
};
if let Some(note) = thread.get_mut(idx) {
note.resolved = resolved;
return true;
}
false
}
pub fn delete_note(&mut self, sheet_id: SheetId, comment_id: &str) -> bool {
let Some((cell_id, idx)) = self.locate(&sheet_id, comment_id) else {
return false;
};
let Some(sheet) = self.data.get_mut(&sheet_id) else {
return false;
};
let Some(thread) = sheet.threads.get_mut(&cell_id) else {
return false;
};
let is_root = thread.get(idx).map(|n| n.parent.is_none()).unwrap_or(false);
if is_root {
sheet.threads.remove(&cell_id);
} else {
thread.remove(idx);
if thread.is_empty() {
sheet.threads.remove(&cell_id);
}
}
true
}
}