|
| 1 | +# App modules |
| 2 | + |
| 3 | +The five "app pages" — inbox, kanban, calendar, file manager, settings — are non-trivial interactive surfaces, each in its own module under [`src/v4/`](../src/v4/). They share the same loading pattern: lazy-imported when the relevant root element is present. |
| 4 | + |
| 5 | +| Module | Page | Root selector | |
| 6 | +| --- | --- | --- | |
| 7 | +| `inbox.js` | [`inbox.html`](../production/inbox.html) | `#inbox-root` | |
| 8 | +| `kanban.js` | [`kanban.html`](../production/kanban.html) | `.kanban-board` (auto-detected) | |
| 9 | +| `calendar.js` | [`calendar.html`](../production/calendar.html) | `.calendar-grid` | |
| 10 | +| `file-manager.js` | [`file_manager.html`](../production/file_manager.html) | `.file-manager-root` (auto-detected) | |
| 11 | +| `settings.js` | [`settings.html`](../production/settings.html) | `.settings-content` | |
| 12 | + |
| 13 | +Each exports an idempotent `init<Name>()` and is wired up in [`src/main-v4.js`](../src/main-v4.js): |
| 14 | + |
| 15 | +```js |
| 16 | +if (document.getElementById('inbox-root')) { |
| 17 | + import('./v4/inbox.js').then((m) => m.initInbox()); |
| 18 | +} |
| 19 | +if (document.querySelector('.calendar-grid')) { |
| 20 | + import('./v4/calendar.js').then((m) => m.initCalendar()); |
| 21 | +} |
| 22 | +if (document.querySelector('.settings-content')) { |
| 23 | + import('./v4/settings.js').then((m) => m.initSettings()); |
| 24 | +} |
| 25 | +``` |
| 26 | + |
| 27 | +## Inbox |
| 28 | + |
| 29 | +A full email client UI: folders sidebar, message list, reader pane, compose modal. |
| 30 | + |
| 31 | +### Features |
| 32 | + |
| 33 | +- **Folders**: Inbox, Starred, Sent, Drafts, Spam, Trash — switching filters the message list |
| 34 | +- **Reader pane**: clicking a message shows the body to the right |
| 35 | +- **Compose**: button → modal with To / Subject / Body inputs |
| 36 | +- **Reply / Forward** buttons in the reader header |
| 37 | +- **Search** input filters messages in the current folder |
| 38 | +- **Keyboard shortcuts** (when not typing in an input): |
| 39 | + - `j` / `↓` — next message |
| 40 | + - `k` / `↑` — previous message |
| 41 | + - `r` — reply |
| 42 | + - `s` — toggle star on the selected message |
| 43 | + - `#` — delete the selected message |
| 44 | + - `c` — open compose |
| 45 | + |
| 46 | +Forward is a button in the reader header (no keyboard shortcut). |
| 47 | + |
| 48 | +### Data shape |
| 49 | + |
| 50 | +The inbox seed is an array of message objects in [`src/v4/inbox.js`](../src/v4/inbox.js). To wire to a real backend, see [data-adapter.md](data-adapter.md) — append `?api=1` to the URL and the inbox switches from seed to HTTP. |
| 51 | + |
| 52 | +```js |
| 53 | +{ |
| 54 | + id: 1, |
| 55 | + folder: 'inbox', // inbox | sent | drafts | trash |
| 56 | + unread: true, |
| 57 | + starred: false, |
| 58 | + label: 'work', // work | personal | promotions | urgent | null |
| 59 | + from: 'Sarah K.', // inbox: sender name. sent/drafts: 'Me' |
| 60 | + fromEmail: 'sarah@design.co', // present on inbox items |
| 61 | + to: 'team@example.com', // present on sent/drafts items |
| 62 | + subject: 'Re: Q1 design review', |
| 63 | + preview: 'I\'ve added comments to the figma file…', |
| 64 | + body: 'Full message body as plain text with \\n line breaks', |
| 65 | + time: '9:42 AM', // display string, not an ISO date |
| 66 | + trashed: false // set on items in trash folder |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +Note: `time` is a display string (`'9:42 AM'`, `'Yesterday'`, `'Apr 19'`), not a `Date` or ISO timestamp. If you swap in an HTTP adapter, transform real timestamps to display strings before rendering. |
| 71 | + |
| 72 | +### Extending |
| 73 | + |
| 74 | +To add a new folder: |
| 75 | + |
| 76 | +1. Add to the folders array at the top of `inbox.js`. |
| 77 | +2. Make sure seed messages have a matching `folder` value. |
| 78 | + |
| 79 | +To add a label / tag system: |
| 80 | + |
| 81 | +1. Add `labels: string[]` to the message shape. |
| 82 | +2. Render label chips in the message list item. |
| 83 | +3. Add a labels-filter UI to the folders sidebar. |
| 84 | + |
| 85 | +## Kanban |
| 86 | + |
| 87 | +Drag-and-drop board with columns and cards. [`src/v4/kanban.js`](../src/v4/kanban.js). |
| 88 | + |
| 89 | +### Features |
| 90 | + |
| 91 | +- **Columns** with title and card count |
| 92 | +- **Cards** with title, description, labels, assignees, due date, priority |
| 93 | +- **Drag-and-drop** between columns and reorder within a column (HTML5 native drag API) |
| 94 | +- **Click a card** to open an edit modal (title, description, labels, due, priority, delete) |
| 95 | + |
| 96 | +### Data shape |
| 97 | + |
| 98 | +```js |
| 99 | +{ |
| 100 | + id: 1, |
| 101 | + col: 'todo', // column id |
| 102 | + title: 'Add user filter to contacts page', |
| 103 | + desc: 'Filter by role + active status', |
| 104 | + labels: ['eng', 'frontend'], // ids referencing LABELS list |
| 105 | + assignees: ['JS', 'MR'], // initials |
| 106 | + due: 'May 25', |
| 107 | + priority: 'high' // low | medium | high |
| 108 | +} |
| 109 | +``` |
| 110 | + |
| 111 | +Columns and labels are defined at the top of [`src/v4/kanban.js`](../src/v4/kanban.js) as `COLUMNS` and `LABELS` arrays. |
| 112 | + |
| 113 | +### Extending |
| 114 | + |
| 115 | +- **Add a card**: insert into the cards array, call `render()`; drag handlers are delegated on the board so no per-card wiring needed |
| 116 | +- **Persist drag**: hook the `drop` handler in `kanban.js` to PATCH the card's new `col` field |
| 117 | +- **Add columns**: append to `COLUMNS`; the rendered columns are derived from it |
| 118 | +- **WIP limits**: not built in. Add a `limit` field to columns and compare against `cards.filter(c => c.col === id).length` in `renderColumn()` |
| 119 | + |
| 120 | +## Calendar |
| 121 | + |
| 122 | +Month-grid view with event CRUD. [`src/v4/calendar.js`](../src/v4/calendar.js). |
| 123 | + |
| 124 | +### Features |
| 125 | + |
| 126 | +- **Month grid** with prev / today / next nav |
| 127 | +- **Click a day** → "Add event" modal |
| 128 | +- **Click an event** → edit / delete modal |
| 129 | +- **Color tags** for event categories (blue, yellow, purple, red, green) |
| 130 | +- **Procedural events** — recurring placeholders (e.g. weekly standup) generated on render so the grid always has content |
| 131 | +- **Drag-to-reschedule is not implemented** — events are edited via the modal instead |
| 132 | + |
| 133 | +### Data shape |
| 134 | + |
| 135 | +```js |
| 136 | +{ |
| 137 | + id: 1, |
| 138 | + title: 'Sprint review', |
| 139 | + start: '2026-05-20T14:00:00Z', |
| 140 | + end: '2026-05-20T15:00:00Z', |
| 141 | + color: 'teal', // teal | blue | yellow | red | purple |
| 142 | + description: '…' |
| 143 | +} |
| 144 | +``` |
| 145 | + |
| 146 | +### Extending |
| 147 | + |
| 148 | +- **Week / day view**: add view-switcher buttons; the underlying date grid logic is already abstracted. |
| 149 | +- **All-day events**: add `allDay: true` to the shape; render in a separate row above the timed grid. |
| 150 | +- **Recurring events**: store an [iCalendar RRULE](https://datatracker.ietf.org/doc/html/rfc5545) and expand on render. |
| 151 | + |
| 152 | +## File manager |
| 153 | + |
| 154 | +Tree + grid file browser. [`src/v4/file-manager.js`](../src/v4/file-manager.js). |
| 155 | + |
| 156 | +### Features |
| 157 | + |
| 158 | +- **Folder tree** on the left with expand / collapse |
| 159 | +- **File area** on the right with grid / list view toggle |
| 160 | +- **Breadcrumb** above the file area |
| 161 | +- **Per-file menu button** (⋯) opens a popover via `openMenu()` with Rename and Delete |
| 162 | +- **Star / unstar** any file; a "Starred" virtual folder collects them |
| 163 | +- **Shared** virtual folder shows files from a designated parent folder (demo: `marketing`) |
| 164 | + |
| 165 | +### Data shape |
| 166 | + |
| 167 | +```js |
| 168 | +{ |
| 169 | + folders: [ |
| 170 | + { id: 'root', name: 'My drive', parent: null }, |
| 171 | + { id: 'docs', name: 'Documents', parent: 'root' }, |
| 172 | + { id: 'photos', name: 'Photos', parent: 'root' } |
| 173 | + ], |
| 174 | + files: [ |
| 175 | + { |
| 176 | + id: 1, |
| 177 | + folder: 'docs', |
| 178 | + name: 'Q3 plan.pdf', |
| 179 | + type: 'pdf', // determines icon |
| 180 | + size: 1248000, |
| 181 | + modified: '2026-05-15T10:00:00Z' |
| 182 | + } |
| 183 | + ] |
| 184 | +} |
| 185 | +``` |
| 186 | + |
| 187 | +### Extending |
| 188 | + |
| 189 | +- **File preview**: add a click handler that opens a modal with the file content (image, PDF embed, video). |
| 190 | +- **Download**: add a `{ label: 'Download', action: () => downloadFile(id) }` entry to the per-file menu in `file-manager.js`. |
| 191 | +- **Right-click context menu**: not built in. Wire `contextmenu` events on file rows to call the same `openMenu()` with the file's item list. |
| 192 | +- **Multi-select**: add shift / cmd-click handling to mark multiple files; show a bulk-action bar at the top. |
| 193 | +- **Upload**: drop-zone over the file area → POST to your backend. |
| 194 | +- **Search**: add an `<input>` above the file area and filter `getCurrentFiles()` by name on input. |
| 195 | + |
| 196 | +## Settings |
| 197 | + |
| 198 | +`localStorage`-backed settings page. [`src/v4/settings.js`](../src/v4/settings.js). |
| 199 | + |
| 200 | +### Features |
| 201 | + |
| 202 | +- **Sections** (in [`production/settings.html`](../production/settings.html)): Account, Notifications, Privacy & security, Appearance, Integrations, Billing, Team, Danger zone |
| 203 | +- **Section nav** in a left rail with scroll-spy active state |
| 204 | +- **Persisted state** — toggles, radios, and form inputs save to `localStorage['gentelella:settings']` on change |
| 205 | +- **Theme controls** in Appearance read/write `localStorage.theme` (the same key used by the pre-paint script) |
| 206 | + |
| 207 | +### Data shape |
| 208 | + |
| 209 | +```js |
| 210 | +localStorage.getItem('gentelella:settings') |
| 211 | +// JSON-encoded object — keys vary by input type: |
| 212 | +{ |
| 213 | + 'toggle:notifications:Email digest': true, // toggles: toggle:<section-id>:<label> |
| 214 | + 'toggle:notifications:Push': false, |
| 215 | + 'radio:theme': 'dark', // radios: radio:<name> |
| 216 | + 'radio:density': 'comfy' |
| 217 | +} |
| 218 | +``` |
| 219 | + |
| 220 | +The persister discovers inputs by class: |
| 221 | + |
| 222 | +- Toggles in `.settings-toggle-list .toggle` get a key `toggle:<section-id>:<label>` derived from the closest `.settings-section` and the row's `.label` text |
| 223 | +- Radios in `.theme-options input[type="radio"]` get `radio:<name>` |
| 224 | + |
| 225 | +To add a new persisted input, place it inside one of these container classes — no extra attribute needed. |
| 226 | + |
| 227 | +### Extending |
| 228 | + |
| 229 | +- **Add a section**: copy a `<section class="settings-section" id="…">` block in [`production/settings.html`](../production/settings.html) and append a `<a class="settings-nav-link" href="#…">` in the `.settings-nav` aside. |
| 230 | +- **Sync to backend**: wrap `load()` / `save()` in [`src/v4/settings.js`](../src/v4/settings.js) with a `httpAdapter` (see [data-adapter.md](data-adapter.md)) so settings persist server-side instead of (or in addition to) `localStorage`. |
| 231 | +- **Validation**: extend the toggle / radio change handlers in `settings.js` to validate the new value before calling `save()`. |
| 232 | + |
| 233 | +## Common patterns |
| 234 | + |
| 235 | +All five modules share these conventions: |
| 236 | + |
| 237 | +- **Single root element**: each module checks for its root selector before doing anything else. |
| 238 | +- **Idempotent init**: safe to call twice; already-initialized roots are skipped via a marker class (`._gentelella-init`). |
| 239 | +- **Event delegation**: handlers attach to the root element and use `e.target.closest('.thing')` for matching. No per-element listeners on render. |
| 240 | +- **Seed-first**: starts with an in-memory data array; mutations re-render the affected DOM region. `?api=1` swaps the seed for an HTTP adapter. |
| 241 | +- **No SPA navigation**: each module is self-contained on its own page. There's no client-side router. |
| 242 | + |
| 243 | +## Where to look |
| 244 | + |
| 245 | +- [`src/v4/inbox.js`](../src/v4/inbox.js) |
| 246 | +- [`src/v4/kanban.js`](../src/v4/kanban.js) |
| 247 | +- [`src/v4/calendar.js`](../src/v4/calendar.js) |
| 248 | +- [`src/v4/file-manager.js`](../src/v4/file-manager.js) |
| 249 | +- [`src/v4/settings.js`](../src/v4/settings.js) |
| 250 | +- [data-adapter.md](data-adapter.md) for wiring up a real backend |
0 commit comments