Wire the Present button to frame-driven presentation mode #87

Open
opened 2026-04-28 10:50:50 +00:00 by AhmedHanafy725 · 3 comments
Member

Problem

The Present button in the board page (templates/web/board.html:23) is currently hardcoded to onclick="alert('Presentation mode coming soon')", even though the underlying presentation module — static/web/js/whiteboard/frames.js — is already implemented (startPresentation, nextFrame, prevFrame, focusFrame) and is loaded by the page (board.html:242). Users see a placeholder alert instead of the working feature.

Frames are zoom-to-fit slides: with one or more Frame objects on the board, the user clicks Present, the canvas zooms to fit the first frame, and arrow keys advance through the rest. Today none of that is reachable from the UI.

Expected behavior

  • Clicking the Present button starts presentation mode if at least one frame is on the board (calls WhiteboardFrames.startPresentation()).
  • If no frame exists, surface a clear inline message — not window.alert. The current frames.js:17 already calls alert('No frames found...'); which has the same UX problem; replace it with a themed toast / overlay.
  • Once in presentation mode:
    • / Space / PageDown advance to the next frame (WhiteboardFrames.nextFrame()).
    • / PageUp go to the previous frame (WhiteboardFrames.prevFrame()).
    • Esc exits presentation mode (WhiteboardFrames.stopPresentation()).
    • The toolbar / minimap / property panel are hidden so the canvas fills the viewport.
  • Exiting presentation mode restores the normal UI and the previous zoom/pan position.
  • Presentation mode does not interfere with sync — other users continue to see live edits as before; the presenter's zoom changes are local-only.

Affected files

  • crates/hero_whiteboard_ui/templates/web/board.html — replace the inline alert with a togglePresentation() call; add a small presentation overlay (controls + close).
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/frames.js — replace alert(...) with a call into a UI hook (e.g. window.showFramesEmptyNotice && window.showFramesEmptyNotice()); add a togglePresentation API; capture+restore the previous zoom/pan in startPresentation / stopPresentation; add keyboard handler.
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/shortcuts.js (optional) — keep or augment any existing key bindings that should be suppressed during presentation mode.

No server / SDK / openrpc / DB changes.

Acceptance criteria

  • Present button no longer shows alert(...); it toggles presentation mode through WhiteboardFrames.
  • With ≥ 1 frame on the board, clicking Present zooms to fit the first frame and hides the navbar / toolbar / minimap / property panel.
  • Arrow / Space / PageDown / PageUp advance and rewind through frames; Escape exits.
  • Exiting presentation mode restores the previous zoom and pan position and shows the normal UI.
  • If there are no frames, a themed inline notice appears (no window.alert).
  • No regression in normal editing (the non-presentation keyboard shortcuts still work outside presentation mode).
  • cargo check / clippy / fmt --check / test clean.

Notes

  • The simplest UI hide approach is a single body.classList.add('wb-presenting') flag with a small CSS block that hides .wb-navbar, .wb-toolbar, .wb-minimap, .wb-prop-panel, .wb-zoom. Restore by removing the class on exit.
  • Capture stage.scaleX(), stage.x(), stage.y() at presentation-start so exit can restore them. The current startPresentation doesn't snapshot these.
  • Don't add a slide counter / progress bar in this iteration; keep scope tight. Optional follow-up.
