diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 0c7ca1d..7461445 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -13,6 +13,7 @@ import 'hyperscript.org'; import * as _hyperscript from "hyperscript.org"; import hotkeys from "hotkeys-js"; import * as theme from "./theme.ts"; +import * as timer from "./timer.ts"; _hyperscript.browserInit(); @@ -146,46 +147,7 @@ document.addEventListener('click', function (event) { document.addEventListener("DOMContentLoaded", function () { theme.init(); - - let n = setInterval( - () => { - let whichOne = 0; - // document.getElementById('active-timer').querySelectorAll('span.timer-duration')[0] - let dd = document.getElementById('active-timer'); - if (dd === undefined || dd === null) { - return - } - let timeBox = dd.children[1].children[whichOne]; - let s = timeBox.textContent.split(":"); - let second = parseInt(s.pop()); - let minute = parseInt(s.pop()); - if (isNaN(minute)) { - minute = 0; - } - let hour = parseInt(s.pop()); - if (isNaN(hour)) { - hour = 0; - } - second += 1; - if (second >= 60) { - second = 0; - minute += 1; - if (minute > 60) { - hour += 1; - } - } - timeBox.textContent = hour.toString() - // @ts-ignore - .padStart(2, "0") + ":" + - minute.toString() - // @ts-ignore - .padStart(2, "0") + ":" + - second.toString() - // @ts-ignore - .padStart(2, "0"); - }, 1000 - ) - + timer.init(); let day_progress = setInterval( () => { const dd = document.getElementById('time_of_the_day'); diff --git a/frontend/src/timer.ts b/frontend/src/timer.ts new file mode 100644 index 0000000..da600f9 --- /dev/null +++ b/frontend/src/timer.ts @@ -0,0 +1,42 @@ +var global_twk_timer: number|null = null; + +function updateTimer() { + let timer_dom = document.getElementById('active-timer'); + if (timer_dom === null) { + return; + } + + let timer_box_dom = timer_dom.querySelector(".timer-duration"); + if (timer_box_dom === null) { + return; + } + + let task_start = timer_dom?.getAttribute("data-task-start"); + if (task_start === null || task_start === undefined) { + return; + } + let task_start_dt = Date.parse(task_start); + let now_utc = Date.now(); + let diff_s = Math.floor((now_utc - task_start_dt)/1000); + let hours = Math.floor(diff_s/3600); + let minutes = Math.floor((diff_s - (hours*3600))/60); + let seconds = Math.floor((diff_s - (hours*3600)) - (minutes*60)); + timer_box_dom.textContent = + hours.toString().padStart(2, "0") + ":" + + minutes.toString().padStart(2, "0") + ":" + + seconds.toString().padStart(2, "0") +} + +export function init() { + global_twk_timer = setInterval( + updateTimer, + 1000 + ) +} + +export function stop() { + if (global_twk_timer != null && global_twk_timer != undefined) { + clearInterval(global_twk_timer); + global_twk_timer = null; + } +} \ No newline at end of file diff --git a/frontend/templates/active_task.html b/frontend/templates/active_task.html index 7d9697f..317fba6 100644 --- a/frontend/templates/active_task.html +++ b/frontend/templates/active_task.html @@ -14,7 +14,7 @@
+ data-task-id="{{ active_task.uuid }}" data-task-start="{{ datetime_iso(datetime=active_task.start) }}"> {{ active_task.description }}
diff --git a/src/lib.rs b/src/lib.rs index 9f7a696..c4b72a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,10 @@ use std::str::FromStr; use crate::endpoints::tasks::task_query_builder::{TaskQuery, TaskReport}; use crate::endpoints::tasks::{is_a_tag, is_tag_keyword}; + +#[cfg(test)] +mod tests; +use chrono::TimeZone; use chrono::{DateTime, TimeDelta}; use linkify::LinkKind; use rand::distr::{Alphanumeric, SampleString}; @@ -25,6 +29,7 @@ use taskchampion::Uuid; use tera::{escape_html, Context}; use tracing::{trace, warn}; + lazy_static::lazy_static! { pub static ref TEMPLATES: tera::Tera = { let mut tera = match tera::Tera::new("dist/templates/**/*") { @@ -37,6 +42,7 @@ lazy_static::lazy_static! { tera.register_function("project_name", get_project_name_link()); tera.register_function("date_proper", get_date_proper()); tera.register_function("timer_value", get_timer()); + tera.register_function("datetime_iso", get_datetime_iso()); tera.register_function("date", get_date()); tera.register_function("obj", obj()); tera.register_function("remove_project_tag", remove_project_from_tag()); @@ -367,6 +373,17 @@ fn update_tag_bar_key_comb() -> impl tera::Filter { ) } +#[cfg(not(test))] +#[allow(dead_code)] +fn get_utc_now() -> DateTime { + chrono::prelude::Utc::now() +} +#[cfg(test)] +#[allow(dead_code)] +fn get_utc_now() -> DateTime { + chrono::Utc.with_ymd_and_hms(2025, 5, 1, 3, 55, 0).unwrap() +} + pub struct DeltaNow { pub now: DateTime, pub delta: TimeDelta, @@ -380,7 +397,7 @@ impl DeltaNow { // Try taskchampions variant. chrono::prelude::NaiveDateTime::parse_from_str(time, "%Y-%m-%dT%H:%M:%SZ").unwrap()) .and_utc(); - let now = chrono::prelude::Utc::now(); + let now = get_utc_now(); let delta = now - time; Self { now, delta, time } } @@ -424,6 +441,22 @@ fn get_date_proper() -> impl tera::Function { ) } +fn get_datetime_iso() -> impl tera::Function { + Box::new( + move |args: &HashMap| -> tera::Result { + let date_time_str = args.get("datetime").unwrap().as_str().unwrap(); + // we are working with utc time + let date_time = chrono::prelude::NaiveDateTime::parse_from_str(date_time_str, "%Y%m%dT%H%M%SZ") + .unwrap_or_else(|_| + // Try taskchampions variant. + chrono::prelude::NaiveDateTime::parse_from_str(date_time_str, "%Y-%m-%dT%H:%M:%SZ").unwrap() + ) + .and_utc(); + Ok(tera::to_value(date_time.to_rfc3339()).unwrap()) + }, + ) +} + fn get_date() -> impl tera::Function { Box::new( move |args: &HashMap| -> tera::Result { @@ -485,7 +518,7 @@ fn get_timer() -> impl tera::Function { let s = if delta.num_hours() > 0 { format!( - "{:>02}:{:>02}", + "{:>02}:{:>02}:00", delta.num_hours(), delta.num_minutes() - (delta.num_hours() * 60) ) @@ -497,7 +530,7 @@ fn get_timer() -> impl tera::Function { num_seconds % 60 ) } else { - format!("{}s", num_seconds) + format!("00:00:{:>02}", num_seconds) }; Ok(tera::to_value(s).unwrap()) }, diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..6180007 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,54 @@ +use tera::Function; + +use super::*; + +#[test] +fn test_get_timer() { + let get_timer_func = get_timer(); + let time_seconds: HashMap = HashMap::from([ + ("date".to_string(), serde_json::to_value("20250501T035408Z").unwrap()) + ]); + + let result = get_timer_func.call(&time_seconds); + assert_eq!(result.is_ok(), true); + let result_value = result.unwrap(); + assert_eq!(result_value, serde_json::to_value("00:00:52").unwrap()); + + let time_seconds: HashMap = HashMap::from([ + ("date".to_string(), serde_json::to_value("20250501T033408Z").unwrap()) + ]); + + let result = get_timer_func.call(&time_seconds); + assert_eq!(result.is_ok(), true); + let result_value = result.unwrap(); + assert_eq!(result_value, serde_json::to_value("00:20:52").unwrap()); + + let time_seconds: HashMap = HashMap::from([ + ("date".to_string(), serde_json::to_value("20250501T023408Z").unwrap()) + ]); + + let result = get_timer_func.call(&time_seconds); + assert_eq!(result.is_ok(), true); + let result_value = result.unwrap(); + assert_eq!(result_value, serde_json::to_value("01:20:00").unwrap()); +} + +#[test] +fn test_get_datetime_iso() { + let get_datetime_iso_func = get_datetime_iso(); + let datetime: HashMap = HashMap::from([ + ("datetime".to_string(), serde_json::to_value("20250501T035408Z").unwrap()) + ]); + let result = get_datetime_iso_func.call(&datetime); + assert_eq!(result.is_ok(), true); + let result_value = result.unwrap(); + assert_eq!(result_value, serde_json::to_value("2025-05-01T03:54:08+00:00").unwrap()); + + let datetime: HashMap = HashMap::from([ + ("datetime".to_string(), serde_json::to_value("2025-05-01T03:54:08Z").unwrap()) + ]); + let result = get_datetime_iso_func.call(&datetime); + assert_eq!(result.is_ok(), true); + let result_value = result.unwrap(); + assert_eq!(result_value, serde_json::to_value("2025-05-01T03:54:08+00:00").unwrap()); +}