Skip to main content

things3_cloud/ui/components/
task_item.rs

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