| 1 |
|
package web |
| 2 |
|
|
| 3 |
|
import ( |
| 4 |
|
"context" |
| 5 |
|
"net/url" |
| 6 |
|
|
| 7 |
|
"sourcecraft.dev/bigbes/sr-ht-dolt/browse" |
| 8 |
|
"sourcecraft.dev/bigbes/sr-ht-dolt/core" |
| 9 |
|
) |
| 10 |
|
|
| 11 |
|
// View is a specialized, read-only rendering of a repository whose table shape |
| 12 |
|
// it recognizes. Views are registered at init time via RegisterView and chosen |
| 13 |
|
// per-repo by Applies. The generic table browser is always available too, so a |
| 14 |
|
// View never has to be exhaustive. |
| 15 |
|
// |
| 16 |
|
// A View pulls all of its data through the BrowseSession surface only (Tables, |
| 17 |
|
// Rows, Branches, Log, ...); there is no SQL engine behind a bare store. Build |
| 18 |
|
// returns an opaque value handed to the view's Template as its .Data field. |
| 19 |
|
type View interface { |
| 20 |
|
Name() string // URL slug, e.g. "beads"; must be a valid path segment, unique |
| 21 |
|
Label() string // human tab label, e.g. "Beads" |
| 22 |
|
Template() string // page template filename in templates/, e.g. "beads.html" |
| 23 |
|
Applies(tables []browse.TableInfo) bool // fingerprint by table names/columns |
| 24 |
|
// Build produces the opaque .Data value for the view's Template. query is the |
| 25 |
|
// request URL's query string, so a view can offer sub-modes (e.g. beads' |
| 26 |
|
// ?issue=<id> detail pane) without a new route. |
| 27 |
|
Build(ctx context.Context, sess BrowseSession, repo *core.Repo, ref string, query url.Values) (any, error) |
| 28 |
|
} |
| 29 |
|
|
| 30 |
|
// registeredViews is the package-global registry populated at init time by |
| 31 |
|
// RegisterView. It is read once per process: Register snapshots it into |
| 32 |
|
// app.views so handlers (and tests, which set app.views directly) never touch |
| 33 |
|
// the global at request time. |
| 34 |
|
// |
| 35 |
|
// Nothing here parses a view's Template() any more: pages discovers every page |
| 36 |
|
// in templates/, so a view's page is registered by its file existing. A view |
| 37 |
|
// whose Template() names no such file renders as a 500 with a log line, which |
| 38 |
|
// is what it is. |
| 39 |
|
var registeredViews []View |
| 40 |
|
|
| 41 |
|
// init registers every view this service ships, in the order their tabs appear. |
| 42 |
|
// |
| 43 |
|
// The registration lives here, in one list, rather than in an init() beside each |
| 44 |
|
// concrete view — which is where it used to be, and which made the tab order a |
| 45 |
|
// consequence of file names. A package's init functions run in lexical file |
| 46 |
|
// order, so "memory.go" registering itself would have put Memory between Beads |
| 47 |
|
// and Milestones, and the only way to restore the intended order from inside |
| 48 |
|
// memory.go was to register a view belonging to another file first. Tab order is |
| 49 |
|
// a decision, and a decision that reads as a list of three lines cannot be |
| 50 |
|
// changed by renaming a file. |
| 51 |
1 |
func init() { |
| 52 |
1 |
RegisterView(&beadsView{}) |
| 53 |
1 |
RegisterView(&milestonesView{}) |
| 54 |
1 |
RegisterView(&memoryView{}) |
| 55 |
1 |
} |
| 56 |
|
|
| 57 |
|
// RegisterView adds v to the global registry, appending it after everything |
| 58 |
|
// registered before it: registration order is tab order. If a view with the same |
| 59 |
|
// Name() is already registered it is replaced in place, so re-registering (a |
| 60 |
|
// test swapping an implementation, say) never reorders the bar. |
| 61 |
3 |
func RegisterView(v View) { |
| 62 |
3 |
for i, existing := range registeredViews { |
| 63 |
3 |
if existing.Name() == v.Name() { |
| 64 |
0 |
registeredViews[i] = v |
| 65 |
0 |
return |
| 66 |
0 |
} |
| 67 |
|
} |
| 68 |
3 |
registeredViews = append(registeredViews, v) |
| 69 |
|
} |
| 70 |
|
|
| 71 |
|
// applicableViews returns the subset of views whose Applies reports true for |
| 72 |
|
// tables, preserving registration order. It is pure: handlers pass their own |
| 73 |
|
// snapshot (app.views) so the result is deterministic and testable. |
| 74 |
73 |
func applicableViews(views []View, tables []browse.TableInfo) []View { |
| 75 |
73 |
out := make([]View, 0, len(views)) |
| 76 |
107 |
for _, v := range views { |
| 77 |
107 |
if v.Applies(tables) { |
| 78 |
94 |
out = append(out, v) |
| 79 |
94 |
} |
| 80 |
|
} |
| 81 |
73 |
return out |
| 82 |
|
} |