## Problem The Present button in the board page (`templates/web/board.html:23`) is currently hardcoded to `onclick="alert('Presentation mode coming soon')"`, even though the underlying presentation module — `static/web/js/whiteboard/frames.js` — is already implemented (`startPresentation`, `nextFrame`, `prevFrame`, `focusFrame`) and is loaded by the page (`board.html:242`). Users see a placeholder alert instead of the working feature. Frames are zoom-to-fit slides: with one or more `Frame` objects on the board, the user clicks Present, the canvas zooms to fit the first frame, and arrow keys advance through the rest. Today none of that is reachable from the UI. ## Expected behavior - Clicking the Present button starts presentation mode if at least one frame is on the board (calls `WhiteboardFrames.startPresentation()`). - If no frame exists, surface a clear inline message — not `window.alert`. The current `frames.js:17` already calls `alert('No frames found...');` which has the same UX problem; replace it with a themed toast / overlay. - Once in presentation mode: - `→` / `Space` / `PageDown` advance to the next frame (`WhiteboardFrames.nextFrame()`). - `←` / `PageUp` go to the previous frame (`WhiteboardFrames.prevFrame()`). - `Esc` exits presentation mode (`WhiteboardFrames.stopPresentation()`). - The toolbar / minimap / property panel are hidden so the canvas fills the viewport. - Exiting presentation mode restores the normal UI and the previous zoom/pan position. - Presentation mode does not interfere with sync — other users continue to see live edits as before; the presenter's zoom changes are local-only. ## Affected files - `crates/hero_whiteboard_ui/templates/web/board.html` — replace the inline `alert` with a `togglePresentation()` call; add a small presentation overlay (controls + close). - `crates/hero_whiteboard_ui/static/web/js/whiteboard/frames.js` — replace `alert(...)` with a call into a UI hook (e.g. `window.showFramesEmptyNotice && window.showFramesEmptyNotice()`); add a `togglePresentation` API; capture+restore the previous zoom/pan in `startPresentation` / `stopPresentation`; add keyboard handler. - `crates/hero_whiteboard_ui/static/web/js/whiteboard/shortcuts.js` (optional) — keep or augment any existing key bindings that should be suppressed during presentation mode. No server / SDK / openrpc / DB changes. ## Acceptance criteria - [ ] Present button no longer shows `alert(...)`; it toggles presentation mode through `WhiteboardFrames`. - [ ] With ≥ 1 frame on the board, clicking Present zooms to fit the first frame and hides the navbar / toolbar / minimap / property panel. - [ ] Arrow / Space / PageDown / PageUp advance and rewind through frames; Escape exits. - [ ] Exiting presentation mode restores the previous zoom and pan position and shows the normal UI. - [ ] If there are no frames, a themed inline notice appears (no `window.alert`). - [ ] No regression in normal editing (the non-presentation keyboard shortcuts still work outside presentation mode). - [ ] `cargo check / clippy / fmt --check / test` clean. ## Notes - The simplest UI hide approach is a single `body.classList.add('wb-presenting')` flag with a small CSS block that hides `.wb-navbar`, `.wb-toolbar`, `.wb-minimap`, `.wb-prop-panel`, `.wb-zoom`. Restore by removing the class on exit. - Capture `stage.scaleX()`, `stage.x()`, `stage.y()` at presentation-start so exit can restore them. The current `startPresentation` doesn't snapshot these. - Don't add a slide counter / progress bar in this iteration; keep scope tight. Optional follow-up.
Author
Member

Implementation Spec for Issue #87

Objective

Wire the existing WhiteboardFrames presentation module to the Present button in the board page, with keyboard navigation (arrows / space / page-keys) and Escape to exit. Hide the chrome (navbar, toolbar, minimap, properties panel, zoom controls) while presenting; restore zoom/pan and chrome on exit. Replace the alert(...) for the no-frames case with an in-page themed notice.

Requirements

  • Clicking the Present button toggles presentation mode (start when not active, exit when active).
  • With ≥ 1 frame, presentation starts at the first frame, the canvas zooms to fit it, and the chrome is hidden.
  • With 0 frames, an inline themed notice appears (no window.alert).
  • During presentation: / Space / PageDown advance, / PageUp go back, Esc exits.
  • Exiting restores the previous zoom + pan and shows the chrome.
  • The Present button visibly reflects state (e.g. plays the same icon for both states; tooltip changes).
  • No regression in normal editing or in the existing Frame create / drag / resize / rename flows.
  • Presentation is local-only (does not affect other users on the same board).
  • Diff is contained to board.html and frames.js.
  • cargo check / clippy / fmt --check / test clean.

Files to Modify

  • crates/hero_whiteboard_ui/templates/web/board.html — change the Present button's onclick to call a new togglePresentation(); add a small CSS block (in {% block head %}) that hides chrome when body.wb-presenting is set; add a tiny in-page frames-empty-toast notice; add a global keydown listener that delegates to WhiteboardFrames when in presentation mode.
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/frames.js — capture/restore zoom + pan in startPresentation / stopPresentation; toggle body.wb-presenting; replace the alert(...) with a call to a global showNoFramesNotice() (defined in board.html); export a togglePresentation() convenience.

No server / SDK / openrpc / DB changes.

Implementation Plan

Step 1: frames.js — capture/restore zoom+pan, body class, toggle, replace alert

Files: crates/hero_whiteboard_ui/static/web/js/whiteboard/frames.js

  1. Add module-scope previousView = null to hold { scale, x, y } snapshotted at startPresentation. On stopPresentation, restore stage.scaleX/scaleY/x/y from it via WhiteboardCanvas.setZoom + stage.position(...), then call WhiteboardCanvas.drawGrid().
  2. In startPresentation:
    • If frames.length === 0, call window.showNoFramesNotice && window.showNoFramesNotice() instead of alert(...). Bail.
    • Otherwise: snapshot the view, set presentationMode = true, set currentFrameIndex = 0, add wb-presenting to document.body.classList, call focusFrame(frames[0]).
  3. In stopPresentation:
    • If we're not in presentation mode, no-op.
    • Otherwise: clear presentationMode, remove wb-presenting from body, restore zoom/pan from previousView, clear previousView.
  4. Add a public togglePresentation() that calls startPresentation() when inactive, stopPresentation() when active.
  5. The existing nextFrame / prevFrame keep their current behavior; export them.

Dependencies: none.

Step 2: board.html — wire button, keyboard, notice, hide-chrome CSS

