use super::*;
use crate::core::usecase::project::Projects;
#[derive(Debug)]
pub struct Navigation {
pub screen: Screen,
pub dest: Nav,
pub team: usize,
pub detail_return: Screen,
pub detail_return_nav: Nav,
pub current_project: Option<Project>,
pub current_cycle: Option<Cycle>,
pub global_search: Option<String>,
}
impl Default for Navigation {
fn default() -> Self {
Self {
screen: Screen::IssueList,
dest: Nav::Team(0, TeamSection::Issues),
team: 0,
detail_return: Screen::IssueList,
detail_return_nav: Nav::Team(0, TeamSection::Issues),
current_project: None,
current_cycle: None,
global_search: None,
}
}
}
impl App {
pub fn open_selected(&mut self) {
match self.nav.screen {
Screen::ProjectList => self.open_project_detail(),
Screen::CycleList => self.open_cycle_detail(),
Screen::ViewList => self.open_selected_view(),
Screen::IssueList | Screen::ProjectDetail | Screen::CycleDetail => {
self.open_issue_detail()
}
Screen::IssueDetail => {}
}
}
pub fn reload_current_tab(&mut self) {
match self.nav.dest {
Nav::MyIssues => {
if let Ok(open) = usecase::issue::open_my_issues(&mut self.store) {
self.open_issues(IssueSource::My, open);
}
}
Nav::Views | Nav::Team(_, TeamSection::Views) => {
let request = usecase::view::ensure_views(&self.store);
self.send(request);
}
Nav::View(index) => {
let Some(view) = self.store.custom_views.get(index) else {
return;
};
let view_id = view.id.clone();
if ViewKind::of(view) == ViewKind::Issues {
let open = usecase::issue::open_view_issues(&mut self.store, view_id);
self.open_issues(IssueSource::View, open);
} else {
let open = usecase::project::open_view_projects(&mut self.store, view_id);
self.open_projects(Projects::View, open);
}
}
Nav::Favorite(_) => {}
Nav::Team(_, section) => {
let Some(team_id) = self.team_id() else {
return;
};
match section {
TeamSection::Issues => {
let preset = self.view.lists[IssueSource::Team].preset;
let open =
usecase::issue::open_team_issues(&mut self.store, team_id, preset);
self.open_issues(IssueSource::Team, open);
}
TeamSection::Projects => {
let open = usecase::project::open_team_projects(&mut self.store, team_id);
self.open_projects(Projects::Team, open);
}
TeamSection::Cycles => {
let open = usecase::cycle::open_team_cycles(&mut self.store, team_id);
if open.replaced() {
self.view.selected_cycle_index = 0;
}
self.send(open.request());
}
TeamSection::Views => {}
}
}
}
}
pub(super) fn open_issues(&mut self, source: IssueSource, open: usecase::Open) {
if open.replaced() {
self.view.lists[source].selected = 0;
}
self.send(open.request());
}
fn open_projects(&mut self, projects: Projects, open: usecase::Open) {
if open.replaced() {
match projects {
Projects::Team => self.view.selected_project_index = 0,
Projects::View => self.view.selected_view_project_index = 0,
}
}
self.send(open.request());
}
pub fn force_reload(&mut self) {
self.nav.global_search = None;
self.store.forget_pages();
let request = match self.nav.dest {
Nav::MyIssues => usecase::issue::reload(&mut self.store, IssueSource::My),
Nav::Views | Nav::Team(_, TeamSection::Views) => {
Some(usecase::view::reload_views(&mut self.store).into())
}
Nav::View(_) if self.in_project_view() => {
usecase::project::reload(&mut self.store, Projects::View)
}
Nav::View(_) => usecase::issue::reload(&mut self.store, IssueSource::View),
Nav::Team(_, TeamSection::Projects) => {
usecase::project::reload(&mut self.store, Projects::Team)
}
Nav::Team(_, TeamSection::Cycles) => usecase::cycle::reload(&mut self.store),
Nav::Team(_, TeamSection::Issues) => {
usecase::issue::reload(&mut self.store, IssueSource::Team)
}
Nav::Favorite(_) => None,
};
match request {
Some(request) => self.request(request),
None => self.reload_current_tab(),
}
if self.nav.screen == Screen::ProjectDetail {
let request = usecase::issue::reload(&mut self.store, IssueSource::Project);
self.send(request);
}
if self.nav.screen == Screen::CycleDetail {
let request = usecase::issue::reload(&mut self.store, IssueSource::Cycle);
self.send(request);
}
self.queue_detail_fetches();
}
pub fn queue_detail_fetches(&mut self) {
match self.nav.screen {
Screen::IssueDetail => {
let request = usecase::issue::ensure_thread(&self.store);
self.send(request);
}
Screen::ProjectDetail => {
if let Some(project) = &self.nav.current_project {
let id = project.id.clone();
let open = usecase::issue::open_project_issues(&mut self.store, id);
self.open_issues(IssueSource::Project, open);
}
}
Screen::CycleDetail => {
if let Some(cycle) = &self.nav.current_cycle {
let id = cycle.id.clone();
let open = usecase::issue::open_cycle_issues(&mut self.store, id);
self.open_issues(IssueSource::Cycle, open);
}
}
_ => {}
}
}
fn screen_for(&self, nav: Nav) -> Screen {
match nav {
Nav::View(i)
if self
.store
.custom_views
.get(i)
.is_some_and(|v| ViewKind::of(v) == ViewKind::Projects) =>
{
Screen::ProjectList
}
_ => nav.screen(),
}
}
pub fn activate(&mut self, nav: Nav) {
if let Nav::Favorite(index) = nav {
return self.open_favorite(index);
}
if let Nav::Team(index, _) = nav
&& index != self.nav.team
&& index < self.store.teams.len()
{
self.select_team_index(index);
}
let screen = self.screen_for(nav);
if self.nav.dest == nav && self.nav.screen == screen {
return;
}
if screen == Screen::ViewList && self.nav.dest != nav {
self.view.selected_view_index = 0;
}
self.nav.dest = nav;
self.nav.screen = screen;
self.view.sidebar.focus = false;
self.reload_current_tab();
}
pub fn go_to_team_issues(&mut self) {
self.go_to_team_section(TeamSection::Issues);
}
pub fn go_to_team_section(&mut self, section: TeamSection) {
self.activate(Nav::Team(self.nav.team, section));
}
pub fn open_selected_view(&mut self) {
if let Some(index) = self
.listed_views()
.get(self.view.selected_view_index)
.copied()
{
self.activate(Nav::View(index));
}
}
fn select_team_index(&mut self, index: usize) {
self.nav.team = index;
self.view.lists[IssueSource::Team].selected = 0;
self.view.selected_project_index = 0;
self.view.selected_cycle_index = 0;
self.view.lists[IssueSource::Team].filters.clear();
if let Some(team_id) = self.team_id() {
let request = usecase::team::switch(&mut self.store, team_id);
self.send(request);
}
}
pub fn open_issue_detail(&mut self) {
if let Some(issue) = self.visible_issues().get(self.selected_index()).copied() {
let issue = issue.clone();
self.open_issue_from_list(&issue);
}
}
pub fn open_issue_by_identifier(&mut self, identifier: &str) {
let stub = Issue {
id: IssueId::new(identifier),
identifier: identifier.to_string(),
..Issue::default()
};
self.view.sidebar.focus = false;
self.open_issue_from_list(&stub);
}
pub fn open_issue_from_list(&mut self, issue: &Issue) {
if self.nav.screen != Screen::IssueDetail {
self.nav.detail_return = self.nav.screen;
self.nav.detail_return_nav = self.nav.dest;
}
self.view.detail_scroll = 0;
self.nav.screen = Screen::IssueDetail;
let request = usecase::issue::open(&mut self.store, issue.clone());
self.send(request);
}
pub fn close_detail(&mut self) {
self.nav.screen = self.nav.detail_return;
self.nav.dest = self.nav.detail_return_nav;
}
pub fn leave_container(&mut self) {
match self.nav.dest {
Nav::Team(_, TeamSection::Projects) => self.nav.screen = Screen::ProjectList,
Nav::Team(_, TeamSection::Cycles) => self.nav.screen = Screen::CycleList,
Nav::View(_) => self.nav.screen = Screen::ProjectList,
_ => self.focus_sidebar(true),
}
}
pub fn step_issue(&mut self, delta: isize) {
if self.nav.screen != Screen::IssueDetail {
return;
}
let Some(current) = &self.store.current_issue else {
return;
};
let issues = self.visible_issues();
let Some(position) = issues.iter().position(|i| i.id == current.id) else {
return;
};
let total = issues.len();
let next = (position as isize + delta).clamp(0, total as isize - 1) as usize;
if next == position {
return;
}
let issue = issues[next].clone();
*self.selected_index_mut() = next;
self.maybe_prefetch_within(total);
let ret = self.nav.detail_return;
self.open_issue_from_list(&issue);
self.nav.detail_return = ret;
}
pub fn detail_position(&self) -> Option<(usize, usize)> {
let current = self.store.current_issue.as_ref()?;
let issues = self.visible_issues();
let index = issues.iter().position(|i| i.id == current.id)?;
Some((index, issues.len()))
}
pub fn refresh_detail(&mut self) {
let request = usecase::issue::refresh(&mut self.store);
self.send(request);
}
pub fn go_to_project(&mut self, project: Project) {
self.activate(Nav::Team(self.nav.team, TeamSection::Projects));
self.open_project(project);
}
pub fn go_to_cycle(&mut self, cycle: Cycle) {
self.activate(Nav::Team(self.nav.team, TeamSection::Cycles));
self.open_cycle(cycle);
}
pub fn open_project_detail(&mut self) {
if let Some(project) = self.project_rows().get(self.project_cursor()).cloned() {
self.open_project(project);
}
}
pub(super) fn open_project(&mut self, project: Project) {
self.nav.current_project = Some(project);
self.reset_list(IssueSource::Project);
self.nav.screen = Screen::ProjectDetail;
self.queue_detail_fetches();
}
pub fn open_cycle_detail(&mut self) {
if let Some(cycle) = self
.store
.cycles
.items
.get(self.view.selected_cycle_index)
.cloned()
{
self.open_cycle(cycle);
}
}
pub(super) fn open_cycle(&mut self, cycle: Cycle) {
self.nav.current_cycle = Some(cycle);
self.reset_list(IssueSource::Cycle);
self.nav.screen = Screen::CycleDetail;
self.queue_detail_fetches();
}
pub(super) fn in_project_view(&self) -> bool {
matches!(self.nav.dest, Nav::View(i) if self.store.custom_views.get(i).is_some_and(|v| ViewKind::of(v) == ViewKind::Projects))
}
pub fn project_rows(&self) -> &[Project] {
if self.in_project_view() {
&self.store.view_projects.items
} else {
&self.store.projects.items
}
}
pub fn project_cursor(&self) -> usize {
if self.in_project_view() {
self.view.selected_view_project_index
} else {
self.view.selected_project_index
}
}
pub(super) fn project_cursor_mut(&mut self) -> &mut usize {
if self.in_project_view() {
&mut self.view.selected_view_project_index
} else {
&mut self.view.selected_project_index
}
}
pub fn project_offset(&mut self) -> &mut usize {
if self.in_project_view() {
&mut self.frame.offsets.view_projects
} else {
&mut self.frame.offsets.projects
}
}
fn views_scope(&self) -> Option<&TeamId> {
match self.nav.dest {
Nav::Team(index, TeamSection::Views) => self.store.teams.get(index).map(|t| &t.id),
_ => None,
}
}
pub fn listed_views(&self) -> Vec<usize> {
usecase::view::listed(
&self.store.custom_views,
self.views_scope(),
self.view.view_kind == ViewKind::Issues,
)
}
pub fn set_view_kind(&mut self, kind: ViewKind) {
if self.view.view_kind != kind {
self.view.view_kind = kind;
self.view.selected_view_index = 0;
}
}
pub fn cycle_view_kind(&mut self) {
self.set_view_kind(match self.view.view_kind {
ViewKind::Issues => ViewKind::Projects,
ViewKind::Projects => ViewKind::Issues,
});
}
}