Skip to main content

things3_cloud/ui/components/
task_line.rs

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