Files
notedeck/crates/notedeck_columns/src/timeline/unit.rs
T
kernelkind 1a93663b1a replace HybridSet with NoteUnits
This will unify the collections that hold the notes to timelines
and threads and allow the notifications timeline to have grouped
notifications, among other things

Signed-off-by: kernelkind <kernelkind@gmail.com>
2025-08-25 10:31:50 -04:00

206 lines
5.5 KiB
Rust

use std::collections::{BTreeMap, HashSet};
use enostr::Pubkey;
use nostrdb::NoteKey;
use notedeck::NoteRef;
/// A `NoteUnit` represents a cohesive piece of data derived from notes
#[derive(Debug, Clone)]
pub enum NoteUnit {
Single(NoteRef), // A single note
Composite(CompositeUnit),
}
impl NoteUnit {
pub fn key(&self) -> NoteKey {
match self {
NoteUnit::Single(note_ref) => note_ref.key,
NoteUnit::Composite(clustered_entry) => clustered_entry.key(),
}
}
pub fn get_underlying_noteref(&self) -> &NoteRef {
match self {
NoteUnit::Single(note_ref) => note_ref,
NoteUnit::Composite(clustered) => match clustered {
CompositeUnit::Reaction(reaction_entry) => &reaction_entry.note_reacted_to,
},
}
}
pub fn get_latest_ref(&self) -> &NoteRef {
match self {
NoteUnit::Single(note_ref) => note_ref,
NoteUnit::Composite(composite_unit) => composite_unit.get_latest_ref(),
}
}
}
impl Ord for NoteUnit {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.get_latest_ref().cmp(other.get_latest_ref())
}
}
impl PartialEq for NoteUnit {
fn eq(&self, other: &Self) -> bool {
self.get_latest_ref() == other.get_latest_ref()
}
}
impl Eq for NoteUnit {}
impl PartialOrd for NoteUnit {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
/// Combines potentially many notes into one cohesive piece of data
#[derive(Debug, Clone)]
pub enum CompositeUnit {
Reaction(ReactionUnit),
}
impl CompositeUnit {
pub fn get_latest_ref(&self) -> &NoteRef {
match self {
CompositeUnit::Reaction(reaction_unit) => reaction_unit.get_latest_ref(),
}
}
}
impl PartialEq for CompositeUnit {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Reaction(l0), Self::Reaction(r0)) => l0 == r0,
}
}
}
impl CompositeUnit {
pub fn key(&self) -> NoteKey {
match self {
CompositeUnit::Reaction(reaction_entry) => reaction_entry.note_reacted_to.key,
}
}
}
impl From<CompositeFragment> for CompositeUnit {
fn from(value: CompositeFragment) -> Self {
match value {
CompositeFragment::Reaction(reaction_fragment) => {
CompositeUnit::Reaction(reaction_fragment.into())
}
}
}
}
/// Represents all the reactions to a specific note `ReactionUnit::note_reacted_to`
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ReactionUnit {
pub note_reacted_to: NoteRef, // NOTE: this should not be modified after it's created
pub reactions: BTreeMap<NoteRef, Reaction>,
pub senders: HashSet<Pubkey>, // useful for making sure the same user can't add more than one reaction to a note
}
impl ReactionUnit {
pub fn get_latest_ref(&self) -> &NoteRef {
self.reactions
.first_key_value()
.map(|(r, _)| r)
.unwrap_or(&self.note_reacted_to)
}
}
impl From<ReactionFragment> for ReactionUnit {
fn from(frag: ReactionFragment) -> Self {
let mut senders = HashSet::new();
senders.insert(frag.reaction.sender);
let mut reactions = BTreeMap::new();
reactions.insert(frag.reaction_note_ref, frag.reaction);
Self {
note_reacted_to: frag.noteref_reacted_to,
reactions,
senders,
}
}
}
#[derive(Clone)]
pub enum NoteUnitFragment {
Single(NoteRef),
Composite(CompositeFragment),
}
#[derive(Debug, Clone)]
pub enum CompositeFragment {
Reaction(ReactionFragment),
}
impl CompositeFragment {
pub fn fold_into(self, unit: &mut CompositeUnit) {
match self {
CompositeFragment::Reaction(reaction_fragment) => reaction_fragment.fold_into(unit),
}
}
pub fn key(&self) -> NoteKey {
match self {
CompositeFragment::Reaction(reaction_fragment) => {
reaction_fragment.reaction_note_ref.key
}
}
}
pub fn get_underlying_noteref(&self) -> &NoteRef {
match self {
CompositeFragment::Reaction(reaction_fragment) => &reaction_fragment.noteref_reacted_to,
}
}
pub fn get_latest_ref(&self) -> &NoteRef {
match self {
CompositeFragment::Reaction(reaction_fragment) => &reaction_fragment.reaction_note_ref,
}
}
}
/// A singluar reaction to a note
#[derive(Debug, Clone)]
pub struct ReactionFragment {
pub noteref_reacted_to: NoteRef,
pub reaction_note_ref: NoteRef,
pub reaction: Reaction,
}
impl ReactionFragment {
/// Add all the contents of Self into `CompositeUnit`
pub fn fold_into(self, unit: &mut CompositeUnit) {
match unit {
CompositeUnit::Reaction(reaction_unit) => {
if self.noteref_reacted_to != reaction_unit.note_reacted_to {
return;
}
if reaction_unit.senders.contains(&self.reaction.sender) {
return;
}
reaction_unit.senders.insert(self.reaction.sender);
reaction_unit
.reactions
.insert(self.reaction_note_ref, self.reaction);
}
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Reaction {
pub reaction: String, // can't use char because some emojis are 'grapheme clusters'
pub sender: Pubkey,
}