diff options
-rw-r--r-- | model/time.go | 6 | ||||
-rw-r--r-- | run.bat | 2 | ||||
-rw-r--r-- | static/ui-components.js | 65 | ||||
-rw-r--r-- | timer.go | 27 | ||||
-rw-r--r-- | view/main.templ | 1 | ||||
-rw-r--r-- | view/main_templ.go | 2 | ||||
-rw-r--r-- | view/timer.templ | 18 | ||||
-rw-r--r-- | view/timer_templ.go | 53 | ||||
-rw-r--r-- | view/timers_list.templ | 2 | ||||
-rw-r--r-- | view/timers_list_templ.go | 4 |
10 files changed, 160 insertions, 20 deletions
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 {
@@ -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);
+
@@ -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) { <head>
<title>Cool timer app</title>
<link rel="stylesheet" href="/static/style.css" />
+ <script type="module" src="/static/ui-components.js"></script>
<script src="/static/htmx.min.js"></script>
<script src="/static/response-targets.js"></script>
</head>
diff --git a/view/main_templ.go b/view/main_templ.go index 109be9c..5b3831d 100644 --- a/view/main_templ.go +++ b/view/main_templ.go @@ -23,7 +23,7 @@ func Main(contents templ.Component) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html><head><title>Cool timer app</title><link rel=\"stylesheet\" href=\"/static/style.css\"><script src=\"/static/htmx.min.js\"></script><script src=\"/static/response-targets.js\"></script></head><body hx-boost=\"true\" hx-ext=\"response-targets\">") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html><head><title>Cool timer app</title><link rel=\"stylesheet\" href=\"/static/style.css\"><script type=\"module\" src=\"/static/ui-components.js\"></script><script src=\"/static/htmx.min.js\"></script><script src=\"/static/response-targets.js\"></script></head><body hx-boost=\"true\" hx-ext=\"response-targets\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/view/timer.templ b/view/timer.templ index fa258eb..8d20933 100644 --- a/view/timer.templ +++ b/view/timer.templ @@ -7,6 +7,20 @@ import ( templ TimerView(timer model.Timer) {
<h1>This is timer { timer.Name } </h1>
<p><a href="/">Back to list</a></p>
- <p>Start time: { timer.StartTime.String() }</p>
- <p>End time: { timer.EndTime.String() }</p>
+ <p>Start time: <local-date>{ timer.StartTime.AsUTCString() }</local-date></p>
+ <p>End time: <local-date>{ timer.EndTime.AsUTCString() }</local-date></p>
+ <p>
+ Total time:
+ <timer-countdown
+ start={ timer.StartTime.AsUTCString() }
+ end={ timer.EndTime.AsUTCString() }
+ ></timer-countdown>
+ </p>
+ <p>
+ Remaining time:
+ <timer-countdown
+ end={ timer.EndTime.AsUTCString() }
+ ></timer-countdown>
+ </p>
}
+
diff --git a/view/timer_templ.go b/view/timer_templ.go index 7fee7d3..55e5346 100644 --- a/view/timer_templ.go +++ b/view/timer_templ.go @@ -40,33 +40,72 @@ func TimerView(timer model.Timer) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1><p><a href=\"/\">Back to list</a></p><p>Start time: ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1><p><a href=\"/\">Back to list</a></p><p>Start time: <local-date>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(timer.StartTime.String()) + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(timer.StartTime.AsUTCString()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 10, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 10, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p><p>End time: ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</local-date></p><p>End time: <local-date>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(timer.EndTime.String()) + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(timer.EndTime.AsUTCString()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 11, Col: 41} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 11, Col: 58} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p>") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</local-date></p><p>Total time:\r <timer-countdown start=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(timer.StartTime.AsUTCString()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 15, Col: 49} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" end=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(timer.EndTime.AsUTCString()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 16, Col: 45} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></timer-countdown></p><p>Remaining time:\r <timer-countdown end=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(timer.EndTime.AsUTCString()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 22, Col: 45} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></timer-countdown></p>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/view/timers_list.templ b/view/timers_list.templ index 2d71a52..92d7013 100644 --- a/view/timers_list.templ +++ b/view/timers_list.templ @@ -25,6 +25,8 @@ templ TimerCreateForm(timerName string, err string) { >
<p>
<input type="text" name="timerName" value={ timerName } placeholder="Name" />
+ <input type="number" name="days" placeholder="Days" style="width: 5em;" />
+ <input type="number" name="hours" placeholder="Hours" style="width: 5em;" />
<button type="submit">Create</button>
</p>
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\"> <button type=\"submit\">Create</button></p>") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" placeholder=\"Name\"> <input type=\"number\" name=\"days\" placeholder=\"Days\" style=\"width: 5em;\"> <input type=\"number\" name=\"hours\" placeholder=\"Hours\" style=\"width: 5em;\"> <button type=\"submit\">Create</button></p>") 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 { |