package gallery import ( "fmt" "html/template" "sort" "strings" "amadan.net/rastrillo/fiesta" "amadan.net/rastrillo/fiesta/internal/build" "amadan.net/rastrillo/fiesta/internal/codeview" ) // Getting started: what ships and what it weighs, read off the release // build while the page renders. Every weight on the page is weigh() of // the bytes build.Dist returns for fiesta.Version; no figure is written // in copy.go or here, so a page about file sizes stays true after // someone edits a file. Each row links its file where the site serves // it, under ReleaseBase, marked data-gallery-site: the one link form // internal/www resolves through its redirect to the newest release. // // Rastrillo's page lists what a scaffolded app is handed; this one // lists the release, so the rows and the total are Fiesta's own, while // the view types, the rail reader and the weight line keep Rastrillo's // names. // assetView is one shipped file: what it is called, where the site // serves it, what it weighs (the finished line in the page's language) // and what it is for. type assetView struct{ ID, Name, Href, Weight, Blurb string } // assetsView is the page's data: the rows in page order, the total a // page that links fiesta.css and every script pays, and the two code // blocks, highlighted: the one link, and the two-file form for a site // serving its own copy. type assetsView struct { List []assetView Total string Link, Own template.HTML } // scriptOrder is the order the scripts are listed in: fiesta.js first // and calendar.js last, as the page says to load them, the rest as the // scripts paragraph names them. That is the order for the deferred // scripts; transition.js's row says it loads before the stylesheets. // A script in the release and not here fails the render by name, so a // new script cannot ship off this page. var scriptOrder = []string{"fiesta.js", "busy.js", "light.js", "anchor.js", "shell.js", "transition.js", "copy.js", "select.js", "datetime.js", "calendar.js"} // blurbs is the copy id of each shipped file whose sentence has no // placeholder. The one-file themes, the themes and the strings take // theirs with a theme or a language written in; see buildAssets. var blurbs = map[string]string{ "tokens.css": "gallery.started.file.tokens", "shell.css": "gallery.started.file.shell-css", "fiesta.js": "gallery.started.file.fiesta-js", "busy.js": "gallery.started.file.busy-js", "light.js": "gallery.started.file.light-js", "anchor.js": "gallery.started.file.anchor-js", "shell.js": "gallery.started.file.shell-js", "transition.js": "gallery.started.file.transition-js", "copy.js": "gallery.started.file.copy-js", "select.js": "gallery.started.file.select-js", "datetime.js": "gallery.started.file.datetime-js", "calendar.js": "gallery.started.file.calendar-js", "icons.svg": "gallery.started.file.sprite", } // release is the release build of one site's source, reduced to what // the page needs: every file's weight, by name. type release map[string]weight // weighRelease builds the release from s's source and weighs every // file in it. newSite calls it once and keeps the result on the site: // the rail asks assetNav for the rows on every page of the tree, and a // release build and forty gzips a page would be most of the render. func (s *site) weighRelease() (release, error) { dist, err := build.Dist(s.src, fiesta.Version) if err != nil { return nil, err } rel := make(release, len(dist)) for name, body := range dist { rel[name] = weigh(body) } return rel, nil } // buildAssets reads the release build for p's page: the rows in the // order the spec lists them (the one-file themes, fiesta.css first; // tokens.css; each theme; shell.css; the scripts; the sprite; the // strings, one per language), each weighed from the bytes the release // holds. Every file the release ships is a row, or one of the files the // page leaves out by design (LICENSE, NOTICE, SHA256SUMS, the per-icon // SVGs the Icons page draws, the JSON the strings scripts wrap), or an // error naming it. func (s *site) buildAssets(p page) (assetsView, error) { rel := s.weighed var out assetsView listed := map[string]bool{} add := func(name, blurb string) error { w, ok := rel[name] if !ok { return fmt.Errorf("gallery: Getting started lists %s and the release has no such file", name) } listed[name] = true out.List = append(out.List, assetView{ ID: anchorID("asset", name), Name: name, Href: ReleaseBase + name, Weight: s.weightIn(p.Locale, w), Blurb: blurb, }) return nil } // The one-file themes. The default theme's is fiesta.css: the one // theme with no fiesta-.css of its own. themes := fiesta.ThemeNames() def := "" for _, theme := range themes { if _, ok := rel["fiesta-"+theme+".css"]; !ok { if def != "" { return assetsView{}, fmt.Errorf("gallery: both %s and %s lack a one-file theme of their own", def, theme) } def = theme } } if def == "" { return assetsView{}, fmt.Errorf("gallery: no theme is fiesta.css") } if err := add("fiesta.css", s.say(p.Locale, "gallery.started.file.bundle", "theme", def)); err != nil { return assetsView{}, err } for _, theme := range themes { if theme == def { continue } if err := add("fiesta-"+theme+".css", s.say(p.Locale, "gallery.started.file.bundle", "theme", theme)); err != nil { return assetsView{}, err } } if err := add("tokens.css", s.say(p.Locale, blurbs["tokens.css"])); err != nil { return assetsView{}, err } for _, theme := range themes { if err := add(theme+".css", s.say(p.Locale, "gallery.started.file.theme", "theme", theme)); err != nil { return assetsView{}, err } } for _, name := range append([]string{"shell.css"}, scriptOrder...) { if err := add(name, s.say(p.Locale, blurbs[name])); err != nil { return assetsView{}, err } } if err := add("icons.svg", s.say(p.Locale, blurbs["icons.svg"])); err != nil { return assetsView{}, err } var langs []string for name := range rel { if strings.HasPrefix(name, "i18n/") && strings.HasSuffix(name, ".js") { langs = append(langs, strings.TrimSuffix(strings.TrimPrefix(name, "i18n/"), ".js")) } } sort.Strings(langs) for _, lang := range langs { // Each language named in itself, as the language menu names it. if err := add("i18n/"+lang+".js", s.say(p.Locale, "gallery.started.file.strings", "language", s.say(lang, "gallery.locale.name"))); err != nil { return assetsView{}, err } } for name := range rel { switch { case listed[name], name == "LICENSE", name == "NOTICE", name == "SHA256SUMS", strings.HasPrefix(name, "icons/"), strings.HasPrefix(name, "i18n/") && strings.HasSuffix(name, ".json"): continue } return assetsView{}, fmt.Errorf("gallery: the release ships %s and Getting started has no row for it", name) } // The total: fiesta.css and every script, each weighed on its own, // the way a visitor downloads them. raw, gz := 0, 0 for _, name := range append([]string{"fiesta.css"}, scriptOrder...) { raw, gz = raw+rel[name].Raw, gz+rel[name].Gzip } out.Total = s.say(p.Locale, "gallery.started.total", "raw", kibUnit(p.Locale, raw), "gzip", kibUnit(p.Locale, gz)) out.Link = codeview.Highlight(``) out.Own = codeview.Highlight(`` + "\n" + ``) return out, nil } // kibUnit is kib with the unit after it, for a sentence that stands on // its own rather than under a note saying the unit. KiB is the unit's // own symbol, written the same in every language. func kibUnit(locale string, n int) string { return kib(locale, n) + " KiB" } // assetNav is one rail entry per shipped file, in the order the page // lists them, derived from the same view the page renders from, so a // file joins both lists on the day it ships, or neither. A release the // view cannot be built from is reported by startedBody, which fails // the render; the empty rail returned here is never written. func (s *site) assetNav(p page) []navItem { view, err := s.buildAssets(p) if err != nil { return nil } file := s.href(p.Theme, p.Locale, s.fileOf("getting-started")) var items []navItem for _, a := range view.List { items = append(items, navItem{Label: a.Name, Href: file + "#" + a.ID, Code: true}) } return items } // startedBody is the page a reader meets before any of the vocabulary: // what ships, what each file is for and what it weighs, then the one // link, the two-file form for a site serving its own copy, and how an // upgrade reaches a page. The code blocks are translate="no": the // address in the first is a URL the site's link gate resolves like any // other, and neither is prose. const startedBody = `{{define "ds-body-getting-started"}}

{{say "gallery.started.lead"}}

{{say "gallery.started.stylesheets"}}

{{say "gallery.started.stylesheets.body"}}

{{say "gallery.started.scripts"}}

{{say "gallery.started.scripts.body"}}

{{say "gallery.started.weights"}}

{{say "gallery.started.weights.note"}}

{{.Assets.Total}}

{{say "gallery.started.link.heading"}}

{{say "gallery.started.link.body"}}

{{.Assets.Link}}

{{say "gallery.started.own.heading"}}

{{say "gallery.started.own.body"}}

{{.Assets.Own}}

{{say "gallery.started.upgrading"}}

{{say "gallery.started.upgrading.body"}}

{{end}}` // startedBody renders the Getting started page's body for p. func (s *site) startedBody(p page) (template.HTML, error) { assets, err := s.buildAssets(p) if err != nil { return "", err } return s.body(p.Locale, "ds-body-getting-started", struct { page Assets assetsView }{p, assets}) } func init() { registerTemplate(startedBody) }