Compare commits

..

7 Commits

13 changed files with 526 additions and 115 deletions

View File

@ -4,11 +4,14 @@ import (
"errors" "errors"
"time" "time"
"github.com/a-h/templ"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"git.grosinger.net/tgrosinger/saasitone/pkg/form" "git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page" "git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/pkg/services" "git.grosinger.net/tgrosinger/saasitone/pkg/services"
"git.grosinger.net/tgrosinger/saasitone/templ/layouts"
"git.grosinger.net/tgrosinger/saasitone/templ/pages"
"git.grosinger.net/tgrosinger/saasitone/templates" "git.grosinger.net/tgrosinger/saasitone/templates"
) )
@ -22,11 +25,6 @@ type (
cache *services.CacheClient cache *services.CacheClient
*services.TemplateRenderer *services.TemplateRenderer
} }
cacheForm struct {
Value string `form:"value"`
form.Submission
}
) )
func init() { func init() {
@ -46,10 +44,8 @@ func (h *Cache) Routes(g *echo.Group) {
func (h *Cache) Page(ctx echo.Context) error { func (h *Cache) Page(ctx echo.Context) error {
p := page.New(ctx) p := page.New(ctx)
p.Layout = templates.LayoutMain
p.Name = templates.PageCache p.Name = templates.PageCache
p.Title = "Set a cache entry" p.Title = "Set a cache entry"
p.Form = form.Get[cacheForm](ctx)
// Fetch the value from the cache // Fetch the value from the cache
value, err := h.cache. value, err := h.cache.
@ -57,20 +53,31 @@ func (h *Cache) Page(ctx echo.Context) error {
Key("page_cache_example"). Key("page_cache_example").
Fetch(ctx.Request().Context()) Fetch(ctx.Request().Context())
var valueStrPtr *string = nil
// Store the value in the page, so it can be rendered, if found // Store the value in the page, so it can be rendered, if found
switch { switch {
case err == nil: case err == nil:
p.Data = value.(string) valueStr := value.(string)
valueStrPtr = &valueStr
case errors.Is(err, services.ErrCacheMiss): case errors.Is(err, services.ErrCacheMiss):
default: default:
return fail(err, "failed to fetch from cache") return fail(err, "failed to fetch from cache")
} }
return h.RenderPage(ctx, p) f := form.Get[pages.CacheForm](ctx)
component := pages.Cache(p, f, valueStrPtr)
// TODO: This can be reused
p.LayoutComponent = func(content templ.Component) templ.Component {
return layouts.Main(p, content)
}
return h.RenderPageTempl(ctx, p, component)
} }
func (h *Cache) Submit(ctx echo.Context) error { func (h *Cache) Submit(ctx echo.Context) error {
var input cacheForm var input pages.CacheForm
if err := form.Submit(ctx, &input); err != nil { if err := form.Submit(ctx, &input); err != nil {
return err return err

View File

@ -4,10 +4,13 @@ import (
"fmt" "fmt"
"math/rand" "math/rand"
"github.com/a-h/templ"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"git.grosinger.net/tgrosinger/saasitone/pkg/page" "git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/pkg/services" "git.grosinger.net/tgrosinger/saasitone/pkg/services"
"git.grosinger.net/tgrosinger/saasitone/templ/layouts"
"git.grosinger.net/tgrosinger/saasitone/templ/pages"
"git.grosinger.net/tgrosinger/saasitone/templates" "git.grosinger.net/tgrosinger/saasitone/templates"
) )
@ -17,11 +20,6 @@ type (
Search struct { Search struct {
*services.TemplateRenderer *services.TemplateRenderer
} }
searchResult struct {
Title string
URL string
}
) )
func init() { func init() {
@ -39,23 +37,28 @@ func (h *Search) Routes(g *echo.Group) {
func (h *Search) Page(ctx echo.Context) error { func (h *Search) Page(ctx echo.Context) error {
p := page.New(ctx) p := page.New(ctx)
p.Layout = templates.LayoutMain
p.Name = templates.PageSearch p.Name = templates.PageSearch
// Fake search results // Fake search results
var results []searchResult var results []pages.SearchResult
if search := ctx.QueryParam("query"); search != "" { if search := ctx.QueryParam("query"); search != "" {
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
title := "Lorem ipsum example ddolor sit amet" title := "Lorem ipsum example ddolor sit amet"
index := rand.Intn(len(title)) index := rand.Intn(len(title))
title = title[:index] + search + title[index:] title = title[:index] + search + title[index:]
results = append(results, searchResult{ results = append(results, pages.SearchResult{
Title: title, Title: title,
URL: fmt.Sprintf("https://www.%s.com", search), URL: fmt.Sprintf("https://www.%s.com", search),
}) })
} }
} }
p.Data = results
return h.RenderPage(ctx, p) component := pages.Search(p, results)
// TODO: This can be reused
p.LayoutComponent = func(content templ.Component) templ.Component {
return layouts.Main(p, content)
}
return h.RenderPageTempl(ctx, p, component)
} }

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/a-h/templ"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -12,6 +13,8 @@ import (
"git.grosinger.net/tgrosinger/saasitone/pkg/page" "git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/pkg/services" "git.grosinger.net/tgrosinger/saasitone/pkg/services"
"git.grosinger.net/tgrosinger/saasitone/pkg/tasks" "git.grosinger.net/tgrosinger/saasitone/pkg/tasks"
"git.grosinger.net/tgrosinger/saasitone/templ/layouts"
"git.grosinger.net/tgrosinger/saasitone/templ/pages"
"git.grosinger.net/tgrosinger/saasitone/templates" "git.grosinger.net/tgrosinger/saasitone/templates"
) )
@ -25,12 +28,6 @@ type (
tasks *services.TaskClient tasks *services.TaskClient
*services.TemplateRenderer *services.TemplateRenderer
} }
taskForm struct {
Delay int `form:"delay" validate:"gte=0"`
Message string `form:"message" validate:"required"`
form.Submission
}
) )
func init() { func init() {
@ -50,16 +47,22 @@ func (h *Task) Routes(g *echo.Group) {
func (h *Task) Page(ctx echo.Context) error { func (h *Task) Page(ctx echo.Context) error {
p := page.New(ctx) p := page.New(ctx)
p.Layout = templates.LayoutMain
p.Name = templates.PageTask p.Name = templates.PageTask
p.Title = "Create a task" p.Title = "Create a task"
p.Form = form.Get[taskForm](ctx)
return h.RenderPage(ctx, p) f := form.Get[pages.TaskForm](ctx)
component := pages.Task(p, f)
// TODO: This can be reused
p.LayoutComponent = func(content templ.Component) templ.Component {
return layouts.Main(p, content)
}
return h.RenderPageTempl(ctx, p, component)
} }
func (h *Task) Submit(ctx echo.Context) error { func (h *Task) Submit(ctx echo.Context) error {
var input taskForm var input pages.TaskForm
err := form.Submit(ctx, &input) err := form.Submit(ctx, &input)

46
templ/pages/cache.templ Normal file
View File

@ -0,0 +1,46 @@
package pages
import (
"git.grosinger.net/tgrosinger/saasitone/templ/components"
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
)
type CacheForm struct {
Value string `form:"value"`
form.Submission
}
templ Cache(p page.Page, f *CacheForm, value *string) {
<form id="task" method="post" hx-post={ p.ToURL("cache.submit") }>
<article class="message">
<div class="message-header">
<p>Test the cache</p>
</div>
<div class="message-body">
This route handler shows how the default in-memory cache works. Try updating the value using the form below and see how it persists after you reload the page.
HTMX makes it easy to re-render the cached value after the form is submitted.
</div>
</article>
<label for="value" class="label">Value in cache: </label>
if value != nil {
<span class="tag is-success">{ *value }</span>
} else {
<i>(empty)</i>
}
<br/>
<br/>
<div class="field">
<label for="value" class="label">Value</label>
<div class="control">
<input id="value" name="value" class="input" value={ f.Value }/>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Update cache</button>
</div>
</div>
@components.CSRF(p.CSRF)
</form>
}

108
templ/pages/cache_templ.go Normal file
View File

@ -0,0 +1,108 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package pages
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/templ/components"
)
type CacheForm struct {
Value string `form:"value"`
form.Submission
}
func Cache(p page.Page, f *CacheForm, value *string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form id=\"task\" method=\"post\" hx-post=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(p.ToURL("cache.submit"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/cache.templ`, Line: 15, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><article class=\"message\"><div class=\"message-header\"><p>Test the cache</p></div><div class=\"message-body\">This route handler shows how the default in-memory cache works. Try updating the value using the form below and see how it persists after you reload the page. HTMX makes it easy to re-render the cached value after the form is submitted.</div></article><label for=\"value\" class=\"label\">Value in cache: </label> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if value != nil {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span class=\"tag is-success\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(*value)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/cache.templ`, Line: 27, Col: 40}
}
_, 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("</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<i>(empty)</i>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<br><br><div class=\"field\"><label for=\"value\" class=\"label\">Value</label><div class=\"control\"><input id=\"value\" name=\"value\" class=\"input\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(f.Value)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/cache.templ`, Line: 36, Col: 64}
}
_, 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("\"></div></div><div class=\"field is-grouped\"><div class=\"control\"><button class=\"button is-link\">Update cache</button></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.CSRF(p.CSRF).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

14
templ/pages/search.templ Normal file
View File

@ -0,0 +1,14 @@
package pages
import "git.grosinger.net/tgrosinger/saasitone/pkg/page"
type SearchResult struct {
Title string
URL string
}
templ Search(p page.Page, results []SearchResult) {
for _, result := range results {
<a class="panel-block" href={ templ.URL(result.URL) }>{ result.Title }</a>
}
}

View File

@ -0,0 +1,66 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package pages
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import "git.grosinger.net/tgrosinger/saasitone/pkg/page"
type SearchResult struct {
Title string
URL string
}
func Search(p page.Page, results []SearchResult) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
for _, result := range results {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"panel-block\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(result.URL)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(result.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/search.templ`, Line: 12, Col: 70}
}
_, 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("</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

55
templ/pages/task.templ Normal file
View File

@ -0,0 +1,55 @@
package pages
import (
"strconv"
"git.grosinger.net/tgrosinger/saasitone/templ/components"
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
)
type TaskForm struct {
Delay int `form:"delay" validate:"gte=0"`
Message string `form:"message" validate:"required"`
form.Submission
}
templ Task(p page.Page, f *TaskForm) {
if p.HTMX.Request.Target != "task" {
<article class="message is-link">
<div class="message-body">
<p>Submitting this form will create an <i>ExampleTask</i> in the task queue. After the specified delay, the message will be logged by the queue processor.</p>
<p>See pkg/tasks and the README for more information.</p>
</div>
</article>
}
@taskForm(p, f)
}
templ taskForm(p page.Page, f *TaskForm) {
<form id="task" method="post" hx-post={ p.ToURL("task.submit") }>
@components.Messages(p)
<div class="field">
<label for="delay" class="label">Delay (in seconds)</label>
<div class="control">
<input type="number" id="delay" name="delay" class={ "input", f.Submission.GetFieldStatusClass("Delay") } value={ strconv.Itoa(f.Delay) }/>
</div>
<p class="help">How long to wait until the task is executed</p>
@components.FieldErrors(f.Submission.GetFieldErrors("Delay"))
</div>
<div class="field">
<label for="message" class="label">Message</label>
<div class="control">
<textarea id="message" name="message" class={ "textarea", f.Submission.GetFieldStatusClass("Message") }>{ f.Message }</textarea>
</div>
<p class="help">The message the task will output to the log</p>
@components.FieldErrors(f.Submission.GetFieldErrors("Message"))
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Add task to queue</button>
</div>
</div>
@components.CSRF(p.CSRF)
</form>
}

194
templ/pages/task_templ.go Normal file
View File

@ -0,0 +1,194 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.707
package pages
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"strconv"
"git.grosinger.net/tgrosinger/saasitone/pkg/form"
"git.grosinger.net/tgrosinger/saasitone/pkg/page"
"git.grosinger.net/tgrosinger/saasitone/templ/components"
)
type TaskForm struct {
Delay int `form:"delay" validate:"gte=0"`
Message string `form:"message" validate:"required"`
form.Submission
}
func Task(p page.Page, f *TaskForm) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if p.HTMX.Request.Target != "task" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<article class=\"message is-link\"><div class=\"message-body\"><p>Submitting this form will create an <i>ExampleTask</i> in the task queue. After the specified delay, the message will be logged by the queue processor.</p><p>See pkg/tasks and the README for more information.</p></div></article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = taskForm(p, f).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func taskForm(p page.Page, f *TaskForm) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form id=\"task\" method=\"post\" hx-post=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(p.ToURL("task.submit"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/task.templ`, Line: 30, Col: 63}
}
_, 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("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.Messages(p).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"field\"><label for=\"delay\" class=\"label\">Delay (in seconds)</label><div class=\"control\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 = []any{"input", f.Submission.GetFieldStatusClass("Delay")}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"number\" id=\"delay\" name=\"delay\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var4).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/task.templ`, Line: 1, Col: 0}
}
_, 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("\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(f.Delay))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/task.templ`, Line: 35, Col: 139}
}
_, 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("\"></div><p class=\"help\">How long to wait until the task is executed</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.FieldErrors(f.Submission.GetFieldErrors("Delay")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"field\"><label for=\"message\" class=\"label\">Message</label><div class=\"control\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 = []any{"textarea", f.Submission.GetFieldStatusClass("Message")}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var7...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<textarea id=\"message\" name=\"message\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var7).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/task.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(f.Message)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templ/pages/task.templ`, Line: 43, Col: 119}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</textarea></div><p class=\"help\">The message the task will output to the log</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.FieldErrors(f.Submission.GetFieldErrors("Message")).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"field is-grouped\"><div class=\"control\"><button class=\"button is-link\">Add task to queue</button></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.CSRF(p.CSRF).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -1,36 +0,0 @@
{{define "content"}}
<form id="task" method="post" hx-post="{{url "cache.submit"}}">
<article class="message">
<div class="message-header">
<p>Test the cache</p>
</div>
<div class="message-body">
This route handler shows how the default in-memory cache works. Try updating the value using the form below and see how it persists after you reload the page.
HTMX makes it easy to re-render the cached value after the form is submitted.
</div>
</article>
<label for="value" class="label">Value in cache: </label>
{{if .Data}}
<span class="tag is-success">{{.Data}}</span>
{{- else}}
<i>(empty)</i>
{{- end}}
<br/><br/>
<div class="field">
<label for="value" class="label">Value</label>
<div class="control">
<input id="value" name="value" class="input" value="{{.Form.Value}}"/>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Update cache</button>
</div>
</div>
{{template "csrf" .}}
</form>
{{end}}

View File

@ -1,5 +0,0 @@
{{define "content"}}
{{- range .Data}}
<a class="panel-block" href="{{.URL}}">{{.Title}}</a>
{{- end}}
{{end}}

View File

@ -1,43 +0,0 @@
{{define "content"}}
{{- if not (eq .HTMX.Request.Target "task")}}
<article class="message is-link">
<div class="message-body">
<p>Submitting this form will create an <i>ExampleTask</i> in the task queue. After the specified delay, the message will be logged by the queue processor.</p>
<p>See pkg/tasks and the README for more information.</p>
</div>
</article>
{{- end}}
{{template "form" .}}
{{end}}
{{define "form"}}
<form id="task" method="post" hx-post="{{url "task.submit"}}">
{{template "messages" .}}
<div class="field">
<label for="delay" class="label">Delay (in seconds)</label>
<div class="control">
<input type="number" id="delay" name="delay" class="input {{.Form.GetFieldStatusClass "Delay"}}" value="{{.Form.Delay}}"/>
</div>
<p class="help">How long to wait until the task is executed</p>
{{template "field-errors" (.Form.GetFieldErrors "Delay")}}
</div>
<div class="field">
<label for="message" class="label">Message</label>
<div class="control">
<textarea id="message" name="message" class="textarea {{.Form.GetFieldStatusClass "Message"}}">{{.Form.Message}}</textarea>
</div>
<p class="help">The message the task will output to the log</p>
{{template "field-errors" (.Form.GetFieldErrors "Message")}}
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Add task to queue</button>
</div>
</div>
{{template "csrf" .}}
</form>
{{end}}

View File

@ -16,7 +16,6 @@ type (
const ( const (
LayoutMain Layout = "main" LayoutMain Layout = "main"
LayoutAuth Layout = "auth"
LayoutHTMX Layout = "htmx" LayoutHTMX Layout = "htmx"
) )