diff --git a/model/time.go b/model/time.go index b7dc3d2..baf3ce6 100644 --- a/model/time.go +++ b/model/time.go @@ -12,12 +12,12 @@ func MakeTimeNow() Time { return Time(time.Now().UTC()) } -func (self Time) String() string { - return time.Time(self).String() +func (self Time) AsUTCString() string { + return time.Time(self).Format(time.RFC3339) } func (self Time) Value() (sqldriver.Value, error) { - return time.Time(self).Format(time.RFC3339), nil + return self.AsUTCString(), nil } func (self *Time) Scan(value any) error { diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..1697457 --- /dev/null +++ b/run.bat @@ -0,0 +1,2 @@ +templ generate +go run . diff --git a/static/ui-components.js b/static/ui-components.js new file mode 100644 index 0000000..b825f23 --- /dev/null +++ b/static/ui-components.js @@ -0,0 +1,65 @@ +class LocalDate extends HTMLElement { + connectedCallback() { + let utcDate = new Date(this.innerText); + this.innerText = utcDate.toLocaleString(); + } +} +customElements.define("local-date", LocalDate); + +class TimerCountdown extends HTMLElement { + startTime; + endTime; + refreshInterval = null; + + refreshCountdown() { + let diff = Math.floor((this.endTime - this.startTime) / 1000); + if (diff <= 0) { + this.innerText = "Finished"; + return; + } + + let days = Math.floor(diff / (24 * 60 * 60)); + diff -= days * 24 * 60 * 60; + let hours = Math.floor(diff / (60 * 60)); + diff -= hours * 60 * 60; + let minutes = Math.floor(diff / 60); + diff -= minutes * 60; + let seconds = diff; + + let newText = ""; + if (days > 0) { + newText += `${days} days, `; + } + newText += `${hours.toFixed(0).padStart(2, "0")}:` + + `${minutes.toFixed(0).padStart(2, "0")}:` + + `${seconds.toFixed(0).padStart(2, "0")}`; + + this.innerText = newText; + } + + refreshCountdownNow() { + this.startTime = (new Date()).getTime(); + this.refreshCountdown(); + } + + connectedCallback() { + this.endTime = (new Date(this.getAttribute("end"))).getTime(); + if (this.hasAttribute("start")) { + this.startTime = (new Date(this.getAttribute("start"))).getTime(); + this.refreshCountdown(); + } + else { + this.startTime = (new Date()).getTime(); + this.refreshCountdown(); + this.refreshInterval = setInterval(this.refreshCountdownNow.bind(this), 1000); + } + } + + disconnectedCallback() { + if (this.refreshInterval !== null) { + clearInterval(this.refreshInterval); + } + } +} +customElements.define("timer-countdown", TimerCountdown); + diff --git a/timer.go b/timer.go index d5e0a67..324f846 100644 --- a/timer.go +++ b/timer.go @@ -6,6 +6,8 @@ import ( "database/sql" "context" "strings" + "time" + "strconv" _ "github.com/mattn/go-sqlite3" @@ -13,11 +15,12 @@ import ( "stevenlr.com/timer/model" ) -func insertTimer(tx *sql.Tx, name string) error { +func insertTimer(tx *sql.Tx, name string, seconds int) error { now := model.MakeTimeNow() + end := model.Time(time.Time(now).Add(time.Duration(seconds) * time.Second)) id := model.MakeUUID() _, err := tx.Exec(` - INSERT INTO Timer VALUES ($1, $2, $3, $4)`, id, name, now, now); + INSERT INTO Timer VALUES ($1, $2, $3, $4)`, id, name, now, end); return err } @@ -37,10 +40,10 @@ func initializeDatabase(db *sql.DB) error { `) if err != nil { return err } - err = insertTimer(tx, "My timer") + err = insertTimer(tx, "My timer", 600) if err != nil { return err } - err = insertTimer(tx, "My timer2") + err = insertTimer(tx, "My timer2", 600) if err != nil { return err } return tx.Commit() @@ -123,6 +126,20 @@ func (server *MyServer) handleDeleteTimer(w http.ResponseWriter, r *http.Request func (server* MyServer) handlePutTimer(w http.ResponseWriter, r *http.Request) { timerName := strings.TrimSpace(r.FormValue("timerName")) + days, err := strconv.ParseInt(strings.TrimSpace(r.FormValue("days")), 10, 32) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + view.TimerCreateForm(timerName, "Error parsing days").Render(context.Background(), w) + return + } + + hours, err := strconv.ParseInt(strings.TrimSpace(r.FormValue("hours")), 10, 32) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + view.TimerCreateForm(timerName, "Error parsing hours").Render(context.Background(), w) + return + } + tx, err := server.db.Begin() if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -137,7 +154,7 @@ func (server* MyServer) handlePutTimer(w http.ResponseWriter, r *http.Request) { return } - err = insertTimer(tx, timerName) + err = insertTimer(tx, timerName, int(((max(days, 0) * 24) + max(hours, 0)) * 3600)) if err != nil { w.WriteHeader(http.StatusInternalServerError) view.TimerCreateForm(timerName, "Internal server error").Render(context.Background(), w) diff --git a/view/main.templ b/view/main.templ index b6d94d8..64a365c 100644 --- a/view/main.templ +++ b/view/main.templ @@ -6,6 +6,7 @@ templ Main(contents templ.Component) {
Start time: { timer.StartTime.String() }
-End time: { timer.EndTime.String() }
+Start time:
End time:
+ Total time:
+
+ Remaining time:
+
Start time: ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Start time:
End time: ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
End time:
Total time:\r
Remaining time:\r
+ +
if err != "" { diff --git a/view/timers_list_templ.go b/view/timers_list_templ.go index af4922d..955ab9a 100644 --- a/view/timers_list_templ.go +++ b/view/timers_list_templ.go @@ -100,7 +100,7 @@ func TimerCreateForm(timerName string, err string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" placeholder=\"Name\"> ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" placeholder=\"Name\"> ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -112,7 +112,7 @@ func TimerCreateForm(timerName string, err string) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(err) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timers_list.templ`, Line: 31, Col: 34} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timers_list.templ`, Line: 33, Col: 34} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil {