aven 0.1.6

Local-first task manager CLI and sync server
Documentation
use anyhow::Result;

use crate::operations::TaskDraft;
use crate::tui::app::App;
use crate::tui::authoring::AddTaskStep;
use crate::tui::overlay::{
    AddTaskMode, AddTaskState, ConfirmSubmitRoute, MultilineSubmitRoute, OverlayRoute,
    OverlayState, OverlaySubmit, OverlaySubmitKind, PickerSubmitRoute, TextSubmitRoute,
};

#[cfg(test)]
pub(crate) fn handles_submit_kind(route: OverlayRoute, kind: OverlaySubmitKind) -> bool {
    match kind {
        OverlaySubmitKind::Text => route.text_submit_route().is_some(),
        OverlaySubmitKind::Multiline => route.multiline_submit_route().is_some(),
        OverlaySubmitKind::Picker => route.picker_submit_route().is_some(),
        OverlaySubmitKind::Confirm => route.confirm_submit_route().is_some(),
    }
}

impl App {
    pub(super) async fn handle_overlay_submit(&mut self, submit: OverlaySubmit) -> Result<()> {
        match submit {
            OverlaySubmit::AddTask(state) => {
                self.handle_add_task_submit(*state).await?;
            }
            OverlaySubmit::Picker { route, values } => {
                self.handle_picker_submit(route, values).await?;
            }
            OverlaySubmit::HeaderMenu { action } => {
                self.submit_header_menu(action).await?;
            }
            OverlaySubmit::Order { order } => {
                self.submit_order_menu(order).await?;
            }
            OverlaySubmit::Text { route, value } => {
                self.handle_text_submit(route, value).await?;
            }
            OverlaySubmit::Multiline { route, value } => {
                self.handle_multiline_submit(route, value).await?;
            }
            OverlaySubmit::Confirm { route } => {
                self.handle_confirm_submit(route).await?;
            }
        }
        Ok(())
    }

    async fn handle_add_task_submit(&mut self, mut state: AddTaskState) -> Result<()> {
        let title = state.title.text.trim();
        if title.is_empty() {
            state.focus = AddTaskStep::Title;
            state.mode = AddTaskMode::Compose;
            state.title_error = true;
            self.overlay = Some(OverlayState::AddTask(Box::new(state)));
            self.set_warning("task title is required");
            return Ok(());
        }

        for label in state.labels.clone() {
            let label = crate::labels::normalize_label(&label);
            if !self.store.labels.contains(&label)
                && let Err(error) = self.store.create_label(label).await
            {
                state.mode = AddTaskMode::Compose;
                self.overlay = Some(OverlayState::AddTask(Box::new(state)));
                return Err(error);
            }
        }

        let draft = TaskDraft {
            title: title.to_string(),
            description: state.description.lines.join("\n").trim().to_string(),
            project: state.selected_project.clone(),
            status: state.status.clone(),
            priority: state.priority.clone(),
            labels: state.labels.clone(),
            is_epic: false,
        };
        if let Err(error) = self.submit_created_task(draft).await {
            self.overlay = Some(OverlayState::AddTask(Box::new(state)));
            return Err(error);
        }
        self.authoring.clear();
        Ok(())
    }

    async fn handle_text_submit(&mut self, route: OverlayRoute, value: String) -> Result<()> {
        match route.text_submit_route() {
            Some(TextSubmitRoute::AddTaskTitleToast) => {
                self.set_success(route.fallback_message(OverlaySubmitKind::Text))
            }
            Some(TextSubmitRoute::AddProject) => {
                let message = self.store.create_project(value).await?;
                self.restore_selection_after_mutation();
                self.set_success(message);
            }
            Some(TextSubmitRoute::AddLabel) => {
                let message = self.store.create_label(value).await?;
                self.set_success(message);
            }
            Some(TextSubmitRoute::RenameProjectName) => {
                self.submit_rename_project(value).await?;
            }
            Some(TextSubmitRoute::DeleteProjectNameConfirm) => {
                self.submit_delete_project_name(value).await?;
            }
            Some(TextSubmitRoute::EditTitle) => {
                self.submit_edit_title(value).await?;
            }
            Some(TextSubmitRoute::ConflictManual) => {
                self.submit_manual_conflict_value(value).await?;
            }
            None => self.set_success(route.fallback_message(OverlaySubmitKind::Text)),
        }
        Ok(())
    }

