oficina / home Public

Clone
git clone https://amadan.net/oficina/home

Plain git — no account needed to clone.

Download

Download this file

1package home
2
3import (
4 "html/template"
5
6 "amadan.net/oficina/estilo.git/sky"
7 "bytes"
8 "embed"
9 "net/http"
10 "sync"
11
12 "amadan.net/oficina/estilo.git/theme"
13 "github.com/go-chi/chi/v5"
14)
15
16//go:embed static/site
17var siteFS embed.FS
18
19// The front page is a real page now, not the skeleton's bare fact. It is
20// authored as a file rather than a Go string because it is long enough
21// that a const would hide its diffs, and it has no dynamic content and no
22// user input to escape — so it is served as bytes, with no template step
23// standing between the file and the response.
24//
25// It is drawn in Calma, the suite's look: estilo's theme and Onest, the
26// sky behind the hero, and each app's own colour from estilo's ring
27// (appsCSS). Everything it links is on this origin; SecurityPolicy's
28// `default-src 'self'` already admits the fonts and pictures, and the
29// stylesheets are covered by `style-src 'self'`.
30//
31// Everything the page points at has to exist. The app links go to the six
32// live app hosts; there is no sign-up form, because there is no route that
33// would accept one (provisioning is still `oficina provision`, an operator
34// CLI). A form posting into a 404 is worse than saying so plainly, and the
35// page says so plainly.
36var indexPage = siteAsset("index.html")
37
38func siteAsset(name string) []byte {
39 b, err := siteFS.ReadFile("static/site/" + name)
40 if err != nil {
41 panic(err) // Embedded above; unreachable unless the build is broken.
42 }
43 return b
44}
45
46// appsCSS gives each app on the page its own accent: estilo's per-app
47// accent blocks, scoped from :root to [data-app="<slug>"], so a card for
48// Sheets tints green from the same ring the Sheets app itself uses, with
49// no colour copied here to drift.
50var appsCSS = func() []byte {
51 var b bytes.Buffer
52 b.WriteString("/* Each app's accent, from estilo's colour ring (theme.AccentCSS). */\n")
53 for _, a := range theme.Apps() {
54 block, _ := theme.AccentCSS(a.Slug)
55 b.Write(bytes.Replace(block, []byte(":root {"), []byte(`[data-app="`+a.Slug+`"] {`), 1))
56 }
57 return b.Bytes()
58}()
59
60// Public on both Home origins, and a closed list for the same reason
61// mountBrand keeps one: a request path must never be treated as a file
62// name.
63func mountSite(r chi.Router) {
64 mountCSS(r, "/static/site/home.css", siteAsset("home.css"))
65 mountCSS(r, "/static/site/apps.css", appsCSS)
66}
67
68// skyMarker is where the front page's sky goes: rendered from estilo's
69// own template, so the page carries exactly the layers (and the inline
70// placeholder) the pinned estilo draws, never a hand-copied set of URLs.
71const skyMarker = "<!-- estilo-sky: the suite's sky, rendered from estilo's template on first serve (page.go) -->"
72
73func withSky(page []byte) []byte {
74 t := template.Must(template.ParseFS(sky.Templates(), "templates/*.html"))
75 var b bytes.Buffer
76 if err := t.ExecuteTemplate(&b, "estilo-sky", sky.For(sky.SuiteSeed)); err != nil {
77 panic(err) // estilo's own template over its own data; a broken pin
78 }
79 return bytes.Replace(page, []byte(skyMarker), b.Bytes(), 1)
80}
81
82// servedIndex is the front page with every stylesheet and picture linked
83// by its content version. Built on first serve, once the routers have
84// mounted (and so versioned) everything the page links.
85var (
86 servedIndexOnce sync.Once
87 servedIndex []byte
88)
89
90// handleIndex serves the front page.
91func (a *App) handleIndex(w http.ResponseWriter, _ *http.Request) {
92 servedIndexOnce.Do(func() { servedIndex = versionStaticRefs(withSky(indexPage)) })
93 w.Header().Set("Content-Type", "text/html; charset=utf-8")
94 w.Write(servedIndex)
95}
96