| 304 |
304 |
|
} = load(db, self.ready_only)?;
|
| 305 |
305 |
|
self.projects = build_projects(&projects, &tasks);
|
| 306 |
306 |
|
self.events = build_events(events);
|
| 307 |
|
- |
self.timer = build_timer(active_timer, time_summary, sessions, &tasks);
|
|
307 |
+ |
self.timer = build_timer(active_timer, &time_summary, sessions, &tasks);
|
| 308 |
308 |
|
self.contacts = build_contacts(contacts);
|
| 309 |
309 |
|
self.rows = build_board(projects, ready.as_deref().unwrap_or(&tasks));
|
| 310 |
310 |
|
let open = self
|
| 331 |
331 |
|
Pane::Projects => (&mut self.proj_state, self.projects.len()),
|
| 332 |
332 |
|
Pane::Events => (&mut self.event_state, self.events.len()),
|
| 333 |
333 |
|
Pane::Contacts => (&mut self.contact_state, self.contacts.len()),
|
| 334 |
|
- |
_ => return,
|
|
334 |
+ |
// Named rather than a wildcard, so a pane added later has to come
|
|
335 |
+ |
// here and say whether it scrolls instead of silently not doing it.
|
|
336 |
+ |
Pane::Timer => return,
|
| 335 |
337 |
|
};
|
| 336 |
338 |
|
if len == 0 {
|
| 337 |
339 |
|
return;
|
| 423 |
425 |
|
/// so the Timer pane buckets time exactly as the Day view does.
|
| 424 |
426 |
|
fn local_midnight_utc(date: NaiveDate) -> chrono::DateTime<Utc> {
|
| 425 |
427 |
|
let naive = date.and_hms_opt(0, 0, 0).expect("midnight is always valid");
|
| 426 |
|
- |
Local
|
| 427 |
|
- |
.from_local_datetime(&naive)
|
| 428 |
|
- |
.earliest()
|
| 429 |
|
- |
.map(|dt| dt.with_timezone(&Utc))
|
| 430 |
|
- |
.unwrap_or_else(|| Utc.from_utc_datetime(&naive))
|
|
428 |
+ |
Local.from_local_datetime(&naive).earliest().map_or_else(
|
|
429 |
+ |
|| Utc.from_utc_datetime(&naive),
|
|
430 |
+ |
|dt| dt.with_timezone(&Utc),
|
|
431 |
+ |
)
|
| 431 |
432 |
|
}
|
| 432 |
433 |
|
|
| 433 |
434 |
|
fn is_open(t: &Task) -> bool {
|
| 551 |
552 |
|
/// session's task id back to its description and project for display.
|
| 552 |
553 |
|
fn build_timer(
|
| 553 |
554 |
|
active: Option<(TimeSession, String)>,
|
| 554 |
|
- |
summary: Vec<TimeTrackingSummary>,
|
|
555 |
+ |
summary: &[TimeTrackingSummary],
|
| 555 |
556 |
|
sessions: Vec<TimeSession>,
|
| 556 |
557 |
|
tasks: &[Task],
|
| 557 |
558 |
|
) -> TimerView {
|
| 564 |
565 |
|
// the desktop Day view does. "Today" is matched on the UTC date the query
|
| 565 |
566 |
|
// buckets by, same as the app.
|
| 566 |
567 |
|
let today = Utc::now().format("%Y-%m-%d").to_string();
|
| 567 |
|
- |
let panel = roll_up_time_summary(&summary, &today);
|
|
568 |
+ |
let panel = roll_up_time_summary(summary, &today);
|
| 568 |
569 |
|
|
| 569 |
570 |
|
// Most recent finished sessions, newest first.
|
| 570 |
571 |
|
let mut finished: Vec<TimeSession> = sessions
|
| 583 |
584 |
|
.with_timezone(&Local)
|
| 584 |
585 |
|
.format("%b %-d")
|
| 585 |
586 |
|
.to_string(),
|
| 586 |
|
- |
task: task
|
| 587 |
|
- |
.map(|t| t.description.clone())
|
| 588 |
|
- |
.unwrap_or_else(|| "(unknown task)".to_string()),
|
|
587 |
+ |
task: task.map_or_else(|| "(unknown task)".to_string(), |t| t.description.clone()),
|
| 589 |
588 |
|
project: task.and_then(|t| t.project_name.clone()),
|
| 590 |
589 |
|
minutes: s.duration_minutes.unwrap_or_else(|| s.elapsed_minutes()),
|
| 591 |
590 |
|
}
|
| 822 |
821 |
|
}
|
| 823 |
822 |
|
}
|
| 824 |
823 |
|
|
| 825 |
|
- |
fn row_item<'a>(p: Theme, row: &'a Row) -> ListItem<'a> {
|
|
824 |
+ |
fn row_item(p: Theme, row: &Row) -> ListItem<'_> {
|
| 826 |
825 |
|
match row {
|
| 827 |
826 |
|
Row::Header { name, open } => {
|
| 828 |
827 |
|
let line = Line::from(vec![
|
| 900 |
899 |
|
}
|
| 901 |
900 |
|
}
|
| 902 |
901 |
|
|
| 903 |
|
- |
fn project_item<'a>(p: Theme, row: &'a ProjectRow) -> ListItem<'a> {
|
|
902 |
+ |
fn project_item(p: Theme, row: &ProjectRow) -> ListItem<'_> {
|
| 904 |
903 |
|
let status_color = match row.status {
|
| 905 |
904 |
|
ProjectStatus::Active => p.status_success,
|
| 906 |
905 |
|
ProjectStatus::OnHold => p.status_warning,
|
| 935 |
934 |
|
ListItem::new(line)
|
| 936 |
935 |
|
}
|
| 937 |
936 |
|
|
| 938 |
|
- |
fn event_item<'a>(p: Theme, row: &'a EventRow) -> ListItem<'a> {
|
|
937 |
+ |
fn event_item(p: Theme, row: &EventRow) -> ListItem<'_> {
|
| 939 |
938 |
|
match row {
|
| 940 |
939 |
|
EventRow::DayHeader { label, count } => {
|
| 941 |
940 |
|
let line = Line::from(vec![
|
| 981 |
980 |
|
}
|
| 982 |
981 |
|
}
|
| 983 |
982 |
|
|
| 984 |
|
- |
fn contact_item<'a>(p: Theme, c: &'a Contact) -> ListItem<'a> {
|
|
983 |
+ |
fn contact_item(p: Theme, c: &Contact) -> ListItem<'_> {
|
| 985 |
984 |
|
let mut spans = vec![
|
| 986 |
985 |
|
Span::raw(" "),
|
| 987 |
986 |
|
Span::styled(
|
| 1156 |
1155 |
|
let base = home.join("Library/Application Support");
|
| 1157 |
1156 |
|
|
| 1158 |
1157 |
|
#[cfg(not(target_os = "macos"))]
|
| 1159 |
|
- |
let base = std::env::var_os("XDG_DATA_HOME")
|
| 1160 |
|
- |
.map(PathBuf::from)
|
| 1161 |
|
- |
.unwrap_or_else(|| home.join(".local/share"));
|
|
1158 |
+ |
let base =
|
|
1159 |
+ |
std::env::var_os("XDG_DATA_HOME").map_or_else(|| home.join(".local/share"), PathBuf::from);
|
| 1162 |
1160 |
|
|
| 1163 |
1161 |
|
Some(base.join("com.goingson.app").join("goingson.db"))
|
| 1164 |
1162 |
|
}
|