6.2 KiB
6.2 KiB
DB Page: Grouped-by-Kind with Pagination
Problem
The www/db.html page loads all events from IndexedDB at once via store.getAll(), parses every event, and renders every row into a single <table>. With thousands of events, this causes:
- Slow initial render (thousands of DOM rows created at once)
- Unresponsive filtering/sorting (re-renders entire table on every keystroke)
- No way to get a quick overview of what kinds of events exist
Solution Overview
Replace the flat table with a two-level grouped view:
- Kind Summary View — initial load shows one row per kind with count, newest, and oldest timestamps
- Expanded Kind View — clicking a kind row expands it to show paginated event rows for that kind
- Pagination — each expanded kind has its own pagination controls
flowchart TD
A[Page Load] --> B[getAllEvents from IndexedDB]
B --> C[Parse events into currentEvents]
C --> D[groupEventsByKind]
D --> E[renderKindSummary]
E --> F{User clicks kind row}
F -->|Expand| G[renderExpandedKind with pagination]
F -->|Collapse| E
G --> H[Per-kind filter/sort/paginate]
H --> G
Detailed Design
New State Variables
Add to the GLOBAL VARIABLES section of db.html:
// Grouped view state
let kindGroups = new Map(); // Map<number, Event[]>
let expandedKinds = new Set(); // Set<number> - which kinds are open
let kindPageState = new Map(); // Map<number, {page, pageSize, sortCol, sortDir, filters}>
const DEFAULT_PAGE_SIZE = 50;
const PAGE_SIZE_OPTIONS = [25, 50, 100];
Function: groupEventsByKind
Groups the parsed currentEvents array into a Map<kind, Event[]>:
function groupEventsByKind(events) {
const groups = new Map();
for (const evt of events) {
const kind = evt.kind ?? 'unknown';
if (!groups.has(kind)) groups.set(kind, []);
groups.get(kind).push(evt);
}
return groups;
}
Function: renderKindSummary
Replaces the current renderTable() as the initial view. Renders a compact summary table:
| ▶ | Kind | Count | Newest | Oldest |
|---|---|---|---|---|
| ▶ | 0 | 42 | 2024-03-19 | 2024-01-15 |
| ▼ | 1 | 1,203 | 2024-03-19 | 2023-06-01 |
| ▶ | 3 | 87 | 2024-03-18 | 2024-02-10 |
- The ▶/▼ indicator uses the existing
HamburgerMorphingcomponent (arrow_right / arrow_down) - Clicking a row toggles that kind in
expandedKinds - When expanded, the events table for that kind renders directly below the summary row
divStatsshows total: "12,847 events across 23 kinds"
Function: renderExpandedKind
When a kind is expanded, renders:
- A sub-table with the existing column layout:
commands | kind | created_at | id | pubkey | content | tags | relay - Filter row (same wildcard inputs as current)
- Paginated rows (only renders current page worth of
<tr>elements) - Pagination controls bar below the sub-table
Pagination Controls
Per expanded kind, rendered below the event sub-table:
[◀ Prev] Page 1 of 25 [Next ▶] | Show: [25] [50] [100] | Showing 1-50 of 1,203
- Each kind maintains independent page state in
kindPageState - Changing page size resets to page 1
- Filtering resets to page 1
Per-Kind Filter and Sort
- Each expanded kind has its own filter inputs and sort state stored in
kindPageState - Filtering operates only on events of that kind
- Sorting operates only on events of that kind
- Both reset pagination to page 1
CSS Additions
New styles needed:
.kind-summary-row— styled like a header row, clickable, with hover effect.kind-summary-row .expand-icon— container for the HamburgerMorphing expand/collapse indicator.kind-expanded-section— container for the expanded event table + pagination.pagination-bar— flex row for pagination controls.pagination-btn— styled like existing.btnCmd.pagination-info— muted text showing current range.page-size-btn/.page-size-btn.active— page size selector buttons
Changes to Existing Functions
loadEvents
- After parsing events into
currentEvents, callgroupEventsByKind(currentEvents)to populatekindGroups - Call
renderKindSummary()instead ofrenderTable()
renderTable
- Kept as-is but renamed to
renderKindEventTable(kind, events, pageState)— only renders events for a single kind - Receives a subset of events (already filtered/sorted/paginated)
getAllEvents
- No changes needed — still loads all events from IndexedDB
- Future optimization: could use IndexedDB indexes to count by kind without loading all data, but that is out of scope for this change
Event Flow
- Page loads →
initDatabase()→loadEvents() loadEvents()callsgetAllEvents(), parses intocurrentEventsgroupEventsByKind(currentEvents)→kindGroupsMaprenderKindSummary()renders the summary table (fast — only N rows where N = number of distinct kinds)- User clicks kind row →
toggleKind(kind)adds/removes fromexpandedKinds - If expanding: initializes
kindPageStatefor that kind, callsrenderExpandedKind(kind) renderExpandedKind(kind)gets events fromkindGroups.get(kind), applies filters/sort fromkindPageState, slices for current page, renders sub-table + pagination- User interacts with pagination/filter/sort → updates
kindPageState, re-renders only that kind section
Preserved Functionality
- Raw JSON modal (view/copy) — works exactly as before, using
currentDisplayedEventsindex - Wildcard filtering — same
wildcardToRegexlogic, scoped per-kind - Column sorting — same
sortEventslogic, scoped per-kind - HamburgerMorphing sort icons — same pattern, scoped per-kind table
- Refresh button — reloads all events, re-groups, re-renders summary
Implementation Order
- Add new state variables
- Create
groupEventsByKind()function - Create
renderKindSummary()function with expand/collapse - Create
renderExpandedKind()function with pagination - Add pagination controls and handlers
- Wire up per-kind filtering
- Wire up per-kind sorting
- Add CSS styles
- Update
loadEvents()to use new pipeline - Update
divStatsfor summary counts - Test with existing raw JSON modal functionality