Project · Time and databases: a time tracker, a pomodoro, a database
TimeTracker times a task: a field names it, Start or Enter starts the clock and Stop ends it, the running time counting up by the executor's clock; the entries sit under it in hours and minutes with their total. The owner keeps what runs and what was spent, and a blank name starts nothing. PomodoroTimer runs focus, a short break, and after the last round a long one. A ring counts each phase down around the time left, dots count the rounds, Pause banks the time gone, and a phase that runs out moves on, waits for Start, and reaches the owner through window.defer. motion::ProgressRing gains a size and content inside, its stroke kept at the theme ring's weight, the content outside the per-turn glide. DatabaseView shows tasks five ways, switched from a strip that scrolls when narrow: a table, a board by status whose moves hand on the new status, a gallery of issue cards, a month calendar by due day, and a timeline from start to due, which counts the records without dates. Task gains a start day. Along the way: table rows, groups and details fill with min_w_full, so they stretch in the table's column and fill a virtual list alike; before, a table in a measured column drew rows at their words' width. The gallery script logs each probe it aims at.
This commit is contained in:
1 parent
943862caad
commit
f70726476a
19 files changed
+1294
-23
No files matched your search
@@ -11,6 +11,7 @@ use jiff::{Timestamp, ToSpan, civil::Date, tz::TimeZone};
|
||||
mod boards;
|
||||
mod plans;
|
||||
mod tasks;
|
||||
mod time;
|
||||
|
||||
use super::Page;
|
||||
use crate::{script::Step, ui::keep};
|
||||
@@ -42,6 +43,14 @@ const SCRIPT: &[Step] = &[
|
||||
Step::UpAt("project-board", 360.0, 110.0),
|
||||
Step::Wait(300),
|
||||
Step::Shot("board-dropped"),
|
||||
Step::DownAt("project-database", 105.0, 17.0),
|
||||
Step::UpAt("project-database", 105.0, 17.0),
|
||||
Step::Wait(300),
|
||||
Step::Shot("database-board"),
|
||||
Step::DownAt("project-database", 349.0, 17.0),
|
||||
Step::UpAt("project-database", 349.0, 17.0),
|
||||
Step::Wait(300),
|
||||
Step::Shot("database-timeline"),
|
||||
];
|
||||
|
||||
/// What a demo says it did, under it.
|
||||
@@ -100,12 +109,14 @@ fn work(today: Date) -> Vec<Task> {
|
||||
.issue(IssueId::new("ARC", 41))
|
||||
.status(Status::InProgress)
|
||||
.priority(Priority::Urgent)
|
||||
.starts(day(-9))
|
||||
.due(day(-1))
|
||||
.assignee(who("ana"))
|
||||
.label(tag("client")),
|
||||
Task::new("samples", "Order plaster samples")
|
||||
.issue(IssueId::new("ARC", 44))
|
||||
.priority(Priority::High)
|
||||
.starts(day(-3))
|
||||
.due(day(0))
|
||||
.assignee(who("ben"))
|
||||
.label(tag("budget")),
|
||||
@@ -113,6 +124,7 @@ fn work(today: Date) -> Vec<Task> {
|
||||
.issue(IssueId::new("ARC", 38))
|
||||
.status(Status::InReview)
|
||||
.priority(Priority::Medium)
|
||||
.starts(day(-5))
|
||||
.due(day(3))
|
||||
.assignee(who("chloe"))
|
||||
.label(tag("site"))
|
||||
@@ -125,6 +137,7 @@ fn work(today: Date) -> Vec<Task> {
|
||||
.issue(IssueId::new("ARC", 29))
|
||||
.status(Status::Done)
|
||||
.priority(Priority::Medium)
|
||||
.starts(day(-16))
|
||||
.due(day(-4))
|
||||
.assignee(who("dev"))
|
||||
.label(tag("site")),
|
||||
@@ -152,5 +165,8 @@ fn render(window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
.child(plans::burndown(cx))
|
||||
.child(plans::milestones(cx))
|
||||
.child(plans::workload(cx))
|
||||
.child(time::tracker(window, cx))
|
||||
.child(time::pomodoro(window, cx))
|
||||
.child(time::database(window, cx))
|
||||
.into_any_element()
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use ely_gpui_component::project::{DatabaseView, PomodoroTimer, Status, TimeEntry, TimeTracker};
|
||||
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
||||
|
||||
use super::{board, quiet};
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{change, keep, section, set},
|
||||
};
|
||||
|
||||
pub fn tracker(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let entries = keep(
|
||||
"project-entries",
|
||||
|| {
|
||||
vec![
|
||||
TimeEntry::new("e1", "Client brief", Duration::from_secs(95 * 60)),
|
||||
TimeEntry::new("e2", "Site photos", Duration::from_secs(40 * 60)),
|
||||
TimeEntry::new("e3", "Budget review", Duration::from_secs(25 * 60)),
|
||||
]
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let running = keep(
|
||||
"project-running",
|
||||
|| None::<(SharedString, Instant)>,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let (listed, now) = (entries.read(cx).clone(), running.read(cx).clone());
|
||||
let (started, stopped, logged) = (running.clone(), running, entries);
|
||||
let tracker = TimeTracker::new("project-tracker", listed)
|
||||
.on_start(move |task, at, _, cx| set(&started, Some((task.clone(), at)), cx))
|
||||
.on_stop(move |at, _, cx| {
|
||||
let Some((task, since)) = stopped.update(cx, |running, cx| {
|
||||
cx.notify();
|
||||
running.take()
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
change(&logged, cx, |entries| {
|
||||
let key = SharedString::from(format!("e{}", entries.len() + 1));
|
||||
entries.insert(0, TimeEntry::new(key, task, at.duration_since(since)));
|
||||
});
|
||||
});
|
||||
let tracker = match now {
|
||||
Some((task, since)) => tracker.running(task, since),
|
||||
None => tracker,
|
||||
};
|
||||
section(
|
||||
"TimeTracker",
|
||||
"Time against a task: name it, Start or Enter starts the clock, and Stop turns it into an entry. Today's entries sit under it with their total.",
|
||||
cx,
|
||||
)
|
||||
.child(probe("project-tracker", div().w(px(420.)).child(tracker)))
|
||||
}
|
||||
|
||||
pub fn pomodoro(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let said = keep("project-pomodoro-said", || None::<SharedString>, window, cx);
|
||||
let shown = said.read(cx).clone();
|
||||
section(
|
||||
"Pomodoro Timer",
|
||||
"Focus for twenty-five minutes, rest for five, and after the fourth round for fifteen. The ring counts the phase down around the time left; a phase that ends waits for Start.",
|
||||
cx,
|
||||
)
|
||||
.child(probe(
|
||||
"project-pomodoro",
|
||||
div()
|
||||
.w(px(280.))
|
||||
.flex()
|
||||
.flex_col()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.child(
|
||||
PomodoroTimer::new("project-pomodoro")
|
||||
.on_phase(move |phase, _, cx| set(&said, Some(format!("{} begins.", phase.words()).into()), cx)),
|
||||
)
|
||||
.children(shown.map(|shown| quiet(shown, cx))),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn database(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let tasks = board(window, cx);
|
||||
let listed = tasks.read(cx).clone();
|
||||
let opened = keep(
|
||||
"project-opened",
|
||||
|| Some(SharedString::from("brief")),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let moved = tasks.clone();
|
||||
let view = DatabaseView::new("project-database", listed)
|
||||
.on_open(move |key, _, cx| set(&opened, Some(key.clone()), cx))
|
||||
.on_status(move |key, status: Status, _, cx| {
|
||||
change(&moved, cx, |tasks| {
|
||||
let task = tasks
|
||||
.iter_mut()
|
||||
.find(|task| task.key == *key)
|
||||
.expect("a listed task");
|
||||
task.status = status;
|
||||
})
|
||||
});
|
||||
section(
|
||||
"DatabaseView",
|
||||
"The studio's tasks five ways: a table, a board by status, a gallery of cards, a month calendar by due day, and a timeline from start to due. A card moved on the board takes its column's status, and a record opens in the panel above.",
|
||||
cx,
|
||||
)
|
||||
.child(probe("project-database", div().w(px(860.)).child(view)))
|
||||
}
|
||||
@@ -425,6 +425,9 @@ async fn target_bounds(
|
||||
if window.update(cx, |gallery, _, cx| gallery.reveal(bounds, cx))? {
|
||||
cx.background_executor().timer(FRAME).await;
|
||||
}
|
||||
cx.update(|cx| Probes::get(key, cx))?
|
||||
.with_context(|| format!("probe {key} vanished after scrolling"))
|
||||
let bounds = cx
|
||||
.update(|cx| Probes::get(key, cx))?
|
||||
.with_context(|| format!("probe {key} vanished after scrolling"))?;
|
||||
log::info!("script: probe {key} at {bounds:?}");
|
||||
Ok(bounds)
|
||||
}
|
||||
Reference in new issue
Block a user