rastrillo / fiesta Public

Clone
git clone https://amadan.net/rastrillo/fiesta

Plain git — no account needed to clone.

Download

Download this file

1package gallery
2
3import (
4 "fmt"
5 "strings"
6
7 "amadan.net/rastrillo/fiesta/example"
8)
9
10// An example's page and its frame documents. frameFiles names the
11// documents and example writes them, under exactly those names, which
12// is how the tree gate, the frame count and the accessibility scan
13// learn of a new document the day a later task adds one here.
14
15// frameFiles is the paths, under the theme and locale directory, of
16// one example's frame documents: one per state, and for a grouped
17// example the row document its one widget frames as well. The
18// per-state documents of a grouped example are still written, because
19// the accessibility gate scans one state at a time.
20func (s *site) frameFiles(e example.Entry) []string {
21 var out []string
22 for _, st := range e.States {
23 out = append(out, "frames/"+e.Name+"/"+st.ID+".html")
24 }
25 if grouped[e.Name] {
26 out = append(out, "frames/"+e.Name+"/row.html")
27 }
28 return out
29}
30
31// stateView is one state on an example's page.
32type stateView struct {
33 ID string
34 Label string
35 Note string
36 Warning *warning
37 Preview previewView
38 Routes string
39}
40
41// groupNote is one state under a grouped widget: its id, its label on
42// a line of its own, and its note where the prose has one. The two
43// are separate elements rather than one string joined with ": ",
44// because that joiner is copy no review saw, and an ASCII colon is
45// wrong punctuation in ja, zh-Hans and yue.
46type groupNote struct {
47 ID, State, Note string
48}
49
50// exampleBody is one example's page: its family as a kicker, its own
51// sentence, the two sentences every example page says, then every
52// state as a frame of its own document with the note on the state
53// under its label, the warning where the prose has one, the widget,
54// and the routes it stands for; or, for a grouped example, one widget
55// and a block per state under it. The three primitives that are also
56// pages of their own end with the link to that page, and a layout's
57// page with the button that opens the shell's document, each in a tab
58// of its own.
59const exampleBody = `{{define "ds-body-example"}}<p class="ds-kicker"><b>{{.Family.Title}}</b> {{.Family.Blurb}}</p>
60{{if .Blurb}}<p rst-page-header-sub>{{.Blurb}}</p>
61{{end}}<p class="ds-lead">{{say "gallery.example.live"}} {{say "gallery.example.html"}}</p>
62{{with .Group}}<section class="ds-sample ds-sample--rows" id="{{$.Entry.Name}}" data-ds-anchor>
63{{template "ds-view" .}}
64{{range $.Notes}}<div class="ds-group-note" id="{{.ID}}" data-ds-anchor data-gallery-state="{{$.Entry.Name}}/{{.ID}}"><b class="ds-note__state">{{.State}}</b>
65{{if .Note}}<p class="ds-note">{{.Note}}</p>
66{{end}}</div>
67{{end}}</section>
68{{else}}{{range .States}}<section class="ds-sample" id="{{.ID}}" data-ds-anchor data-gallery-state="{{$.Entry.Name}}/{{.ID}}">
69<h2 class="ds-state">{{.Label}}</h2>
70{{if .Note}}<p class="ds-note">{{.Note}}</p>
71{{end}}{{with .Warning}}<div rst-callout rst-tone="warning"><span rst-callout-ic aria-hidden="true">{{icon "alert-triangle"}}</span><div rst-callout-body><strong>{{.Title}}</strong><p>{{.Body}}</p></div></div>
72{{end}}{{template "ds-view" .Preview}}
73{{if .Routes}}<p class="ds-routes-note">{{.Routes}}</p>
74{{end}}</section>
75{{end}}{{end}}{{with .Live}}<p class="ds-note"><a data-gallery-nav href="{{.Href}}" target="_blank" rel="noopener">{{.Label}}<span class="rst-sr-only"> ({{say "gallery.newtab"}})</span></a></p>
76{{end}}{{if .Shell}}<p><a rst-btn data-gallery-nav href="{{.ShellHref}}" target="_blank" rel="noopener">{{say "gallery.shell.open" "shell" .Shell}}<span class="rst-sr-only"> ({{say "gallery.newtab"}})</span></a></p>
77{{end}}{{end}}`
78
79// demoIdiom is one primitive that is also a page of the tree: the
80// words its link wears, by copy id, and the address it goes to.
81type demoIdiom struct {
82 Label string
83 Href func(s *site, theme, locale string) string
84}
85
86// demoIdioms are the three primitives whose preview is not the last
87// word on them. The two shells are whole page frames, better seen at
88// the size of a window than of a paragraph, so each links its layout's
89// document; the modal's whole claim is that it is a URL, so the
90// demonstration is not complete until you have been to it. Rastrillo's
91// three, under Rastrillo's name.
92var demoIdioms = map[string]demoIdiom{
93 "shell-topbar": {"gallery.primitives.topbar.live", func(s *site, theme, locale string) string { return s.layoutHref(theme, locale, "layout-topbar") }},
94 "shell-sidebar": {"gallery.primitives.sidebar.live", func(s *site, theme, locale string) string { return s.layoutHref(theme, locale, "layout-sidebar") }},
95 "modal": {"gallery.primitives.modal.live", (*site).modalHref},
96}
97
98// layoutHref is a layout example's document opened whole: its default
99// state's frame document.
100func (s *site) layoutHref(theme, locale, name string) string {
101 state, ok := s.m.Default(name)
102 if !ok {
103 // The layouts are frozen in examples/ and named in code; a
104 // manifest without one is a programming error, as a kind
105 // fileOf does not know is.
106 panic("gallery: the manifest has no " + name + " example")
107 }
108 return s.href(theme, locale, "frames/"+name+"/"+state+".html")
109}
110
111// shellOf is the shell a layout example documents, by the name its
112// open button says: column, topbar, sidebar, console or stage. The
113// -page members of the sidebar and console pairs are the same shell's
114// content page and get no button; a component is no shell at all.
115func shellOf(e example.Entry) string {
116 if !e.Document || strings.HasSuffix(e.Name, "-page") {
117 return ""
118 }
119 return strings.TrimPrefix(e.Name, "layout-")
120}
121
122// example renders one example's page body and its frame documents,
123// keyed by the paths frameFiles names.
124func (s *site) example(p page, e example.Entry) (body []byte, frames map[string][]byte, err error) {
125 ex := s.prose.Examples[e.Name]
126 frames = map[string][]byte{}
127 data := struct {
128 page
129 Family family
130 Entry example.Entry
131 Blurb string
132 States []stateView
133 Group *previewView
134 Notes []groupNote
135 Shell string
136 ShellHref string
137 Live *link
138 }{page: p, Entry: e, Blurb: s.proseIn(p.Locale, "examples."+e.Name+".blurb", ex.Blurb)}
139 f, _ := s.familyByKey(e.Family)
140 data.Family = s.familyIn(p.Locale, f)
141 for _, st := range e.States {
142 fr, code, err := s.localSample(p.Theme, p.Locale, e, st)
143 if err != nil {
144 return nil, nil, fmt.Errorf("%s/%s: %w", e.Name, st.ID, err)
145 }
146 rel := "frames/" + e.Name + "/" + st.ID + ".html"
147 frames[rel] = []byte(fr)
148 note := s.proseIn(p.Locale, "examples."+e.Name+".states."+st.ID, ex.States[st.ID])
149 if grouped[e.Name] {
150 data.Notes = append(data.Notes, groupNote{ID: st.ID, State: s.label(p.Locale, e, st), Note: note})
151 continue
152 }
153 view, err := s.preview(p, e, st, s.href(p.Theme, p.Locale, rel), code)
154 if err != nil {
155 return nil, nil, fmt.Errorf("%s/%s: %w", e.Name, st.ID, err)
156 }
157 sv := stateView{ID: st.ID, Label: s.label(p.Locale, e, st), Note: note, Preview: view}
158 if len(st.SampleURLs) > 0 {
159 sv.Routes = s.say(p.Locale, "gallery.routes", "routes", strings.Join(st.SampleURLs, ", "))
160 }
161 if w, ok := ex.Warnings[st.ID]; ok {
162 slot := "examples." + e.Name + ".warnings." + st.ID
163 sv.Warning = &warning{
164 Title: s.proseIn(p.Locale, slot+".title", w.Title),
165 Body: s.proseIn(p.Locale, slot+".body", w.Body),
166 }
167 }
168 data.States = append(data.States, sv)
169 }
170 if grouped[e.Name] {
171 view, rowDoc, err := s.groupedPreview(p, e)
172 if err != nil {
173 return nil, nil, err
174 }
175 fr, err := s.frame(rowDoc, p.Theme, p.Locale, e, "row")
176 if err != nil {
177 return nil, nil, fmt.Errorf("%s/row: %w", e.Name, err)
178 }
179 frames["frames/"+e.Name+"/row.html"] = []byte(fr)
180 data.Group = &view
181 }
182 if d, ok := demoIdioms[e.Name]; ok {
183 data.Live = &link{Href: d.Href(s, p.Theme, p.Locale), Label: s.say(p.Locale, d.Label)}
184 }
185 if data.Shell = shellOf(e); data.Shell != "" {
186 data.ShellHref = s.href(p.Theme, p.Locale, "frames/"+e.Name+"/"+e.Default+".html")
187 }
188 out, err := s.body(p.Locale, "ds-body-example", data)
189 if err != nil {
190 return nil, nil, err
191 }
192 return []byte(out), frames, nil
193}
194
195func init() { registerTemplate(exampleBody) }
196