use super::*;
use crate::snapshot::{self as snap, ViewSnapshot};
impl App {
pub fn snapshot(&self, origin: &snap::Origin) -> Option<ViewSnapshot> {
if self.store.teams.is_empty() || self.restore.as_ref().is_some_and(|r| r.dest.is_some()) {
return None;
}
let screen = self.nav.screen;
let behind = if screen == Screen::IssueDetail {
self.nav.detail_return
} else {
screen
};
let (rows, rows_total, selected_row) = self.snapshot_rows();
Some(ViewSnapshot {
version: snap::VERSION,
workspace: origin.workspace.clone(),
cwd: origin.cwd.clone(),
pid: origin.pid,
updated_at: snap::timestamp_now(),
closed_at: None,
herdr_pane: origin.herdr_pane.clone(),
team: self.current_team().map(team_ref),
destination: self.snapshot_destination(),
screen: screen.into(),
project: (behind == Screen::ProjectDetail)
.then_some(self.nav.current_project.as_ref())
.flatten()
.map(|p| snap::NamedRef {
id: p.id.clone(),
name: p.name.clone(),
}),
cycle: (behind == Screen::CycleDetail)
.then_some(self.nav.current_cycle.as_ref())
.flatten()
.map(|c| snap::NamedRef {
id: c.id.clone(),
name: c.label(),
}),
issue: (screen == Screen::IssueDetail)
.then_some(self.store.current_issue.as_ref())
.flatten()
.map(issue_ref),
search: self.nav.global_search.clone(),
group_by: self.view.group_by.into(),
lists: self.snapshot_lists(),
rows,
rows_total,
selected_row,
})
}
fn snapshot_destination(&self) -> snap::Destination {
let team = |index: usize| self.store.teams.get(index).map(team_ref);
match self.nav.dest {
Nav::MyIssues => snap::Destination::MyIssues,
Nav::Views => snap::Destination::Views,
Nav::View(index) => match self.store.custom_views.get(index) {
Some(view) => snap::Destination::View {
id: view.id.clone(),
name: view.name.clone(),
},
None => snap::Destination::Views,
},
Nav::Team(index, section) => match team(index) {
Some(team) => snap::Destination::Team {
team,
section: section.into(),
},
None => snap::Destination::MyIssues,
},
Nav::Favorite(index) => match self.store.favorites.get(index) {
Some(favorite) => snap::Destination::Favorite {
id: favorite.id.clone(),
title: favorite.label(),
},
None => snap::Destination::MyIssues,
},
}
}
fn snapshot_lists(&self) -> Vec<snap::ListSettings> {
IssueSource::ALL
.into_iter()
.filter_map(|source| {
let list = &self.view.lists[source];
let selected = self.store.issues[source]
.loaded
.then(|| {
visible_of(self.sections_of(source))
.get(list.selected)
.copied()
})
.flatten()
.map(issue_ref);
let default = IssueList::new(source);
if selected.is_none() && list.preset == default.preset && !list.filters.is_active()
{
return None;
}
Some(snap::ListSettings {
source: source.into(),
preset: list.preset.into(),
status: list.filters.status.clone(),
priority: list.filters.priority.map(|p| p.label().to_string()),
selected,
})
})
.collect()
}
fn snapshot_rows(&self) -> (Vec<snap::Row>, usize, Option<usize>) {
let (rows, cursor): (Vec<snap::Row>, usize) = match self.nav.screen {
Screen::ProjectList => (
self.project_rows().iter().map(project_row).collect(),
self.project_cursor(),
),
Screen::CycleList => (
self.store.cycles.items.iter().map(cycle_row).collect(),
self.view.selected_cycle_index,
),
Screen::ViewList => (
self.listed_views()
.into_iter()
.filter_map(|i| self.store.custom_views.get(i))
.map(view_row)
.collect(),
self.view.selected_view_index,
),
Screen::IssueList
| Screen::IssueDetail
| Screen::ProjectDetail
| Screen::CycleDetail => {
let issues = self.visible_issues();
let cursor = match (&self.store.current_issue, self.nav.screen) {
(Some(open), Screen::IssueDetail) => issues
.iter()
.position(|i| i.id == open.id)
.unwrap_or(self.selected_index()),
_ => self.selected_index(),
};
(issues.into_iter().map(issue_row).collect(), cursor)
}
};
let total = rows.len();
let selected = (cursor < total.min(snap::MAX_ROWS)).then_some(cursor);
let mut rows = rows;
rows.truncate(snap::MAX_ROWS);
(rows, total, selected)
}
}
fn team_ref(team: &Team) -> snap::TeamRef {
snap::TeamRef {
id: team.id.clone(),
key: team.key.clone(),
name: team.name.clone(),
}
}
fn issue_ref(issue: &Issue) -> snap::IssueRef {
snap::IssueRef {
id: issue.id.clone(),
identifier: issue.identifier.clone(),
title: issue.title.clone(),
}
}
fn user_name(user: &User) -> String {
user.display_name
.clone()
.unwrap_or_else(|| user.name.clone())
}
fn issue_row(issue: &Issue) -> snap::Row {
snap::Row {
kind: snap::RowKind::Issue,
id: issue.id.to_string(),
identifier: Some(issue.identifier.clone()),
title: issue.title.clone(),
state: issue.state.as_ref().map(|s| s.name.clone()),
assignee: issue.assignee.as_ref().map(user_name),
priority: (issue.priority != Priority::None).then(|| issue.priority.label().to_string()),
url: issue.url.clone(),
}
}
fn project_row(project: &Project) -> snap::Row {
snap::Row {
kind: snap::RowKind::Project,
id: project.id.to_string(),
identifier: None,
title: project.name.clone(),
state: project.state.clone(),
assignee: project.lead.as_ref().map(user_name),
priority: None,
url: project.url.clone(),
}
}
fn cycle_row(cycle: &Cycle) -> snap::Row {
snap::Row {
kind: snap::RowKind::Cycle,
id: cycle.id.to_string(),
identifier: None,
title: cycle.label(),
state: None,
assignee: None,
priority: None,
url: None,
}
}
fn view_row(view: &CustomView) -> snap::Row {
snap::Row {
kind: snap::RowKind::View,
id: view.id.to_string(),
identifier: None,
title: view.name.clone(),
state: None,
assignee: None,
priority: None,
url: None,
}
}
impl From<Screen> for snap::Screen {
fn from(screen: Screen) -> Self {
match screen {
Screen::IssueList => Self::IssueList,
Screen::IssueDetail => Self::IssueDetail,
Screen::ProjectList => Self::ProjectList,
Screen::ProjectDetail => Self::ProjectDetail,
Screen::CycleList => Self::CycleList,
Screen::CycleDetail => Self::CycleDetail,
Screen::ViewList => Self::ViewList,
}
}
}
impl From<TeamSection> for snap::Section {
fn from(section: TeamSection) -> Self {
match section {
TeamSection::Issues => Self::Issues,
TeamSection::Cycles => Self::Cycles,
TeamSection::Projects => Self::Projects,
TeamSection::Views => Self::Views,
}
}
}
impl From<snap::Section> for TeamSection {
fn from(section: snap::Section) -> Self {
match section {
snap::Section::Issues => Self::Issues,
snap::Section::Cycles => Self::Cycles,
snap::Section::Projects => Self::Projects,
snap::Section::Views => Self::Views,
}
}
}
impl From<GroupBy> for snap::GroupBy {
fn from(group_by: GroupBy) -> Self {
match group_by {
GroupBy::Status => Self::Status,
GroupBy::Assignee => Self::Assignee,
GroupBy::Priority => Self::Priority,
GroupBy::Project => Self::Project,
GroupBy::None => Self::None,
}
}
}
impl From<snap::GroupBy> for GroupBy {
fn from(group_by: snap::GroupBy) -> Self {
match group_by {
snap::GroupBy::Status => Self::Status,
snap::GroupBy::Assignee => Self::Assignee,
snap::GroupBy::Priority => Self::Priority,
snap::GroupBy::Project => Self::Project,
snap::GroupBy::None => Self::None,
}
}
}
impl From<IssueSource> for snap::Source {
fn from(source: IssueSource) -> Self {
match source {
IssueSource::Team => Self::Team,
IssueSource::My => Self::My,
IssueSource::View => Self::View,
IssueSource::Project => Self::Project,
IssueSource::Cycle => Self::Cycle,
}
}
}
impl From<snap::Source> for IssueSource {
fn from(source: snap::Source) -> Self {
match source {
snap::Source::Team => Self::Team,
snap::Source::My => Self::My,
snap::Source::View => Self::View,
snap::Source::Project => Self::Project,
snap::Source::Cycle => Self::Cycle,
}
}
}
impl From<Preset> for snap::Preset {
fn from(preset: Preset) -> Self {
match preset {
Preset::Active => Self::Active,
Preset::Backlog => Self::Backlog,
Preset::All => Self::All,
}
}
}
impl From<snap::Preset> for Preset {
fn from(preset: snap::Preset) -> Self {
match preset {
snap::Preset::Active => Self::Active,
snap::Preset::Backlog => Self::Backlog,
snap::Preset::All => Self::All,
}
}
}