things3_cloud/ui/components/
task_line.rs1use 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}