use std::collections::{HashMap, HashSet};
use crate::api::ids::{CustomViewId, IssueId, TeamId, UserId};
use crate::api::types::{
CustomView, Cycle, Favorite, Issue, PageInfo, Project, Team, User, WorkflowState,
};
use crate::message::Page;
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IssueSource {
Team,
My,
View,
Project,
Cycle,
}
impl IssueSource {
pub const ALL: [IssueSource; 5] =
[Self::Team, Self::My, Self::View, Self::Project, Self::Cycle];
}
#[derive(Debug, Default)]
pub struct PerSource<T>([T; 5]);
impl<T> PerSource<T> {
pub fn from_fn(f: impl FnMut(IssueSource) -> T) -> Self {
Self(IssueSource::ALL.map(f))
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.0.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.0.iter_mut()
}
}
impl<T> std::ops::Index<IssueSource> for PerSource<T> {
type Output = T;
fn index(&self, source: IssueSource) -> &T {
&self.0[source as usize]
}
}
impl<T> std::ops::IndexMut<IssueSource> for PerSource<T> {
fn index_mut(&mut self, source: IssueSource) -> &mut T {
&mut self.0[source as usize]
}
}
#[derive(Debug)]
pub struct Rows<T> {
pub items: Vec<T>,
pub page_info: PageInfo,
pub loaded: bool,
}
impl<T> Default for Rows<T> {
fn default() -> Self {
Self {
items: Vec::new(),
page_info: PageInfo::default(),
loaded: false,
}
}
}
impl<T> Rows<T> {
pub fn accept(&mut self, page: Page<T>) {
let Page {
mut items,
page_info,
append,
} = page;
if append {
self.items.append(&mut items);
} else {
self.items = items;
}
self.page_info = page_info;
self.loaded = true;
}
pub fn reset(&mut self) {
*self = Self::default();
}
}
impl Rows<Issue> {
pub fn accept_issues(&mut self, page: Page<Issue>) {
let append = page.append;
self.accept(page);
if append {
let mut seen = HashSet::new();
self.items.retain(|issue| seen.insert(issue.id.clone()));
}
}
}
#[derive(Debug, Clone, Default)]
pub struct TeamContext {
pub states: Vec<WorkflowState>,
pub members: Vec<User>,
}
#[derive(Debug, Default)]
pub struct Store {
pub teams: Vec<Team>,
pub team_contexts: HashMap<TeamId, TeamContext>,
pub viewer_id: Option<UserId>,
pub custom_views: Vec<CustomView>,
pub views_loaded: bool,
pub favorites: Vec<Favorite>,
pub favorites_loaded: bool,
pub issues: PerSource<Rows<Issue>>,
pub loaded_view_id: Option<CustomViewId>,
pub projects: Rows<Project>,
pub cycles: Rows<Cycle>,
pub view_projects: Rows<Project>,
pub loaded_view_projects_id: Option<CustomViewId>,
pub current_issue: Option<Issue>,
}
impl Store {
pub fn patch_issue(&mut self, issue_id: &IssueId, f: impl Fn(&mut Issue)) {
for rows in self.issues.iter_mut() {
for issue in rows.items.iter_mut().filter(|i| &i.id == issue_id) {
f(issue);
}
}
if let Some(current) = &mut self.current_issue
&& ¤t.id == issue_id
{
f(current);
}
}
pub fn refresh_issue(&mut self, fresh: Issue) {
let id = fresh.id.clone();
self.patch_issue(&id, |issue| {
let comments = issue.comments.take();
*issue = fresh.clone();
issue.comments = comments;
});
if let Some(current) = &mut self.current_issue
&& current.id == id
{
*current = fresh;
}
}
pub fn issue(&self, id: &IssueId) -> Option<&Issue> {
self.current_issue
.iter()
.chain(self.issues.iter().flat_map(|rows| &rows.items))
.find(|issue| &issue.id == id)
}
pub fn set_custom_views(&mut self, mut views: Vec<CustomView>) {
views.sort_by(|a, b| {
a.shared
.cmp(&b.shared)
.then_with(|| a.name.to_lowercase().cmp(&b.name.to_lowercase()))
});
self.custom_views = views;
self.views_loaded = true;
}
pub fn set_favorites(&mut self, mut favorites: Vec<Favorite>) {
favorites.sort_by(|a, b| a.sort_order.total_cmp(&b.sort_order));
self.favorites = favorites;
self.favorites_loaded = true;
}
pub fn issue_team_id<'a>(
&'a self,
issue: &'a Issue,
fallback: Option<&'a TeamId>,
) -> Option<&'a TeamId> {
issue.team.as_ref().map(|team| &team.id).or(fallback)
}
pub fn viewer(&self) -> Option<&User> {
let id = self.viewer_id.as_ref()?;
self.team_contexts
.values()
.flat_map(|c| &c.members)
.find(|u| &u.id == id)
}
}