Skip to main content

things3_cloud/ui/components/
task_line.rs

1use crate::common::ICONS;
2use crate::store::{Task, ThingsStore};
3use crate::ui::components::deadline_badge::DeadlineBadge;
4use crate::ui::components::tags_badge::TagsBadge;
5use chrono::{DateTime, Utc};
6use iocraft::prelude::*;
7use std::sync::Arc;
8
9#[derive(Default, Props)]
10pub struct TaskLineProps<'a> {
11    pub task: Option<&'a Task>,
12    pub show_today_markers: bool,
13    pub show_staged_today_marker: bool,
14    pub show_tags: bool,
15    pub show_project: bool,
16    pub show_area: bool,
17}
18
19#[component]
20pub fn TaskLine<'a>(hooks: Hooks, props: &TaskLineProps<'a>) -> impl Into<AnyElement<'a>> {
21    let Some(task) = props.task else {
22        return element!(Fragment).into_any();
23    };
24
25    let store = hooks.use_context::<Arc<ThingsStore>>().clone();
26    let today = *hooks.use_context::<DateTime<Utc>>();
27
28    let leading = vec![
29        marker_element(
30            task,
31            &today,
32            props.show_today_markers,
33            props.show_staged_today_marker,
34        ),
35        title_element(task),
36    ];
37
38    let tags = if props.show_tags {
39        element! { TagsBadge(tags: task.tags.clone()) }.into_any()
40    } else {
41        element!(Fragment).into_any()
42    };
43
44    let context = vec![
45        tags,
46        context_element(task, store.as_ref(), props.show_project, props.show_area),
47        element! { DeadlineBadge(deadline: task.deadline) }.into_any(),
48    ];
49
50    element! {
51        View(flex_direction: FlexDirection::Row, gap: 2) {
52            View(flex_direction: FlexDirection::Row, gap: 1) { #(leading) }
53            View(flex_direction: FlexDirection::Row, gap: 1) { #(context) }
54        }
55    }
56    .into_any()
57}
58
59fn marker_element<'a>(
60    task: &Task,
61    today: &DateTime<Utc>,
62    show_today_markers: bool,
63    show_staged_today_marker: bool,
64) -> AnyElement<'a> {
65    if show_today_markers {
66        if task.evening {
67            return element! { Text(content: ICONS.evening, color: Color::Blue) }.into_any();
68        }
69        if task.is_today(today) {
70            return element! { Text(content: ICONS.today, color: Color::Yellow) }.into_any();
71        }
72    }
73
74    if show_staged_today_marker && task.is_staged_for_today(today) {
75        return element! { Text(content: ICONS.today_staged, color: Color::Yellow) }.into_any();
76    }
77
78    element!(Fragment).into_any()
79}
80
81fn title_element<'a>(task: &Task) -> AnyElement<'a> {
82    if task.title.is_empty() {
83        return element!(Text(content: "(untitled)", color: Color::DarkGrey)).into_any();
84    }
85
86    let content = task.title.clone();
87    element!(Text(content: content)).into_any()
88}
89
90fn context_element<'a>(
91    task: &Task,
92    store: &ThingsStore,
93    show_project: bool,
94    show_area: bool,
95) -> AnyElement<'a> {
96    if show_project && let Some(proj) = store.effective_project_uuid(task) {
97        let title = store.resolve_project_title(&proj);
98        return element! {
99            Text(content: format!("[{} {}]", ICONS.project, title), color: Color::DarkGrey)
100        }
101        .into_any();
102    }
103
104    if show_area && let Some(area) = store.effective_area_uuid(task) {
105        let title = store.resolve_area_title(&area);
106        return element! {
107            Text(content: format!("[{} {}]", ICONS.area, title), color: Color::DarkGrey)
108        }
109        .into_any();
110    }
111
112    element!(Fragment).into_any()
113}