Skip to main content

things3_cloud/ui/components/
task_item.rs

1use iocraft::prelude::*;
2
3use crate::{
4    common::ICONS,
5    store::Task,
6    ui::components::{
7        checklist::CheckList,
8        details_container::DetailsContainer,
9        id::Id,
10        task_line::TaskLine,
11        tasks::TaskOptions,
12    },
13};
14
15#[derive(Default, Props)]
16pub struct TaskItemProps<'a> {
17    pub task: Option<&'a Task>,
18    pub options: TaskOptions,
19    pub id_prefix_len: usize,
20}
21
22#[component]
23pub fn TaskItem<'a>(props: &TaskItemProps<'a>) -> impl Into<AnyElement<'a>> {
24    let Some(task) = props.task else {
25        return element!(Fragment).into_any();
26    };
27
28    let details = if props.options.detailed {
29        element! {
30            TaskDetails(task: task, id_prefix_len: props.id_prefix_len)
31        }
32        .into_any()
33    } else {
34        element!(Fragment).into_any()
35    };
36
37    element! {
38        View(flex_direction: FlexDirection::Row, gap: 1) {
39            Id(id: &task.uuid, length: props.id_prefix_len)
40            View(flex_direction: FlexDirection::Column) {
41                TaskText(task: task, options: props.options)
42                #(details)
43            }
44        }
45    }
46    .into_any()
47}
48
49#[derive(Default, Props)]
50struct TaskTextProps<'a> {
51    pub task: Option<&'a Task>,
52    pub options: TaskOptions,
53}
54
55#[component]
56fn TaskText<'a>(props: &TaskTextProps<'a>) -> impl Into<AnyElement<'a>> {
57    let Some(task) = props.task else {
58        return element!(Fragment).into_any();
59    };
60
61    element! {
62        View(flex_direction: FlexDirection::Row, gap: 1) {
63            Text(content: checkbox_str(task), color: Color::DarkGrey)
64            TaskLine(
65                task: task,
66                show_today_markers: props.options.show_today_markers,
67                show_staged_today_marker: props.options.show_staged_today_marker,
68                show_tags: true,
69                show_project: props.options.show_project,
70                show_area: props.options.show_area,
71            )
72        }
73    }
74    .into_any()
75}
76
77#[derive(Default, Props)]
78struct TaskDetailProps<'a> {
79    pub task: Option<&'a Task>,
80    pub id_prefix_len: usize,
81}
82
83#[component]
84fn TaskDetails<'a>(props: &TaskDetailProps<'a>) -> impl Into<AnyElement<'a>> {
85    let Some(task) = props.task else {
86        return element!(Fragment).into_any();
87    };
88
89    let show_ids = props.id_prefix_len > 0;
90    let note_text = task.notes.as_deref().unwrap_or("").trim();
91
92    let checklist_items = if task.checklist_items.is_empty() {
93        None
94    } else {
95        Some(task.checklist_items.as_slice())
96    };
97
98    if note_text.is_empty() && checklist_items.is_none() {
99        return element!(Fragment).into_any();
100    }
101
102    let note_text = if note_text.is_empty() {
103        element!(Fragment).into_any()
104    } else {
105        element!(Text(content: note_text, color: Color::DarkGrey)).into_any()
106    };
107
108    element! {
109        DetailsContainer {
110            View(flex_direction: FlexDirection::Column, gap: 1) {
111                #(note_text)
112                CheckList(items: checklist_items, show_ids, shift_left: true)
113            }
114        }
115
116    }
117    .into_any()
118}
119
120fn checkbox_str(task: &Task) -> &'static str {
121    if task.is_completed() {
122        ICONS.task_done
123    } else if task.is_canceled() {
124        ICONS.task_canceled
125    } else if task.in_someday() {
126        ICONS.task_someday
127    } else {
128        ICONS.task_open
129    }
130}