templates: Share fragments
This commit is contained in:
parent
dd7be3f520
commit
d64e6f8ec3
12 changed files with 111 additions and 93 deletions
|
|
@ -1,40 +1,32 @@
|
||||||
<!DOCTYPE html>
|
{{ template "_base.html" . }}
|
||||||
<head>
|
{{ define "title" }}WissKI Distillery{{ end }}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>WissKI Distillery</title>
|
|
||||||
{{ CSS }}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1>WissKI Distillery</h1>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<div class="pure-g">
|
|
||||||
<div class="pure-u-1">
|
|
||||||
<p>
|
|
||||||
For more information, see <a href="{{ .SelfRedirect }}">{{ .SelfRedirect }}</a>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-u-1">
|
|
||||||
<h2>WissKIs on this Distillery</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{range .Instances}}
|
|
||||||
{{ if .Running }}
|
|
||||||
<div class="pure-u-1-3">
|
|
||||||
<h3>{{.Slug}}</h3>
|
|
||||||
<p>
|
|
||||||
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.URL}}</a><br>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
{{ define "header/time" }}
|
||||||
Generated at <code>{{ .Time }}</code>
|
<!-- no header/time -->
|
||||||
</footer>
|
{{ end }}
|
||||||
|
{{ define "header"}}
|
||||||
{{ JS }}
|
<!-- no header -->
|
||||||
</body>
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "content" }}
|
||||||
|
<div class="pure-u-1">
|
||||||
|
<p>
|
||||||
|
For more information, see <a href="{{ .SelfRedirect }}">{{ .SelfRedirect }}</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pure-u-1">
|
||||||
|
<h2>WissKIs on this Distillery</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{range .Instances}}
|
||||||
|
{{ if .Running }}
|
||||||
|
<div class="pure-u-1-3">
|
||||||
|
<h3>{{.Slug}}</h3>
|
||||||
|
<p>
|
||||||
|
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.URL}}</a><br>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ func (home *Home) updateRender(ctx context.Context, io stream.IOStream) {
|
||||||
|
|
||||||
//go:embed "home.html"
|
//go:embed "home.html"
|
||||||
var homeHTMLStr string
|
var homeHTMLStr string
|
||||||
var homeTemplate = static.AssetsHomeHome.MustParse(nil, homeHTMLStr)
|
var homeTemplate = static.AssetsHomeHome.MustParseShared("home.html", homeHTMLStr)
|
||||||
|
|
||||||
func (home *Home) homeRender() ([]byte, error) {
|
func (home *Home) homeRender() ([]byte, error) {
|
||||||
var context HomeContext
|
var context HomeContext
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,19 @@
|
||||||
package info
|
package info
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
|
||||||
"html/template"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/lazy"
|
"github.com/FAU-CDI/wisski-distillery/pkg/lazy"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "html/base.html"
|
|
||||||
var baseTemplateString string
|
|
||||||
var baseTemplate = template.Must(template.New("base.html").Parse(baseTemplateString))
|
|
||||||
|
|
||||||
func base(name string) *template.Template {
|
|
||||||
clone := template.Must(baseTemplate.Clone())
|
|
||||||
clone.Tree.Name = name
|
|
||||||
return clone
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:embed "html/info_components.html"
|
//go:embed "html/info_components.html"
|
||||||
var componentsTemplateString string
|
var componentsTemplateString string
|
||||||
var componentsTemplate = static.AssetsComponentsIndex.MustParse(
|
var componentsTemplate = static.AssetsComponentsIndex.MustParseShared(
|
||||||
base("info_components.html"),
|
"info_components.html",
|
||||||
componentsTemplateString,
|
componentsTemplateString,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}Distillery Control Page - Components Page{{ end }}
|
{{ define "title" }}Distillery Control Page - Components Page{{ end }}
|
||||||
|
|
||||||
{{ define "head" }}{{ CSS }}{{ end }}
|
|
||||||
{{ define "footer" }}{{ JS }}{{ end }}
|
|
||||||
|
|
||||||
{{ define "header"}}
|
{{ define "header"}}
|
||||||
<p>
|
<p>
|
||||||
<a class="pure-button" href="/dis/index">Control</a> >
|
<a class="pure-button" href="/dis/index">Control</a> >
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}Distillery Control Page{{ end }}
|
{{ define "title" }}Distillery Control Page{{ end }}
|
||||||
|
|
||||||
{{ define "head" }}{{ CSS }}{{ end }}
|
|
||||||
{{ define "footer" }}{{ JS }}{{ end }}
|
|
||||||
|
|
||||||
{{ define "header"}}
|
{{ define "header"}}
|
||||||
<p>
|
<p>
|
||||||
<a class="pure-button pure-button-primary" href="/dis/index">Control</a>
|
<a class="pure-button pure-button-primary" href="/dis/index">Control</a>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}Distillery Control Page - {{ .Info.Slug }}{{ end }}
|
{{ define "title" }}Distillery Control Page - {{ .Info.Slug }}{{ end }}
|
||||||
|
|
||||||
{{ define "head" }}{{ CSS }}{{ end }}
|
|
||||||
{{ define "footer" }}{{ JS }}{{ end }}
|
|
||||||
|
|
||||||
{{ define "header"}}
|
{{ define "header"}}
|
||||||
<p>
|
<p>
|
||||||
<a class="pure-button" href="/dis/index">Control</a> >
|
<a class="pure-button" href="/dis/index">Control</a> >
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ import (
|
||||||
|
|
||||||
//go:embed "html/info_index.html"
|
//go:embed "html/info_index.html"
|
||||||
var indexTemplateStr string
|
var indexTemplateStr string
|
||||||
var indexTemplate = static.AssetsControlIndex.MustParse(
|
var indexTemplate = static.AssetsControlIndex.MustParseShared(
|
||||||
base("info_index.html"),
|
"info_index.html",
|
||||||
indexTemplateStr,
|
indexTemplateStr,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ import (
|
||||||
|
|
||||||
//go:embed "html/info_instance.html"
|
//go:embed "html/info_instance.html"
|
||||||
var instanceTemplateString string
|
var instanceTemplateString string
|
||||||
var instanceTemplate = static.AssetsControlInstance.MustParse(
|
var instanceTemplate = static.AssetsControlInstance.MustParseShared(
|
||||||
base("info_instance.html"),
|
"info_instance.html",
|
||||||
instanceTemplateString,
|
instanceTemplateString,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package static
|
package static
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -25,26 +24,25 @@ type Assets struct {
|
||||||
//go:generate node build.mjs HomeHome ComponentsIndex ControlIndex ControlInstance
|
//go:generate node build.mjs HomeHome ComponentsIndex ControlIndex ControlInstance
|
||||||
|
|
||||||
// MustParse parses a new template from the given source
|
// MustParse parses a new template from the given source
|
||||||
// and registers the Asset functions to it.
|
// and calls [RegisterAssoc] on it.
|
||||||
// See [Assets.RegisterFuncs].
|
|
||||||
func (assets *Assets) MustParse(t *template.Template, value string) *template.Template {
|
func (assets *Assets) MustParse(t *template.Template, value string) *template.Template {
|
||||||
if t == nil {
|
t = template.Must(t.Parse(value))
|
||||||
t = template.New("")
|
assets.RegisterAssoc(t)
|
||||||
}
|
return t
|
||||||
return template.Must(assets.RegisterFuncs(t).Parse(value))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterFuncs registers three new template functions called "JS", "CSS" and "json".
|
// MustParseShared is like [MustParse], but creates a new SharedTemplate instead
|
||||||
//
|
func (assets *Assets) MustParseShared(name string, value string) *template.Template {
|
||||||
// "JS" and "CSS" take no arguments, and return appropriate tags to be inserted into html.
|
return assets.MustParse(NewSharedTemplate(name), value)
|
||||||
// json takes a single argument of any type, and returns it's encoding as a string to be inserted into the page.
|
}
|
||||||
func (assets *Assets) RegisterFuncs(t *template.Template) *template.Template {
|
|
||||||
return t.Funcs(template.FuncMap{
|
// RegisterAssoc registers two new associated templates with t.
|
||||||
"JS": func() template.HTML { return template.HTML(assets.Scripts) },
|
//
|
||||||
"CSS": func() template.HTML { return template.HTML(assets.Styles) },
|
// The template "scripts" will render all script tags required.
|
||||||
"json": func(data any) (string, error) {
|
// The template "styles" will render all style tags required.
|
||||||
bytes, err := json.Marshal(data)
|
//
|
||||||
return string(bytes), err
|
// If either template already exists, it will be overwritten.
|
||||||
},
|
func (assets *Assets) RegisterAssoc(t *template.Template) {
|
||||||
})
|
t.New("scripts").Parse(assets.Scripts)
|
||||||
|
t.New("styles").Parse(assets.Styles)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
30
internal/dis/component/control/static/templates.go
Normal file
30
internal/dis/component/control/static/templates.go
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
package static
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"encoding/json"
|
||||||
|
"html/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed "templates/*.html"
|
||||||
|
var templates embed.FS
|
||||||
|
|
||||||
|
var (
|
||||||
|
shared *template.Template = template.Must(template.ParseFS(templates, "templates/*.html"))
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewSharedTemplate creates a new template with the given name.
|
||||||
|
// It will be able to make use of shared templates as well as functions.
|
||||||
|
func NewSharedTemplate(name string) *template.Template {
|
||||||
|
new := template.New(name)
|
||||||
|
new.Funcs(template.FuncMap{
|
||||||
|
"json": func(data any) (string, error) {
|
||||||
|
bytes, err := json.Marshal(data)
|
||||||
|
return string(bytes), err
|
||||||
|
},
|
||||||
|
})
|
||||||
|
for _, template := range shared.Templates() {
|
||||||
|
new.AddParseTree(template.Tree.Name, template.Tree.Copy())
|
||||||
|
}
|
||||||
|
return new
|
||||||
|
}
|
||||||
|
|
@ -3,14 +3,18 @@
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<title>{{ block "title" . }}Distillery Control Page{{ end }}</title>
|
<title>{{ block "block" . }}Distillery Control Page{{ end }}</title>
|
||||||
{{ block "head" . }}head{{ end }}
|
{{ block "styles" . }}styles{{ end }}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1 id="top">{{ template "title" . }}</h1>
|
<h1 id="top">{{ template "title" . }}</h1>
|
||||||
|
{{ block "header/time" . }}
|
||||||
|
{{ if .Time }}
|
||||||
<small>Generated at <code class="date">{{ .Time.Format "2006-01-02T15:04:05Z07:00" }}</code></small>
|
<small>Generated at <code class="date">{{ .Time.Format "2006-01-02T15:04:05Z07:00" }}</code></small>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
{{ block "header" . }}header{{ end }}
|
{{ block "header" . }}header{{ end }}
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
|
|
@ -19,8 +23,11 @@
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
{{ if .Time }}
|
||||||
<footer>
|
<footer>
|
||||||
Generated at <code>{{ .Time }}</code>
|
Generated at <code>{{ .Time }}</code>
|
||||||
</footer>
|
</footer>
|
||||||
{{ block "footer" . }}footer{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
{{ block "scripts" . }}scripts{{ end }}
|
||||||
</body>
|
</body>
|
||||||
|
|
@ -6,8 +6,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTMLHandler[T any] struct {
|
type HTMLHandler[T any] struct {
|
||||||
Handler func(r *http.Request) (T, error)
|
Handler func(r *http.Request) (T, error)
|
||||||
Template *template.Template // called with T
|
|
||||||
|
Template *template.Template // called with T
|
||||||
|
TemplateName string // name of template to render, defaults to root
|
||||||
}
|
}
|
||||||
|
|
||||||
var htmlInternalServerErr = []byte(`<!DOCTYPE HTML><title>Internal Server Error</title>Internal Server Error`)
|
var htmlInternalServerErr = []byte(`<!DOCTYPE HTML><title>Internal Server Error</title>Internal Server Error`)
|
||||||
|
|
@ -37,5 +39,10 @@ func (h HTMLHandler[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
// write out the response as json
|
// write out the response as json
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
h.Template.Execute(w, result)
|
|
||||||
|
if h.TemplateName != "" {
|
||||||
|
h.Template.ExecuteTemplate(w, h.TemplateName, result)
|
||||||
|
} else {
|
||||||
|
h.Template.Execute(w, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue