Merge pull request 'hero-feature' (#4) from hero-feature into main
Reviewed-on: UOM/website#4
This commit is contained in:
134
website/HERO_EDITOR.md
Normal file
134
website/HERO_EDITOR.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# UOMysticmoon — Hero Canvas Editor Spec
|
||||||
|
|
||||||
|
> Branch: **`hero-feature`**. Build contract for the WYSIWYG portal-hero editor.
|
||||||
|
> Derived from the design doc *Hero Canvas Editor — Design Document*, **corrected
|
||||||
|
> to match the current codebase** and with the open questions resolved.
|
||||||
|
> Same workflow as the wiki upgrade: design → phased build → verify.
|
||||||
|
|
||||||
|
## 1. Goal
|
||||||
|
|
||||||
|
Let staff compose the portal hero (background image, overlay opacity, and floating
|
||||||
|
elements — text, CTA buttons, moon, badge, image) in-browser, then preview and
|
||||||
|
publish — no source edits. Layout persists as JSON in the existing `settings` table.
|
||||||
|
|
||||||
|
## 2. Locked decisions
|
||||||
|
|
||||||
|
| # | Decision |
|
||||||
|
|---|---|
|
||||||
|
| Scope | **Full v1** — background/overlay, all element types, drag/resize/z-order, draft→preview→publish (built in phases) |
|
||||||
|
| CTA buttons | **First-class `buttons` element type** (independently positioned), not baked into a text block |
|
||||||
|
| First run | **Pre-populate** the canvas with today's hero (headline, subtitle, teaser, CTAs) as editable elements so nothing changes visually until edited |
|
||||||
|
| Drag | **Native Pointer Events** (mouse/touch/pen), zero dependencies |
|
||||||
|
| Font size | Stored in **px** (fixed reference canvas) |
|
||||||
|
| Image compression | **None** server-side; client warns when a file is > ~1 MB |
|
||||||
|
| Preview | `?preview=1` renders the **draft** by reading it through the authenticated admin settings endpoint |
|
||||||
|
| Other pages | Out of scope for v1 (design allows a per-page key later) |
|
||||||
|
|
||||||
|
## 3. Corrections to the design doc (current-code reality)
|
||||||
|
|
||||||
|
1. **Public settings is a whitelist, not `getAll()`.** `GET /api/v1/public/settings`
|
||||||
|
→ `settings.getPublic()` → `PUBLIC_KEYS` in
|
||||||
|
[settings.model.js](server/src/model/settings/settings.model.js). The doc's
|
||||||
|
"no backend changes / picked up automatically" is wrong. **Fix:** add
|
||||||
|
`hero_layout` to `PUBLIC_KEYS` (one line). `hero_layout_draft` stays out
|
||||||
|
(admin-only) — which is why preview reads the draft via `api.admin.getSettings()`.
|
||||||
|
2. **Moon is a reusable component** ([MoonDot.jsx](client/src/components/MoonDot.jsx),
|
||||||
|
props `size`/`glow`), used in logo/login/maintenance — not "only the header."
|
||||||
|
The `moon` element reuses it; it gains an optional `color`.
|
||||||
|
3. **Route vs. nav live in different files.** `/admin/hero` route →
|
||||||
|
[App.jsx](client/src/App.jsx); sidebar link/title → `NAV`/`TITLES` in
|
||||||
|
[AdminLayout.jsx](client/src/routes/admin/AdminLayout.jsx).
|
||||||
|
4. **Admin content area is `maxWidth: 1000px`** — the editor canvas renders
|
||||||
|
scaled-to-fit; percentage positions stay faithful.
|
||||||
|
|
||||||
|
Everything else in the doc matches (hardcoded `HERO_BG` + CTAs + `homepage_teaser`
|
||||||
|
in [Portal.jsx](client/src/routes/public/Portal.jsx); `updateSettings` accepts
|
||||||
|
arbitrary keys; `/admin/uploads` exists; default hero asset present; TEXT settings
|
||||||
|
columns — no schema change).
|
||||||
|
|
||||||
|
## 4. Data model — no schema change
|
||||||
|
|
||||||
|
Two `settings` keys (TEXT): `hero_layout` (live) and `hero_layout_draft` (admin).
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"background": { "image_url": null, "position_x": "left", "position_y": "center", "size": "cover" },
|
||||||
|
"overlay": { "opacity": 0.72 },
|
||||||
|
"elements": [
|
||||||
|
{ "id": "uuid", "type": "text_block|buttons|moon|badge|image",
|
||||||
|
"x": 50, "y": 42, "z": 1, "anchor": "center", "props": { /* per type */ } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Positions are **% of canvas** (reference width 1080, matching `.shell`), so the
|
||||||
|
layout adapts across viewports without breakpoint data. `version` is validated
|
||||||
|
(`=== 1`) before use; anything else falls back.
|
||||||
|
|
||||||
|
### Element props
|
||||||
|
|
||||||
|
| Type | Props |
|
||||||
|
|---|---|
|
||||||
|
| `text_block` | `lines: [{ text, tag(h1/h2/p/span), fontSize(px), color, weight }]`, `align` |
|
||||||
|
| `buttons` | `items: [{ label, to, variant(primary/ghost) }]`, `align`, `gap` |
|
||||||
|
| `moon` | `size`, `glow`, `color` |
|
||||||
|
| `badge` | `text`, `bgColor`, `textColor`, `borderRadius` |
|
||||||
|
| `image` | `src`, `width`(%), `alt` |
|
||||||
|
|
||||||
|
## 5. Backend changes
|
||||||
|
- **One line:** add `'hero_layout'` to `PUBLIC_KEYS`. No new routes/controllers —
|
||||||
|
layout saves through the existing `PUT /admin/settings`; images via `/admin/uploads`.
|
||||||
|
|
||||||
|
## 6. Frontend changes
|
||||||
|
- **New** `client/src/components/HeroElement.jsx` — renders one element by type
|
||||||
|
(shared by the live portal and the editor canvas).
|
||||||
|
- **New** `client/src/routes/admin/views/HeroEditor.jsx` — canvas + element tray +
|
||||||
|
properties panel; native-pointer drag/resize; background/overlay panel; snap grid;
|
||||||
|
auto-save draft, preview, publish, revert.
|
||||||
|
- **Edit** [Portal.jsx](client/src/routes/public/Portal.jsx) — parse `hero_layout`
|
||||||
|
(or draft when `?preview=1` + admin), render elements, fall back to a
|
||||||
|
`DEFAULT_LAYOUT` built from today's hero so the page is unchanged until edited.
|
||||||
|
- **Edit** [AdminLayout.jsx](client/src/routes/admin/AdminLayout.jsx) (nav) +
|
||||||
|
[App.jsx](client/src/App.jsx) (route `/admin/hero`).
|
||||||
|
- **Edit** [MoonDot.jsx](client/src/components/MoonDot.jsx) — optional `color`.
|
||||||
|
- **No** `client/src/api/client.js` changes needed beyond what exists
|
||||||
|
(`admin.updateSettings`, `admin.getSettings`, `admin.upload`).
|
||||||
|
|
||||||
|
## 7. Phased build (each phase: build → verify in preview → commit)
|
||||||
|
|
||||||
|
- **Phase 0 — Spec** ✅ this document.
|
||||||
|
- **Phase 1 — Data path & renderer** ✅ (verified 2026-06-28). `hero_layout`
|
||||||
|
whitelisted; `HeroElement.jsx`; Portal renders the layout with a `DEFAULT_LAYOUT`
|
||||||
|
fallback. Default render matches the old hero; publishing a layout re-renders;
|
||||||
|
draft key not exposed publicly. Shared helpers moved to `client/src/lib/heroLayout.js`.
|
||||||
|
- **Phase 2 — Editor shell + background/overlay** ✅ (verified 2026-06-28).
|
||||||
|
`/admin/hero` view + sidebar nav; canvas live-preview; background upload + 3×3
|
||||||
|
position + overlay opacity; debounced draft auto-save; publish; `?preview=1`
|
||||||
|
reads the draft (admin) with a banner; revert. Verified: overlay/position update
|
||||||
|
the canvas, auto-save writes the draft, publish writes live, preview shows the
|
||||||
|
draft while the normal portal shows live.
|
||||||
|
- **Phase 3 — Elements: select / drag / text_block / buttons** ✅ (verified
|
||||||
|
2026-06-28). Element tray (+ Text / + Buttons); click-to-select with outline;
|
||||||
|
native Pointer Events drag (% of canvas); Delete key + panel delete; z-order
|
||||||
|
(send back / bring forward); text_block line editor (text/tag/size/color/bold,
|
||||||
|
add/remove lines, align) and buttons editor (label/path/variant, add/remove).
|
||||||
|
Verified: select shows the line editor, editing a line updates the canvas live,
|
||||||
|
drag moved 50%→65%, add→3/delete→2 elements, empty-canvas click deselects.
|
||||||
|
- **Phase 4 — moon + badge + image + resize + snap grid** ✅ (verified 2026-06-28).
|
||||||
|
Tray adds moon/badge/image; property panels (moon: size/glow/color; badge:
|
||||||
|
text/colors/radius; image: upload/width/alt); corner resize handle (image→width%,
|
||||||
|
moon→size, text→box width); 8px snap-grid toggle with overlay; image placeholder
|
||||||
|
until a file is chosen. Verified: each type adds + edits, resize moved a moon
|
||||||
|
64→104px, snap grid shows, and a published moon+badge render on the live portal.
|
||||||
|
|
||||||
|
**Status: v1 feature-complete.** All phases verified end-to-end; ready for PR.
|
||||||
|
Deferred (noted in the design doc as follow-ups): 8-point resize (only a corner
|
||||||
|
handle for now), per-viewport layouts, server-side image compression.
|
||||||
|
|
||||||
|
## 8. Edge cases (from the doc, carried forward)
|
||||||
|
- `JSON.parse` wrapped in try/catch + `version` check → fall back to `DEFAULT_LAYOUT`.
|
||||||
|
- Element ids via `crypto.randomUUID()` (never array index).
|
||||||
|
- Empty `elements` → render `DEFAULT_LAYOUT` so the hero is never blank.
|
||||||
|
- Last-write-wins on concurrent admin edits (acceptable for this shard).
|
||||||
|
- Client-side warning for background files > ~1 MB (no hard block; 8 MB server cap).
|
||||||
Reference in New Issue
Block a user