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