    async fn handle_multiline_submit(&mut self, route: OverlayRoute, value: String) -> Result<()> {
        match route.multiline_submit_route() {
            Some(MultilineSubmitRoute::AddTaskDescription) => {
                if self.authoring.capture_add_task_fields(
                    self.authoring
                        .add_task_context()
                        .map(|context| context.title)
                        .unwrap_or_default(),
                    value,
                    AddTaskStep::Description,
                ) {
                    self.begin_add_task_step();
                }
            }
            Some(MultilineSubmitRoute::AddTaskNatural) => {
                self.submit_add_task_natural(value).await?;
            }
            Some(MultilineSubmitRoute::AddNote) => {
                self.submit_add_note(value).await?;
            }
            Some(MultilineSubmitRoute::EditDescription) => {
                self.submit_edit_description(value).await?;
            }
            Some(MultilineSubmitRoute::ConflictManual) => {
                self.submit_manual_conflict_value(value).await?;
            }
            None => self.set_success(route.fallback_message(OverlaySubmitKind::Multiline)),
        }
        Ok(())
    }

    async fn handle_picker_submit(
        &mut self,
        route: OverlayRoute,
        values: Vec<String>,
    ) -> Result<()> {
        match route.picker_submit_route() {
            Some(PickerSubmitRoute::AddTaskTitleProject) => {
                if self.authoring.apply_add_task_project(values) {
                    self.begin_add_task_step();
                }
            }
            Some(PickerSubmitRoute::AddTaskTitlePriority) => {
                if self.authoring.apply_add_task_priority(values) {
                    self.begin_add_task_step();
                }
            }
            Some(PickerSubmitRoute::AddTaskTitleLabels) => {
                self.submit_add_task_title_labels(values).await?;
            }
            Some(PickerSubmitRoute::EditStatus) => match values.first() {
                Some(status) => self.submit_edit_status(status.clone()).await?,
                None => {
                    self.set_warning("no matching status");
                    self.begin_status_picker();
                }
            },
            Some(PickerSubmitRoute::EditProject) => match values.first() {
                Some(project) => self.submit_edit_project(project.clone()).await?,
                None => {
                    self.set_warning("no matching project");
                    self.begin_edit_project();
                }
            },
            Some(PickerSubmitRoute::EditPriority) => match values.first() {
                Some(priority) => self.submit_edit_priority(priority.clone()).await?,
                None => {
                    self.set_warning("no matching priority");
                    self.begin_edit_priority();
                }
            },
            Some(PickerSubmitRoute::EditLabels) => {
                self.submit_edit_labels(values).await?;
            }
            Some(PickerSubmitRoute::EditLabelsMulti) => {
                self.submit_edit_labels_multi(values).await?;
            }
            Some(PickerSubmitRoute::FilterLabel) => {
                self.submit_filter_label(values).await?;
            }
            Some(PickerSubmitRoute::FilterPriority) => {
                self.submit_filter_priority(values).await?;
            }
            Some(PickerSubmitRoute::ScopeProject) => {
                self.submit_scope_project(values).await?;
            }
            Some(PickerSubmitRoute::RenameProjectPicker) => {
                self.submit_rename_project_picker(values);
            }
            Some(PickerSubmitRoute::DeleteProjectPicker) => {
                self.submit_delete_project_picker(values);
            }
            Some(PickerSubmitRoute::SwitchWorkspace) => {
                self.submit_switch_workspace(values).await?;
            }
            Some(PickerSubmitRoute::ConflictField) => {
                self.submit_conflict_field_picker(values).await?;
            }
            Some(PickerSubmitRoute::ConflictManual) => {
                if let Some(value) = values.first() {
                    self.submit_manual_conflict_value(value.clone()).await?;
                } else {
                    self.set_warning("no value selected");
                }
            }
            Some(PickerSubmitRoute::AddDependency) => match values.first() {
                Some(task_id) => self.submit_add_dependency(task_id.clone()).await?,
                None => {
                    self.set_warning("no matching task");
                    self.begin_add_dependency().await?;
                }
            },
            Some(PickerSubmitRoute::RemoveDependency) => match values.first() {
                Some(task_id) => self.submit_remove_dependency(task_id.clone()).await?,
                None => {
                    self.set_warning("no matching dependency");
                    self.begin_remove_dependency();
                }
            },
            None => self.set_success(route.fallback_message(OverlaySubmitKind::Picker)),
        }
        Ok(())
    }

    async fn handle_confirm_submit(&mut self, route: OverlayRoute) -> Result<()> {
        match route.confirm_submit_route() {
            Some(ConfirmSubmitRoute::ConflictConfirm) => {
                self.submit_confirmed_conflict_resolution().await?;
            }
            Some(ConfirmSubmitRoute::ConfigInit) => {
                self.submit_config_init()?;
            }
            Some(ConfirmSubmitRoute::DeleteProjectConfirm) => {
                self.submit_delete_project().await?;
            }
            Some(ConfirmSubmitRoute::DeleteTaskConfirm) => {
                let return_to_detail = self.detail_context;
                self.update_deleted(true).await?;
                self.detail_context = false;
                self.restore_detail_overlay(return_to_detail);
            }
            None => self.set_success(route.fallback_message(OverlaySubmitKind::Confirm)),
        }
        Ok(())
    }
}