use std::sync::Arc; use chrono::{DateTime, Utc}; use iocraft::prelude::*; use crate::{ common::{ICONS, one_line, shown_title}, store::{Task, ThingsStore}, ui::components::{deadline_badge::DeadlineBadge, tags_badge::TagsBadge}, }; #[derive(Default, Props)] pub struct TaskLineProps<'a> { pub task: Option<&'a Task>, pub show_today_markers: bool, pub show_staged_today_marker: bool, pub show_tags: bool, pub show_project: bool, pub show_area: bool, } #[component] pub fn TaskLine<'a>(hooks: Hooks, props: &TaskLineProps<'a>) -> impl Into> { let Some(task) = props.task else { return element!(Fragment).into_any(); }; let store = hooks.use_context::>().clone(); let today = *hooks.use_context::>(); let leading = vec![ marker_element( &store, task, &today, props.show_today_markers, props.show_staged_today_marker, ), title_element(task), ]; let tags = if props.show_tags { element! { TagsBadge(tags: task.tags.clone()) }.into_any() } else { element!(Fragment).into_any() }; let context = vec![ reminder_element(task), tags, context_element(task, store.as_ref(), props.show_project, props.show_area), element! { DeadlineBadge(deadline: task.deadline) }.into_any(), ]; element! { View(flex_direction: FlexDirection::Row, gap: 2) { View(flex_direction: FlexDirection::Row, gap: 1) { #(leading) } View(flex_direction: FlexDirection::Row, gap: 1) { #(context) } } } .into_any() } /// the markers of a to-do that Today lists /// /// a to-do in the Trash or in a closed project or heading carries none fn marker_element<'a>( store: &ThingsStore, task: &Task, today: &DateTime, show_today_markers: bool, show_staged_today_marker: bool, ) -> AnyElement<'a> { if !store.in_today(task, today) { return element!(Fragment).into_any(); } if show_today_markers { if task.evening { return element! { Text(content: ICONS.evening, color: Color::Blue) }.into_any(); } return element! { Text(content: ICONS.today, color: Color::Yellow) }.into_any(); } if show_staged_today_marker && task.is_staged_for_today(today) { return element! { Text(content: ICONS.today_staged, color: Color::Yellow) }.into_any(); } element!(Fragment).into_any() } fn reminder_element<'a>(task: &Task) -> AnyElement<'a> { let Some(time) = task.reminder() else { return element!(Fragment).into_any(); }; element!(Text(content: format!("@{time}"), color: Color::DarkGrey)).into_any() } fn title_element<'a>(task: &Task) -> AnyElement<'a> { if task.title.trim().is_empty() { return element!(Text(content: "(untitled)", color: Color::DarkGrey)).into_any(); } let content = one_line(&task.title); element!(Text(content: content)).into_any() } fn context_element<'a>( task: &Task, store: &ThingsStore, show_project: bool, show_area: bool, ) -> AnyElement<'a> { if show_project && let Some(proj) = store.effective_project_uuid(task) { let title = store.resolve_project_title(&proj); return element! { Text(content: format!("[{} {}]", ICONS.project, one_line(&title)), color: Color::DarkGrey) } .into_any(); } if show_area && let Some(area) = store.effective_area_uuid(task) { let title = store.resolve_area_title(&area); return element! { Text(content: format!("[{} {}]", ICONS.area, shown_title(&title)), color: Color::DarkGrey) } .into_any(); } element!(Fragment).into_any() }