Implement Phase 8: Web UI with htmx templates
- HTML templates: layout, login, notebook list, notebook view, page viewer - Web server with chi router, embedded templates via //go:embed - Login/logout flow with session cookies - Notebook list, page grid with SVG thumbnails, page viewer - Share link views (same templates, no auth chrome) - Server command wired to start gRPC + REST + web servers concurrently - Graceful shutdown on SIGINT/SIGTERM Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
39
web/templates/layout.html
Normal file
39
web/templates/layout.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{block "title" .}}Engineering Pad{{end}}</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: system-ui, sans-serif; background: #fff; color: #111; }
|
||||
.container { max-width: 1200px; margin: 0 auto; padding: 1rem; }
|
||||
nav { background: #111; color: #fff; padding: 0.75rem 1rem; display: flex; justify-content: space-between; align-items: center; }
|
||||
nav a { color: #fff; text-decoration: none; }
|
||||
nav a:hover { text-decoration: underline; }
|
||||
h1 { font-size: 1.5rem; margin-bottom: 1rem; }
|
||||
.card { border: 1px solid #ccc; border-radius: 4px; padding: 1rem; margin-bottom: 0.5rem; }
|
||||
.card:hover { background: #f5f5f5; }
|
||||
a.card-link { text-decoration: none; color: inherit; display: block; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; }
|
||||
.page-thumb { border: 1px solid #ccc; background: #fff; aspect-ratio: 0.773; display: flex; align-items: center; justify-content: center; }
|
||||
.page-thumb img { width: 100%; height: 100%; object-fit: contain; }
|
||||
.btn { display: inline-block; padding: 0.5rem 1rem; border: 1px solid #111; border-radius: 4px; text-decoration: none; color: #111; background: #fff; cursor: pointer; }
|
||||
.btn:hover { background: #f0f0f0; }
|
||||
.actions { display: flex; gap: 0.5rem; margin: 1rem 0; }
|
||||
input[type="text"], input[type="password"] { padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px; width: 100%; max-width: 300px; }
|
||||
label { display: block; margin-bottom: 0.25rem; font-weight: bold; }
|
||||
.form-group { margin-bottom: 1rem; }
|
||||
</style>
|
||||
<script src="/static/htmx.min.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<a href="/notebooks">Engineering Pad</a>
|
||||
{{block "nav-right" .}}{{end}}
|
||||
</nav>
|
||||
<div class="container">
|
||||
{{block "content" .}}{{end}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
16
web/templates/login.html
Normal file
16
web/templates/login.html
Normal file
@@ -0,0 +1,16 @@
|
||||
{{define "title"}}Login — Engineering Pad{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<h1>Login</h1>
|
||||
<form method="POST" action="/login">
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" id="username" name="username" required autofocus>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn">Login</button>
|
||||
</form>
|
||||
{{end}}
|
||||
20
web/templates/notebook.html
Normal file
20
web/templates/notebook.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{{define "title"}}{{.Title}} — Engineering Pad{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<h1>{{.Title}}</h1>
|
||||
<div class="actions">
|
||||
<a href="{{.PDFLink}}" class="btn">Download PDF</a>
|
||||
</div>
|
||||
<div class="grid">
|
||||
{{range .Pages}}
|
||||
<div>
|
||||
<a href="{{.ViewLink}}">
|
||||
<div class="page-thumb">
|
||||
<img src="{{.SVGLink}}" alt="Page {{.Number}}">
|
||||
</div>
|
||||
</a>
|
||||
<div style="text-align:center; margin-top:0.25rem;">Page {{.Number}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
17
web/templates/notebooks.html
Normal file
17
web/templates/notebooks.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{{define "title"}}Notebooks — Engineering Pad{{end}}
|
||||
|
||||
{{define "nav-right"}}<a href="/logout">Logout</a>{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<h1>Notebooks</h1>
|
||||
{{range .Notebooks}}
|
||||
<a class="card-link" href="/notebooks/{{.ID}}">
|
||||
<div class="card">
|
||||
<strong>{{.Title}}</strong>
|
||||
<br><small>{{.PageSize}} — {{.PageCount}} pages — synced {{.SyncedAt}}</small>
|
||||
</div>
|
||||
</a>
|
||||
{{else}}
|
||||
<p>No notebooks synced yet.</p>
|
||||
{{end}}
|
||||
{{end}}
|
||||
13
web/templates/page.html
Normal file
13
web/templates/page.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{{define "title"}}Page {{.PageNumber}} — {{.NotebookTitle}} — Engineering Pad{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<h1>{{.NotebookTitle}} — Page {{.PageNumber}}</h1>
|
||||
<div class="actions">
|
||||
<a href="{{.BackLink}}" class="btn">Back to notebook</a>
|
||||
<a href="{{.JPGLink}}" class="btn">Download JPG</a>
|
||||
<a href="{{.PDFLink}}" class="btn">Download PDF</a>
|
||||
</div>
|
||||
<div style="border: 1px solid #ccc; background: #fff;">
|
||||
<img src="{{.SVGLink}}" alt="Page {{.PageNumber}}" style="width: 100%; height: auto;">
|
||||
</div>
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user