Files: crates/hero_whiteboard_ui/templates/web/board.html

  1. Replace the Present button's onclick:
    <button class="btn btn-sm" title="Present (Esc to exit)" onclick="WhiteboardFrames.togglePresentation()">
        <i class="bi bi-play-fill"></i>
    </button>
    
  2. In {% block head %}, add CSS that hides the chrome when wb-presenting is on <body>:
    body.wb-presenting .wb-navbar,
    body.wb-presenting .wb-toolbar,
    body.wb-presenting .wb-minimap,
    body.wb-presenting .wb-zoom,
    body.wb-presenting #properties-panel { display: none !important; }
    
    Plus a small fixed-position notice that becomes the no-frames toast:
    #frames-empty-toast {
        display:none;
        position:fixed;
        top:24px;
        left:50%;
        transform:translateX(-50%);
        background:var(--wb-surface);
        border:1px solid var(--wb-border);
        color:var(--wb-text);
        border-radius:8px;
        padding:10px 16px;
        font-size:13px;
        z-index:10000;
        box-shadow:0 4px 16px rgba(0,0,0,0.15);
    }
    
  3. Add the toast markup inside {% block content %} (anywhere; it's position:fixed):
    <div id="frames-empty-toast">No frames on this board. Add a frame (F) to use Presentation mode.</div>
    
  4. In {% block scripts %}, add the global helpers:
    window.showNoFramesNotice = function () {
        var t = document.getElementById('frames-empty-toast');
        if (!t) return;
        t.style.display = 'block';
        setTimeout(function () { t.style.display = 'none'; }, 2500);
    };
    document.addEventListener('keydown', function (e) {
        if (typeof WhiteboardFrames === 'undefined' || !WhiteboardFrames.isPresentationMode || !WhiteboardFrames.isPresentationMode()) return;
        if (e.key === 'Escape') { e.preventDefault(); WhiteboardFrames.stopPresentation(); return; }
        if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') { e.preventDefault(); WhiteboardFrames.nextFrame(); return; }
        if (e.key === 'ArrowLeft' || e.key === 'PageUp')                    { e.preventDefault(); WhiteboardFrames.prevFrame(); return; }
    });
    
    Insert before the existing rename keydown handler so Escape on presentation takes priority.

Dependencies: Step 1 (the togglePresentation / isPresentationMode API).

Acceptance Criteria

  • Present button no longer pops alert(...); clicking it toggles presentation mode.
  • With at least one frame, presentation starts at the first frame, the canvas zooms to fit, and chrome is hidden.
  • With zero frames, an inline themed toast appears (no window.alert); the toast auto-dismisses after ~2.5 s.
  • / Space / PageDown advance through frames; / PageUp go back; Esc exits.
  • Exiting presentation mode restores the previous zoom + pan and re-shows the chrome.
  • No regression in normal editing.
  • No regression in existing frame create / drag / resize / rename flows.
  • cargo check / clippy / fmt --check / test all pass.

Notes

  • Don't add a slide counter / progress bar in this iteration.
  • Capture stage.scaleX(), stage.x(), stage.y() at start. The existing frames.js startPresentation doesn't snapshot these.
  • The Present button reuses the existing bi-play-fill icon for both states; the title attribute Present (Esc to exit) handles the affordance for now.
  • The keydown handler doesn't preempt input fields — it only acts when isPresentationMode() is true, so typing in modals isn't affected (presentation mode hides modals' parent panel anyway).
## Implementation Spec for Issue #87 ### Objective Wire the existing `WhiteboardFrames` presentation module to the Present button in the board page, with keyboard navigation (arrows / space / page-keys) and Escape to exit. Hide the chrome (navbar, toolbar, minimap, properties panel, zoom controls) while presenting; restore zoom/pan and chrome on exit. Replace the `alert(...)` for the no-frames case with an in-page themed notice. ### Requirements - Clicking the Present button toggles presentation mode (start when not active, exit when active). - With ≥ 1 frame, presentation starts at the first frame, the canvas zooms to fit it, and the chrome is hidden. - With 0 frames, an inline themed notice appears (no `window.alert`). - During presentation: `→` / `Space` / `PageDown` advance, `←` / `PageUp` go back, `Esc` exits. - Exiting restores the previous zoom + pan and shows the chrome. - The Present button visibly reflects state (e.g. plays the same icon for both states; tooltip changes). - No regression in normal editing or in the existing Frame create / drag / resize / rename flows. - Presentation is local-only (does not affect other users on the same board). - Diff is contained to `board.html` and `frames.js`. - `cargo check / clippy / fmt --check / test` clean. ### Files to Modify - `crates/hero_whiteboard_ui/templates/web/board.html` — change the Present button's `onclick` to call a new `togglePresentation()`; add a small CSS block (in `{% block head %}`) that hides chrome when `body.wb-presenting` is set; add a tiny in-page `frames-empty-toast` notice; add a global keydown listener that delegates to `WhiteboardFrames` when in presentation mode. - `crates/hero_whiteboard_ui/static/web/js/whiteboard/frames.js` — capture/restore zoom + pan in `startPresentation` / `stopPresentation`; toggle `body.wb-presenting`; replace the `alert(...)` with a call to a global `showNoFramesNotice()` (defined in board.html); export a `togglePresentation()` convenience. No server / SDK / openrpc / DB changes. ### Implementation Plan #### Step 1: `frames.js` — capture/restore zoom+pan, body class, toggle, replace alert Files: `crates/hero_whiteboard_ui/static/web/js/whiteboard/frames.js` 1. Add module-scope `previousView = null` to hold `{ scale, x, y }` snapshotted at `startPresentation`. On `stopPresentation`, restore `stage.scaleX/scaleY/x/y` from it via `WhiteboardCanvas.setZoom` + `stage.position(...)`, then call `WhiteboardCanvas.drawGrid()`. 2. In `startPresentation`: - If `frames.length === 0`, call `window.showNoFramesNotice && window.showNoFramesNotice()` instead of `alert(...)`. Bail. - Otherwise: snapshot the view, set `presentationMode = true`, set `currentFrameIndex = 0`, add `wb-presenting` to `document.body.classList`, call `focusFrame(frames[0])`. 3. In `stopPresentation`: - If we're not in presentation mode, no-op. - Otherwise: clear `presentationMode`, remove `wb-presenting` from body, restore zoom/pan from `previousView`, clear `previousView`. 4. Add a public `togglePresentation()` that calls `startPresentation()` when inactive, `stopPresentation()` when active. 5. The existing `nextFrame` / `prevFrame` keep their current behavior; export them. Dependencies: none. #### Step 2: `board.html` — wire button, keyboard, notice, hide-chrome CSS Files: `crates/hero_whiteboard_ui/templates/web/board.html` 1. Replace the Present button's `onclick`: ```html <button class="btn btn-sm" title="Present (Esc to exit)" onclick="WhiteboardFrames.togglePresentation()"> <i class="bi bi-play-fill"></i> </button> ``` 2. In `{% block head %}`, add CSS that hides the chrome when `wb-presenting` is on `<body>`: ```css body.wb-presenting .wb-navbar, body.wb-presenting .wb-toolbar, body.wb-presenting .wb-minimap, body.wb-presenting .wb-zoom, body.wb-presenting #properties-panel { display: none !important; } ``` Plus a small fixed-position notice that becomes the no-frames toast: ```css #frames-empty-toast { display:none; position:fixed; top:24px; left:50%; transform:translateX(-50%); background:var(--wb-surface); border:1px solid var(--wb-border); color:var(--wb-text); border-radius:8px; padding:10px 16px; font-size:13px; z-index:10000; box-shadow:0 4px 16px rgba(0,0,0,0.15); } ``` 3. Add the toast markup inside `{% block content %}` (anywhere; it's `position:fixed`): ```html <div id="frames-empty-toast">No frames on this board. Add a frame (F) to use Presentation mode.</div> ``` 4. In `{% block scripts %}`, add the global helpers: ```js window.showNoFramesNotice = function () { var t = document.getElementById('frames-empty-toast'); if (!t) return; t.style.display = 'block'; setTimeout(function () { t.style.display = 'none'; }, 2500); }; document.addEventListener('keydown', function (e) { if (typeof WhiteboardFrames === 'undefined' || !WhiteboardFrames.isPresentationMode || !WhiteboardFrames.isPresentationMode()) return; if (e.key === 'Escape') { e.preventDefault(); WhiteboardFrames.stopPresentation(); return; } if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') { e.preventDefault(); WhiteboardFrames.nextFrame(); return; } if (e.key === 'ArrowLeft' || e.key === 'PageUp') { e.preventDefault(); WhiteboardFrames.prevFrame(); return; } }); ``` Insert before the existing rename keydown handler so Escape on presentation takes priority. Dependencies: Step 1 (the `togglePresentation` / `isPresentationMode` API). ### Acceptance Criteria - [ ] Present button no longer pops `alert(...)`; clicking it toggles presentation mode. - [ ] With at least one frame, presentation starts at the first frame, the canvas zooms to fit, and chrome is hidden. - [ ] With zero frames, an inline themed toast appears (no `window.alert`); the toast auto-dismisses after ~2.5 s. - [ ] `→` / `Space` / `PageDown` advance through frames; `←` / `PageUp` go back; `Esc` exits. - [ ] Exiting presentation mode restores the previous zoom + pan and re-shows the chrome. - [ ] No regression in normal editing. - [ ] No regression in existing frame create / drag / resize / rename flows. - [ ] `cargo check / clippy / fmt --check / test` all pass. ### Notes - Don't add a slide counter / progress bar in this iteration. - Capture `stage.scaleX()`, `stage.x()`, `stage.y()` at start. The existing `frames.js` `startPresentation` doesn't snapshot these. - The Present button reuses the existing `bi-play-fill` icon for both states; the title attribute `Present (Esc to exit)` handles the affordance for now. - The keydown handler doesn't preempt input fields — it only acts when `isPresentationMode()` is true, so typing in modals isn't affected (presentation mode hides modals' parent panel anyway).
Author
Member

Test Results

  • cargo test --workspace: all green.
  • cargo clippy --workspace -- -D warnings: clean.
  • cargo fmt --all -- --check: clean.
  • cargo check --workspace: clean.
  • node --check frames.js: parses cleanly.

Manual verification recommended:

  1. Open a board and place at least one frame (key F). Click Present — canvas zooms to fit the frame; navbar / toolbar / minimap / property panel / zoom controls are hidden.
  2. Press Right / Space / PageDown — advance to next frame; Left / PageUp — back; Esc — exit. Chrome reappears, previous zoom + pan restored.
  3. Click Present with no frames on the board — themed toast appears, auto-dismisses after ~2.5 s. No window.alert.
  4. Click Present, then click the same button again — exits (toggle behavior).
## Test Results - `cargo test --workspace`: all green. - `cargo clippy --workspace -- -D warnings`: clean. - `cargo fmt --all -- --check`: clean. - `cargo check --workspace`: clean. - `node --check frames.js`: parses cleanly. Manual verification recommended: 1. Open a board and place at least one frame (key F). Click Present — canvas zooms to fit the frame; navbar / toolbar / minimap / property panel / zoom controls are hidden. 2. Press Right / Space / PageDown — advance to next frame; Left / PageUp — back; Esc — exit. Chrome reappears, previous zoom + pan restored. 3. Click Present with no frames on the board — themed toast appears, auto-dismisses after ~2.5 s. No `window.alert`. 4. Click Present, then click the same button again — exits (toggle behavior).
Author
Member

Implementation Summary

2 files changed, +90 / -2.

frames.js

  • Snapshot {scale, x, y} at startPresentation so stopPresentation can restore the previous view.
  • Toggle body.wb-presenting so the host template can hide its chrome via CSS.
  • Replaced the legacy alert(...) with a call to a global window.showNoFramesNotice() (host-defined toast).
  • Added a togglePresentation() convenience export that calls start / stop based on the current mode.

board.html

  • Replaced the Present button's onclick="alert(...)" with onclick="WhiteboardFrames.togglePresentation()". Tooltip now reads Present (Esc to exit).
  • New {% block head %} style block: hides .wb-navbar / .wb-toolbar / .wb-minimap / .wb-zoom / #properties-panel while body.wb-presenting is set.
  • Added #frames-empty-toast markup + a window.showNoFramesNotice() helper that shows it for ~2.5 s.
  • Added a global keydown listener that delegates to WhiteboardFrames while presentation mode is active: Esc exits, Right/Space/PageDown advances, Left/PageUp goes back. Inserted before the existing rename keydown so Escape on presentation has priority.

Verification

  • cargo test --workspace: all green.
  • cargo clippy --workspace -- -D warnings: clean.
  • cargo fmt --all -- --check: clean.
  • cargo check --workspace: clean.
  • node --check frames.js: parses cleanly.

Notes / caveats

  • UI / static-asset only; no Rust / SDK / openrpc / DB changes.
  • Presentation is local to the presenting tab. Other users continue to edit normally; their views are unaffected.
  • The chrome-hide is purely CSS-driven via the body class — no destructive removals — so exiting cleanly restores everything.
  • Slide counter / progress bar were intentionally left out; can be a small follow-up.
## Implementation Summary 2 files changed, +90 / -2. ### `frames.js` - Snapshot `{scale, x, y}` at `startPresentation` so `stopPresentation` can restore the previous view. - Toggle `body.wb-presenting` so the host template can hide its chrome via CSS. - Replaced the legacy `alert(...)` with a call to a global `window.showNoFramesNotice()` (host-defined toast). - Added a `togglePresentation()` convenience export that calls `start` / `stop` based on the current mode. ### `board.html` - Replaced the Present button's `onclick="alert(...)"` with `onclick="WhiteboardFrames.togglePresentation()"`. Tooltip now reads `Present (Esc to exit)`. - New `{% block head %}` style block: hides `.wb-navbar / .wb-toolbar / .wb-minimap / .wb-zoom / #properties-panel` while `body.wb-presenting` is set. - Added `#frames-empty-toast` markup + a `window.showNoFramesNotice()` helper that shows it for ~2.5 s. - Added a global `keydown` listener that delegates to `WhiteboardFrames` while presentation mode is active: Esc exits, Right/Space/PageDown advances, Left/PageUp goes back. Inserted before the existing rename keydown so Escape on presentation has priority. ### Verification - `cargo test --workspace`: all green. - `cargo clippy --workspace -- -D warnings`: clean. - `cargo fmt --all -- --check`: clean. - `cargo check --workspace`: clean. - `node --check frames.js`: parses cleanly. ### Notes / caveats - UI / static-asset only; no Rust / SDK / openrpc / DB changes. - Presentation is local to the presenting tab. Other users continue to edit normally; their views are unaffected. - The chrome-hide is purely CSS-driven via the body class — no destructive removals — so exiting cleanly restores everything. - Slide counter / progress bar were intentionally left out; can be a small follow-up.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_whiteboard#87
No description provided.