diff options
| author | AKP <tom@tdpain.net> | 2022-04-02 21:56:50 +0100 |
|---|---|---|
| committer | AKP <tom@tdpain.net> | 2022-04-02 21:56:50 +0100 |
| commit | 2bf9ce74de03a2d75e7c19a380119deef77d8fba (patch) | |
| tree | 359397b715db87c9e27702497bf90ff040b92ae1 | |
| parent | 32ba36903f66d7613534f49df81fd834ba1c5925 (diff) | |
Add loading things and success toast
Signed-off-by: AKP <tom@tdpain.net>
| -rw-r--r-- | walrss/internal/http/edit.go | 21 | ||||
| -rw-r--r-- | walrss/internal/http/http.go | 4 | ||||
| -rw-r--r-- | walrss/internal/http/views/main.qtpl.html | 201 | ||||
| -rw-r--r-- | walrss/internal/http/views/main.qtpl.html.go | 267 | ||||
| -rw-r--r-- | walrss/internal/http/views/page.qtpl.html | 17 | ||||
| -rw-r--r-- | walrss/internal/http/views/page.qtpl.html.go | 17 |
6 files changed, 335 insertions, 192 deletions
diff --git a/walrss/internal/http/edit.go b/walrss/internal/http/edit.go index 8599f07..0c5bd95 100644 --- a/walrss/internal/http/edit.go +++ b/walrss/internal/http/edit.go @@ -3,6 +3,7 @@ package http import ( "github.com/codemicro/walrss/walrss/internal/core" "github.com/codemicro/walrss/walrss/internal/db" + "github.com/codemicro/walrss/walrss/internal/http/views" "github.com/codemicro/walrss/walrss/internal/urls" "github.com/gofiber/fiber/v2" "strconv" @@ -12,7 +13,7 @@ import ( func (s *Server) editEnabledState(ctx *fiber.Ctx) error { currentUserID := getCurrentUserID(ctx) if currentUserID == "" { - return requestStandardSignIn(ctx) + return requestFragmentSignIn(ctx, urls.Index) } user, err := core.GetUserByID(s.state, currentUserID) @@ -30,14 +31,18 @@ func (s *Server) editEnabledState(ctx *fiber.Ctx) error { return err } - ctx.Set("HX-Redirect", urls.Index) - return nil + fragmentEmitSuccess(ctx) + return ctx.SendString((&views.MainPage{ + EnableDigests: user.Schedule.Active, + SelectedDay: user.Schedule.Day, + SelectedTime: user.Schedule.Hour, + }).RenderScheduleCard()) } func (s *Server) editTimings(ctx *fiber.Ctx) error { currentUserID := getCurrentUserID(ctx) if currentUserID == "" { - return requestStandardSignIn(ctx) + return requestFragmentSignIn(ctx, urls.Index) } user, err := core.GetUserByID(s.state, currentUserID) @@ -69,6 +74,10 @@ func (s *Server) editTimings(ctx *fiber.Ctx) error { return err } - ctx.Set("HX-Redirect", urls.Index) - return nil + fragmentEmitSuccess(ctx) + return ctx.SendString((&views.MainPage{ + EnableDigests: user.Schedule.Active, + SelectedDay: user.Schedule.Day, + SelectedTime: user.Schedule.Hour, + }).RenderScheduleCard()) } diff --git a/walrss/internal/http/http.go b/walrss/internal/http/http.go index cf0589e..603002b 100644 --- a/walrss/internal/http/http.go +++ b/walrss/internal/http/http.go @@ -130,3 +130,7 @@ func requestFragmentSignIn(ctx *fiber.Ctx, nextURL string) error { ctx.Set("HX-Redirect", urls.AuthSignIn+"?"+queryParams.Encode()) return nil } + +func fragmentEmitSuccess(ctx *fiber.Ctx) { + ctx.Set("HX-Trigger", "successResponse") +} diff --git a/walrss/internal/http/views/main.qtpl.html b/walrss/internal/http/views/main.qtpl.html index 65272ab..60a19eb 100644 --- a/walrss/internal/http/views/main.qtpl.html +++ b/walrss/internal/http/views/main.qtpl.html @@ -10,89 +10,49 @@ {% func (p *MainPage) Title() %}{% endfunc %} {% func (p *MainPage) Body() %} -{%= navbar() %} - -<div class="container"> - <h1>My settings</h1> - - <div class="card mt-3"> - <div class="card-header"> - Schedule +<div class="position-fixed top end-0 p-3" style="z-index: 11"> + <div id="toast" class="toast align-items-center text-white bg-primary border-0" role="alert" aria-live="assertive" aria-atomic="true"> + <div class="d-flex"> + <div class="toast-body" id="toastBody"> + Hello, world! This is a toast message. + </div> + <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button> </div> - <div class="card-body"> + </div> +</div> - <div class="mb-2"> - <input - type="checkbox" - id="enableCheckbox" - name="enable" - {% if p.EnableDigests %}checked{% endif %} - hx-put="{%s= urls.EditEnabledState %}" - hx-indicator="#enableCheckboxIndicator" - > - <label for="enableCheckbox">Enable digest delivery</label> - <i class="ml-2 iconLoading htmx-indicator" id="enableCheckboxIndicator"></i> - </div> +{%= navbar() %} - <form - class="row row-cols-lg-auto g-3 align-items-center" - hx-put="{%s urls.EditTimings %}" - hx-indicator="#submitScheduleIndicator" - > - <div class="col-12"> - Deliver my digests - </div> +<script> + let toast = document.getElementById("toast") + let toastBody = document.getElementById("toastBody") - <div class="col-12"> - <label class="visually-hidden" for="daySelection">Day of week</label> - <select - class="form-select" - id="daySelection" - name="day" - {% if !p.EnableDigests %}disabled{% endif %} - > - {% for i := db.SendDaily; i <= db.SendOnSunday; i += 1 %} - <option - value="{%d int(i) %}" - {% if p.SelectedDay == i %}selected{% endif %} - > - {% if i != db.SendDaily %}on {% endif %}{%s i.String() %} - </option> - {% endfor %} - </select> - </div> + function showToast(delay) { + new bootstrap.Toast(toast, {delay: delay}).show(); + } - <div class="col-12">at</div> + function errorHandler() { + toastBody.innerText = "Internal error: action incomplete"; + toast.classList.remove("bg-success") + toast.classList.add("bg-danger") + showToast(5000) + } - <div class="col-12"> - <label class="visually-hidden" for="timeSelection">Time of day</label> - <select - class="form-select" - id="timeSelection" - name="time" - {% if !p.EnableDigests %}disabled{% endif %} - > - {% for i := 0; i <= 23; i += 1 %} - <option - value="{%d i %}" - {% if p.SelectedTime == i %}selected{% endif %} - > - {%d i %}:00 - </option> - {% endfor %} - </select> - </div> + document.body.addEventListener("htmx:sendError", errorHandler); + document.body.addEventListener("htmx:responseError", errorHandler); - <div class="col-12">UTC</div> + document.body.addEventListener("successResponse", function () { + toastBody.innerText = "Success!" + toast.classList.remove("bg-danger") + toast.classList.add("bg-success") + showToast(1500) + }) +</script> - <div class="col-12"> - <button type="submit" class="btn btn-primary" {% if !p.EnableDigests %}disabled{% endif %}>Save</button> - <i class="iconLoading align-middle htmx-indicator" style="margin-left: 1rem; width: 2rem;" id="submitScheduleIndicator"></i> - </div> - </form> +<div class="container"> + <h1>My settings</h1> - </div> - </div> + {%= p.RenderScheduleCard() %} <div class="card mt-3"> <div class="card-header"> @@ -134,4 +94,97 @@ </div> </div> +{% endfunc %} + +{% func (p *MainPage) RenderScheduleCard() %} +<div class="card mt-3" id="scheduleCard" hx-target="this" hx-swap="outerHTML"> + <div class="card-header"> + Schedule + </div> + <div class="card-body"> + + <div class="mb-2 row row-cols-lg-auto align-items-center"> + <div class="col-12"> + <input + type="checkbox" + id="enableCheckbox" + name="enable" + {% if p.EnableDigests %}checked{% endif %} + hx-put="{%s= urls.EditEnabledState %}" + hx-indicator="#enableCheckboxIndicator" + > + <label for="enableCheckbox">Enable digest delivery</label> + </div> + + <div class="col-12"> + <div class="spinner-border spinner-border-sm request-indicator align-middle" style="width: 1rem; height: 1rem;" role="status" id="enableCheckboxIndicator"> + <span class="visually-hidden">Loading...</span> + </div> + </div> + </div> + + <form + class="row row-cols-lg-auto g-3 align-items-center" + hx-put="{%s urls.EditTimings %}" + hx-indicator="#submitScheduleIndicator" + > + <div class="col-12"> + Deliver my digests + </div> + + <div class="col-12"> + <label class="visually-hidden" for="daySelection">Day of week</label> + <select + class="form-select" + id="daySelection" + name="day" + {% if !p.EnableDigests %}disabled{% endif %} + > + {% for i := db.SendDaily; i <= db.SendOnSunday; i += 1 %} + <option + value="{%d int(i) %}" + {% if p.SelectedDay == i %}selected{% endif %} + > + {% if i != db.SendDaily %}on {% endif %}{%s i.String() %} + </option> + {% endfor %} + </select> + </div> + + <div class="col-12">at</div> + + <div class="col-12"> + <label class="visually-hidden" for="timeSelection">Time of day</label> + <select + class="form-select" + id="timeSelection" + name="time" + {% if !p.EnableDigests %}disabled{% endif %} + > + {% for i := 0; i <= 23; i += 1 %} + <option + value="{%d i %}" + {% if p.SelectedTime == i %}selected{% endif %} + > + {%d i %}:00 + </option> + {% endfor %} + </select> + </div> + + <div class="col-12">UTC</div> + + <div class="col-12"> + <button type="submit" class="btn btn-primary" {% if !p.EnableDigests %}disabled{% endif %}>Save</button> + </div> + + <div class="col-12"> + <div class="spinner-border align-middle request-indicator" id="submitScheduleIndicator" style="width: 2rem; height: 2rem;" role="status"> + <span class="visually-hidden">Loading...</span> + </div> + </div> + </form> + + </div> +</div> {% endfunc %}
\ No newline at end of file diff --git a/walrss/internal/http/views/main.qtpl.html.go b/walrss/internal/http/views/main.qtpl.html.go index a6c7b1b..d18a5ac 100644 --- a/walrss/internal/http/views/main.qtpl.html.go +++ b/walrss/internal/http/views/main.qtpl.html.go @@ -44,20 +44,121 @@ func (p *MainPage) Title() string { func (p *MainPage) StreamBody(qw422016 *qt422016.Writer) { qw422016.N().S(` +<div class="position-fixed top end-0 p-3" style="z-index: 11"> + <div id="toast" class="toast align-items-center text-white bg-primary border-0" role="alert" aria-live="assertive" aria-atomic="true"> + <div class="d-flex"> + <div class="toast-body" id="toastBody"> + Hello, world! This is a toast message. + </div> + <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button> + </div> + </div> +</div> + `) streamnavbar(qw422016) qw422016.N().S(` +<script> + let toast = document.getElementById("toast") + let toastBody = document.getElementById("toastBody") + + function showToast(delay) { + new bootstrap.Toast(toast, {delay: delay}).show(); + } + + function errorHandler() { + toastBody.innerText = "Internal error: action incomplete"; + toast.classList.remove("bg-success") + toast.classList.add("bg-danger") + showToast(5000) + } + + document.body.addEventListener("htmx:sendError", errorHandler); + document.body.addEventListener("htmx:responseError", errorHandler); + + document.body.addEventListener("successResponse", function () { + toastBody.innerText = "Success!" + toast.classList.remove("bg-danger") + toast.classList.add("bg-success") + showToast(1500) + }) +</script> + <div class="container"> <h1>My settings</h1> + `) + p.StreamRenderScheduleCard(qw422016) + qw422016.N().S(` + <div class="card mt-3"> <div class="card-header"> - Schedule + Feeds </div> <div class="card-body"> - <div class="mb-2"> + <table class="table"> + <thead> + <tr> + <th scope="col">#</th> + <th scope="col">First</th> + <th scope="col">Last</th> + <th scope="col">Handle</th> + </tr> + </thead> + <tbody> + <tr> + <th scope="row">1</th> + <td>Mark</td> + <td>Otto</td> + <td>@mdo</td> + </tr> + <tr> + <th scope="row">2</th> + <td>Jacob</td> + <td>Thornton</td> + <td>@fat</td> + </tr> + <tr> + <th scope="row">3</th> + <td colspan="2">Larry the Bird</td> + <td>@twitter</td> + </tr> + </tbody> + </table> + + </div> + </div> + +</div> +`) +} + +func (p *MainPage) WriteBody(qq422016 qtio422016.Writer) { + qw422016 := qt422016.AcquireWriter(qq422016) + p.StreamBody(qw422016) + qt422016.ReleaseWriter(qw422016) +} + +func (p *MainPage) Body() string { + qb422016 := qt422016.AcquireByteBuffer() + p.WriteBody(qb422016) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} + +func (p *MainPage) StreamRenderScheduleCard(qw422016 *qt422016.Writer) { + qw422016.N().S(` +<div class="card mt-3" id="scheduleCard" hx-target="this" hx-swap="outerHTML"> + <div class="card-header"> + Schedule + </div> + <div class="card-body"> + + <div class="mb-2 row row-cols-lg-auto align-items-center"> + <div class="col-12"> <input type="checkbox" id="enableCheckbox" @@ -73,162 +174,132 @@ func (p *MainPage) StreamBody(qw422016 *qt422016.Writer) { hx-indicator="#enableCheckboxIndicator" > <label for="enableCheckbox">Enable digest delivery</label> - <i class="ml-2 iconLoading htmx-indicator" id="enableCheckboxIndicator"></i> </div> - <form - class="row row-cols-lg-auto g-3 align-items-center" - hx-put="`) + <div class="col-12"> + <div class="spinner-border spinner-border-sm request-indicator align-middle" style="width: 1rem; height: 1rem;" role="status" id="enableCheckboxIndicator"> + <span class="visually-hidden">Loading...</span> + </div> + </div> + </div> + + <form + class="row row-cols-lg-auto g-3 align-items-center" + hx-put="`) qw422016.E().S(urls.EditTimings) qw422016.N().S(`" - hx-indicator="#submitScheduleIndicator" - > - <div class="col-12"> - Deliver my digests - </div> + hx-indicator="#submitScheduleIndicator" + > + <div class="col-12"> + Deliver my digests + </div> - <div class="col-12"> - <label class="visually-hidden" for="daySelection">Day of week</label> - <select - class="form-select" - id="daySelection" - name="day" - `) + <div class="col-12"> + <label class="visually-hidden" for="daySelection">Day of week</label> + <select + class="form-select" + id="daySelection" + name="day" + `) if !p.EnableDigests { qw422016.N().S(`disabled`) } qw422016.N().S(` - > - `) + > + `) for i := db.SendDaily; i <= db.SendOnSunday; i += 1 { qw422016.N().S(` - <option - value="`) + <option + value="`) qw422016.N().D(int(i)) qw422016.N().S(`" - `) + `) if p.SelectedDay == i { qw422016.N().S(`selected`) } qw422016.N().S(` - > - `) + > + `) if i != db.SendDaily { qw422016.N().S(`on `) } qw422016.E().S(i.String()) qw422016.N().S(` - </option> - `) + </option> + `) } qw422016.N().S(` - </select> - </div> + </select> + </div> - <div class="col-12">at</div> + <div class="col-12">at</div> - <div class="col-12"> - <label class="visually-hidden" for="timeSelection">Time of day</label> - <select - class="form-select" - id="timeSelection" - name="time" - `) + <div class="col-12"> + <label class="visually-hidden" for="timeSelection">Time of day</label> + <select + class="form-select" + id="timeSelection" + name="time" + `) if !p.EnableDigests { qw422016.N().S(`disabled`) } qw422016.N().S(` - > - `) + > + `) for i := 0; i <= 23; i += 1 { qw422016.N().S(` - <option - value="`) + <option + value="`) qw422016.N().D(i) qw422016.N().S(`" - `) + `) if p.SelectedTime == i { qw422016.N().S(`selected`) } qw422016.N().S(` - > - `) + > + `) qw422016.N().D(i) qw422016.N().S(`:00 - </option> - `) + </option> + `) } qw422016.N().S(` - </select> - </div> + </select> + </div> - <div class="col-12">UTC</div> + <div class="col-12">UTC</div> - <div class="col-12"> - <button type="submit" class="btn btn-primary" `) + <div class="col-12"> + <button type="submit" class="btn btn-primary" `) if !p.EnableDigests { qw422016.N().S(`disabled`) } qw422016.N().S(`>Save</button> - <i class="iconLoading align-middle htmx-indicator" style="margin-left: 1rem; width: 2rem;" id="submitScheduleIndicator"></i> - </div> - </form> - - </div> - </div> - - <div class="card mt-3"> - <div class="card-header"> - Feeds - </div> - <div class="card-body"> + </div> - <table class="table"> - <thead> - <tr> - <th scope="col">#</th> - <th scope="col">First</th> - <th scope="col">Last</th> - <th scope="col">Handle</th> - </tr> - </thead> - <tbody> - <tr> - <th scope="row">1</th> - <td>Mark</td> - <td>Otto</td> - <td>@mdo</td> - </tr> - <tr> - <th scope="row">2</th> - <td>Jacob</td> - <td>Thornton</td> - <td>@fat</td> - </tr> - <tr> - <th scope="row">3</th> - <td colspan="2">Larry the Bird</td> - <td>@twitter</td> - </tr> - </tbody> - </table> + <div class="col-12"> + <div class="spinner-border align-middle request-indicator" id="submitScheduleIndicator" style="width: 2rem; height: 2rem;" role="status"> + <span class="visually-hidden">Loading...</span> + </div> + </div> + </form> - </div> </div> - </div> `) } -func (p *MainPage) WriteBody(qq422016 qtio422016.Writer) { +func (p *MainPage) WriteRenderScheduleCard(qq422016 qtio422016.Writer) { qw422016 := qt422016.AcquireWriter(qq422016) - p.StreamBody(qw422016) + p.StreamRenderScheduleCard(qw422016) qt422016.ReleaseWriter(qw422016) } -func (p *MainPage) Body() string { +func (p *MainPage) RenderScheduleCard() string { qb422016 := qt422016.AcquireByteBuffer() - p.WriteBody(qb422016) + p.WriteRenderScheduleCard(qb422016) qs422016 := string(qb422016.B) qt422016.ReleaseByteBuffer(qb422016) return qs422016 diff --git a/walrss/internal/http/views/page.qtpl.html b/walrss/internal/http/views/page.qtpl.html index f885de9..9e9c0c2 100644 --- a/walrss/internal/http/views/page.qtpl.html +++ b/walrss/internal/http/views/page.qtpl.html @@ -15,19 +15,22 @@ Page prints a page implementing Page interface. <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="https://unpkg.com/htmx.org@1.7.0"></script> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> + <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script> <style> [disabled] { cursor: not-allowed; } - i.iconLoading { - background-image: url("data:image/svg+xml,%3Csvg xmlns:svg='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='64px' height='64px' viewBox='0 0 128 128' xml:space='preserve'%3E%3Cg%3E%3Cpath d='M75.4 126.63a11.43 11.43 0 0 1-2.1-22.65 40.9 40.9 0 0 0 30.5-30.6 11.4 11.4 0 1 1 22.27 4.87h.02a63.77 63.77 0 0 1-47.8 48.05v-.02a11.38 11.38 0 0 1-2.93.37z' fill='%23000000'/%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 64 64' to='360 64 64' dur='1000ms' repeatCount='indefinite'%3E%3C/animateTransform%3E%3C/g%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-size: cover; - display: inline-block; - width: 1rem; - aspect-ratio: 1/1; + .request-indicator { + display:none; } + .htmx-request .request-indicator{ + display: block; + } + .htmx-request.request-indicator{ + display: block; + } + </style> {%= p.HeadContent() %} </head> diff --git a/walrss/internal/http/views/page.qtpl.html.go b/walrss/internal/http/views/page.qtpl.html.go index 4d53006..b0900f9 100644 --- a/walrss/internal/http/views/page.qtpl.html.go +++ b/walrss/internal/http/views/page.qtpl.html.go @@ -39,19 +39,22 @@ func StreamRenderPage(qw422016 *qt422016.Writer, p Page) { <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="https://unpkg.com/htmx.org@1.7.0"></script> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> + <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script> <style> [disabled] { cursor: not-allowed; } - i.iconLoading { - background-image: url("data:image/svg+xml,%3Csvg xmlns:svg='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='64px' height='64px' viewBox='0 0 128 128' xml:space='preserve'%3E%3Cg%3E%3Cpath d='M75.4 126.63a11.43 11.43 0 0 1-2.1-22.65 40.9 40.9 0 0 0 30.5-30.6 11.4 11.4 0 1 1 22.27 4.87h.02a63.77 63.77 0 0 1-47.8 48.05v-.02a11.38 11.38 0 0 1-2.93.37z' fill='%23000000'/%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 64 64' to='360 64 64' dur='1000ms' repeatCount='indefinite'%3E%3C/animateTransform%3E%3C/g%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-size: cover; - display: inline-block; - width: 1rem; - aspect-ratio: 1/1; + .request-indicator { + display:none; } + .htmx-request .request-indicator{ + display: block; + } + .htmx-request.request-indicator{ + display: block; + } + </style> `) p.StreamHeadContent(qw422016) |
