package gallery
import (
"fmt"
"html/template"
"strings"
)
// The page kinds: one page per section, each at its own URL under
// //. The table pageKinds returns is the whole of that
// seam: a row in it is a page in the tree, a section of the rail, a
// row on the phone index, a step in the previous/next sequence, a
// route off the Overview and a file TestTreeShapeIsComplete expects,
// with nothing else to remember. The families come after the three
// fixed rows, read off the manifest, so a family added to examples/
// is a page the day it lands.
// ReleaseBase is where the site serves the newest 1.x release's files.
// A link to a shipped file starts with it and carries data-gallery-site;
// internal/www holds it to its own origin.
const ReleaseBase = "https://fiesta.rastrillo.org/1/"
// familyOrder is the order the families are read in: the gallery's
// own, as the spec gives it, and not the order the manifest happens to
// name them in. It is the family rows of pageKinds, the sections of
// the rail and the walk of the sequence, which ends on the last shell.
// A family in the manifest and not here fails the render by name.
var familyOrder = []string{"list-screen", "display", "form", "choices-and-actions", "date-and-time", "route", "primitives", "formats", "screens", "shells"}
// inFamilyOrder says whether familyOrder places key.
func inFamilyOrder(key string) bool {
for _, k := range familyOrder {
if k == key {
return true
}
}
return false
}
// pageKind is one row of the table. Title and Blurb are ids: a
// gallery.* id into text, or a prose slot id (families..title);
// textOf resolves either. Blurb is the one sentence the Overview says
// when it routes a reader to the page, and is empty on the Overview,
// which never routes to itself. Nav reads the page's rail entries off
// the finished page, nil where it anchors nothing. Body renders the
// page's own markup.
type pageKind struct {
Kind, File, Title, Blurb string
Nav func(p page) []navItem
Body func(p page) (template.HTML, error)
}
// pageKinds is the table, in page order: the Overview, Getting
// started, Tokens, Icons, then one row per family in the gallery's
// family order.
func (s *site) pageKinds() []pageKind {
kinds := []pageKind{
{Kind: "overview", File: "index.html", Title: "gallery.kind.overview", Body: s.overviewBody},
{Kind: "getting-started", File: "getting-started.html", Title: "gallery.kind.getting-started", Blurb: "gallery.routes.getting-started", Nav: s.assetNav, Body: s.startedBody},
{Kind: "tokens", File: "tokens.html", Title: "gallery.kind.tokens", Blurb: "gallery.route.tokens", Nav: s.tokenNav, Body: s.tokensBody},
{Kind: "icons", File: "icons.html", Title: "gallery.kind.icons", Blurb: "gallery.routes.icons", Nav: s.iconNav, Body: s.iconsBody},
}
for _, f := range s.families {
kinds = append(kinds, pageKind{
Kind: f.Key,
File: f.Key + ".html",
Title: "families." + f.Key + ".title",
Blurb: "families." + f.Key + ".blurb",
Nav: s.familyNav(f.Key),
Body: s.familyBody,
})
}
return kinds
}
// fileOf is one page kind's file. It panics on a kind that is not in
// the table, since every caller passes a Kind read out of the table.
func (s *site) fileOf(kind string) string {
for _, pk := range s.pageKinds() {
if pk.Kind == kind {
return pk.File
}
}
panic("gallery: no page kind " + kind)
}
// href is the address of one file of one theme and locale directory.
// Every link the renderer writes into the tree is built here, so the
// mount is spelled once.
func (s *site) href(theme, locale, file string) string {
return s.url(theme + "/" + locale + "/" + file)
}
// titleOf is one page kind's title in locale.
func (s *site) titleOf(locale string, pk pageKind) string { return s.textOf(locale, pk.Title) }
// blurbOf is one page kind's route blurb in locale, or "" where it has
// none.
func (s *site) blurbOf(locale string, pk pageKind) string {
if pk.Blurb == "" {
return ""
}
return s.textOf(locale, pk.Blurb, "total", fmt.Sprint(len(s.icons)))
}
// sequence is the order the pages are read in: the table's rows, and
// inside each family its examples in manifest order after the family's
// own page. Each step is the page's address and the label the
// previous/next pair names it by: a kind's title, an example's name.
func (s *site) sequence(theme, locale string) []link {
var out []link
for _, pk := range s.pageKinds() {
out = append(out, link{Href: s.href(theme, locale, pk.File), Label: s.titleOf(locale, pk)})
for _, f := range s.families {
if f.Key != pk.Kind {
continue
}
for _, e := range f.Examples {
out = append(out, link{Href: s.href(theme, locale, e.Name+".html"), Label: e.Name})
}
}
}
return out
}
// steps is the pair of links at the foot of one page: the page before
// it and the page after it in the sequence. Either is nil at the ends.
func (s *site) steps(theme, locale, file string) (prev, next *link) {
seq := s.sequence(theme, locale)
at := -1
for i, step := range seq {
if step.Href == s.href(theme, locale, file) {
at = i
break
}
}
if at < 0 {
panic("gallery: " + file + " is not in the sequence")
}
step := func(i int, id string) *link {
if i < 0 || i >= len(seq) {
return nil
}
return &link{Href: seq[i].Href, Label: s.say(locale, id, "page", seq[i].Label)}
}
return step(at-1, "gallery.step.prev"), step(at+1, "gallery.step.next")
}
// route is one entry in the Overview's route list: another page of the
// gallery, its name, and the one sentence saying what is on it.
type route struct{ Label, Blurb, Href string }
// routes is the Overview's way into the rest of the gallery: one per
// page kind that is not kind, in table order, each with its row's
// blurb.
func (s *site) routes(theme, locale, kind string) []route {
var out []route
for _, pk := range s.pageKinds() {
if pk.Kind == kind {
continue
}
out = append(out, route{Label: s.titleOf(locale, pk), Blurb: s.blurbOf(locale, pk), Href: s.href(theme, locale, pk.File)})
}
return out
}
// anchorID is the id one anchorable thing on a page wears, and the
// fragment the rail links it by: built from the English name, so the
// twelve translations of a page carry the same fragments, and prefixed
// by kind, so a token group and an icon of the same name are two ids.
func anchorID(kind, name string) string { return kind + "-" + slug(name) }
// slug lowercases and hyphenates: ASCII letters and digits survive,
// everything else becomes one hyphen, and the ends are trimmed. The
// inputs are English headings and identifiers already in this
// alphabet, so there is no transliteration to do.
func slug(s string) string {
var b strings.Builder
dash := false
for _, r := range strings.ToLower(s) {
switch {
case r >= 'a' && r <= 'z', r >= '0' && r <= '9':
b.WriteRune(r)
dash = false
default:
if b.Len() > 0 && !dash {
b.WriteByte('-')
dash = true
}
}
}
return strings.TrimSuffix(b.String(), "-")
}