Replace the native window.prompt for naming a group with a proper themed modal #205

Closed
opened 2026-05-20 09:24:12 +00:00 by AhmedHanafy725 · 3 comments

Summary

When the user groups objects in the whiteboard (right-click → Group, or Ctrl+G), the app currently asks for the group name via the browser's native window.prompt. The dialog ignores the app's theme, blocks the page, and is visually inconsistent with the rest of the UI which uses themed modals everywhere else.

Where it happens

  • crates/hero_whiteboard_admin/static/web/js/whiteboard/contextmenu.js (right-click → Group): prompt('Group name:', 'Group').
  • crates/hero_whiteboard_admin/static/web/js/whiteboard/shortcuts.js (Ctrl+G): same prompt('Group name:', 'Group').

Both follow the call with WhiteboardGroups.createGroup(nodes, name || 'Group').

Scope

Replace those two window.prompt calls with a small, themed modal that:

  • Uses the app's existing modal styling (consistent with the other in-board modals such as the Share dialog) and respects the current theme (light/dark).
  • Has a single text input pre-filled with Group (the existing default), focused on open with the value selected so the user can just type to overwrite.
  • Has primary (Create) and secondary (Cancel) actions, plus Enter to submit and Escape to cancel. Clicking the backdrop also cancels. Trim whitespace; an empty string falls back to Group (matches current behavior).
  • On confirm, calls WhiteboardGroups.createGroup(selectedNodes, name) with the same nodes captured from the trigger; on cancel, does nothing.
  • Works both when triggered by Ctrl+G and by the right-click context menu; only one instance can be open at a time.
  • Does not block other UI threads (no native dialog); does not steal focus from the rest of the page once dismissed.

Acceptance Criteria

  • Pressing Ctrl+G with a multi-selection opens a themed modal, not the browser prompt, and creating from it groups the selection with the entered name.
  • Right-click → Group behaves the same way.
  • Cancel / Escape / backdrop click closes the modal without grouping.
  • Pressing Enter in the input submits.
  • Empty / whitespace-only input falls back to Group.
  • Visual styling matches the rest of the board UI in both light and dark themes.
  • No remaining window.prompt call related to grouping.

Notes

The whiteboard frontend is vanilla JS modules under crates/hero_whiteboard_admin/static/web/js/whiteboard/ embedded via rust-embed. Other in-board dialogs already use the app's modal pattern; mirror that pattern rather than introducing a new mechanism. Keep the change minimal: one small helper for a themed prompt-modal (or just inline the markup), invoked from the two existing call sites.

## Summary When the user groups objects in the whiteboard (right-click → Group, or Ctrl+G), the app currently asks for the group name via the browser's native `window.prompt`. The dialog ignores the app's theme, blocks the page, and is visually inconsistent with the rest of the UI which uses themed modals everywhere else. ## Where it happens - `crates/hero_whiteboard_admin/static/web/js/whiteboard/contextmenu.js` (right-click → Group): `prompt('Group name:', 'Group')`. - `crates/hero_whiteboard_admin/static/web/js/whiteboard/shortcuts.js` (Ctrl+G): same `prompt('Group name:', 'Group')`. Both follow the call with `WhiteboardGroups.createGroup(nodes, name || 'Group')`. ## Scope Replace those two `window.prompt` calls with a small, themed modal that: - Uses the app's existing modal styling (consistent with the other in-board modals such as the Share dialog) and respects the current theme (light/dark). - Has a single text input pre-filled with `Group` (the existing default), focused on open with the value selected so the user can just type to overwrite. - Has primary (Create) and secondary (Cancel) actions, plus Enter to submit and Escape to cancel. Clicking the backdrop also cancels. Trim whitespace; an empty string falls back to `Group` (matches current behavior). - On confirm, calls `WhiteboardGroups.createGroup(selectedNodes, name)` with the same nodes captured from the trigger; on cancel, does nothing. - Works both when triggered by Ctrl+G and by the right-click context menu; only one instance can be open at a time. - Does not block other UI threads (no native dialog); does not steal focus from the rest of the page once dismissed. ## Acceptance Criteria - Pressing Ctrl+G with a multi-selection opens a themed modal, not the browser prompt, and creating from it groups the selection with the entered name. - Right-click → Group behaves the same way. - Cancel / Escape / backdrop click closes the modal without grouping. - Pressing Enter in the input submits. - Empty / whitespace-only input falls back to `Group`. - Visual styling matches the rest of the board UI in both light and dark themes. - No remaining `window.prompt` call related to grouping. ## Notes The whiteboard frontend is vanilla JS modules under `crates/hero_whiteboard_admin/static/web/js/whiteboard/` embedded via rust-embed. Other in-board dialogs already use the app's modal pattern; mirror that pattern rather than introducing a new mechanism. Keep the change minimal: one small helper for a themed prompt-modal (or just inline the markup), invoked from the two existing call sites.
Author
Owner

Implementation Spec for Issue #205

Objective

Replace the two native window.prompt('Group name:', 'Group') call sites (right-click "Group Selected" in contextmenu.js and Ctrl+G in shortcuts.js) with a themed, in-board modal. The modal must visually match the rest of the whiteboard (via the existing --wb-* CSS variables), capture the selected nodes at trigger time, and route them to WhiteboardGroups.createGroup(capturedNodes, name) on confirm.

Requirements

  • Themed modal using the existing --wb-bg, --wb-surface, --wb-border, --wb-text, --wb-text-muted, --wb-primary, --wb-primary-text CSS variables so dark/light themes work automatically.
  • Pre-filled with Group; input text fully selected on open.
  • Primary Create, secondary Cancel; Enter confirms; Escape cancels; backdrop click cancels.
  • Single-instance guard: a second open() while one is already open returns Promise<null> without disturbing the first.
  • Whitespace trimmed; empty falls back to Group.
  • Selection is snapshot at trigger time (Ctrl+G keypress / right-click), not re-derived on confirm — changing the selection between trigger and confirm doesn't affect the result.
  • Focus returns to the previously focused element after close (close via any path).
  • No native prompt() in the new code path. No Bootstrap added to board.html.

Files to Modify/Create

  • Create: crates/hero_whiteboard_admin/static/web/js/whiteboard/prompt_modal.js — new ES5 IIFE module exposing WhiteboardPromptModal.open({title,label,defaultValue,confirmLabel,cancelLabel}) -> Promise<string|null>. Generic helper (cohesive with other in-board modals; reusable for future rename/etc. prompts).
  • Modify: crates/hero_whiteboard_admin/templates/web/board.html — add the prompt-modal markup near the existing #webframe-url-modal block; add the new script tag BEFORE groups.js/contextmenu.js/shortcuts.js.
  • Modify: crates/hero_whiteboard_admin/static/web/css/whiteboard.css — small .wb-prompt-modal* rules using the --wb-* vars.
  • Modify: crates/hero_whiteboard_admin/static/web/js/whiteboard/contextmenu.js — replace prompt(...) at line ~104.
  • Modify: crates/hero_whiteboard_admin/static/web/js/whiteboard/shortcuts.js — replace prompt(...) at line ~117.

Implementation Plan

Step 1: Add CSS for the prompt modal (independent)

Files: static/web/css/whiteboard.css

  • Append rules for .wb-prompt-modal (backdrop, position:fixed; inset:0; rgba(0,0,0,.5); z-index:9999; display:none; align-items:center; justify-content:center; + .is-open { display:flex; }), .wb-prompt-modal__dialog (uses --wb-surface/--wb-border/--wb-text), __title/__label/__input/__actions/__btn/__btn--primary styled with the --wb-* vars.
    Dependencies: none

Step 2: Create the helper module (independent from Step 1; needed by Steps 3-5)

Files: static/web/js/whiteboard/prompt_modal.js

  • ES5 IIFE var WhiteboardPromptModal = (function(){ ... return { open: open }; })();
  • Lazy-bind to the markup ids #wb-prompt-modal, #wb-prompt-modal-title/-label/-input/-confirm/-cancel.
  • open(opts) defaults: title Name, label Name, defaultValue '', confirmLabel Create, cancelLabel Cancel. Single-instance guard returns Promise.resolve(null). Sets text, value, save previouslyFocused, adds is-open class, attaches click on backdrop + keydown with {capture:true} (Enter/Escape stop propagation; other keys flow through so typing works and shortcuts.js doesn't fire). setTimeout 0 → input.focus() + input.select(). Buttons bound per call (close over opts.defaultValue). Returns Promise<string|null>.
  • close(value): remove class + listeners, isOpen=false, restore focus, resolve.

Step 3: Add modal markup + script tag (depends on Steps 1-2)

Files: templates/web/board.html

  • After #webframe-url-modal, add <div id="wb-prompt-modal" class="wb-prompt-modal" role="dialog" aria-modal="true" aria-labelledby="wb-prompt-modal-title"> with __dialog/__title/__label/__input/__actions/__btn(Cancel/Confirm).
  • In the scripts block, add <script src="{{ base_path }}/js/whiteboard/prompt_modal.js"></script> immediately above groups.js.

Step 4: Replace prompt() in contextmenu.js (parallelizable with Step 5; depends on Steps 2-3)

Files: static/web/js/whiteboard/contextmenu.js

  • Around line ~104: keep the var selectedNodes = (transformer ? transformer.nodes() : []).slice(); snapshot; replace the action body with WhiteboardPromptModal.open({title:'Group Selected', label:'Group name', defaultValue:'Group', confirmLabel:'Create'}).then(function(name){ if (name === null) return; WhiteboardGroups.createGroup(selectedNodes, name || 'Group'); });selectedNodes is the captured snapshot from the outer scope.

Step 5: Replace prompt() in shortcuts.js (parallelizable with Step 4; depends on Steps 2-3)

Files: static/web/js/whiteboard/shortcuts.js

  • Around line ~117 (Ctrl+G branch): capture var capturedNodes = transformer.nodes().slice(); (same snapshot pattern), then WhiteboardPromptModal.open({...}).then(name => { if (name === null) return; WhiteboardGroups.createGroup(capturedNodes, name || 'Group'); });

Step 6: Build & verify (depends on all)

  • touch crates/hero_whiteboard_admin/src/assets.rs (new file added → rust-embed re-scan), then cargo build --release -p hero_whiteboard_admin.
  • Manual: right-click Group + Ctrl+G both open the themed modal; Enter/Escape/backdrop behavior; theme switch; capture-at-trigger preserved if selection changes before confirm; second-open no-op.

Acceptance Criteria

  • No window.prompt or bare prompt( left in contextmenu.js (~line 104) or shortcuts.js (~line 117).
  • Both right-click "Group Selected" and Ctrl+G open the themed modal.
  • Modal pre-fills with Group and selects the text on open.
  • Enter confirms (trimmed; empty → Group); Escape cancels; backdrop click cancels.
  • WhiteboardGroups.createGroup(capturedNodes, name) is called with the nodes selected at trigger time, even if the user changes selection before confirming.
  • Single-instance guard: a second open() returns Promise<null> and doesn't disturb the first.
  • Focus returns to the previously focused element after close.
  • Theme colors track dark/light via the --wb-* vars.
  • prompt_modal.js loaded before groups.js/contextmenu.js/shortcuts.js. No Bootstrap added.
  • cargo build --release -p hero_whiteboard_admin succeeds.

Notes

  • Reference pattern: existing Web Frame URL modal in board.html + webframe.js. The new modal owns its own keydown listener (registered/unregistered around open/close) — no change to the board.html global Webframe keydown block.
  • webframe.js retains its native-prompt FALLBACK (line ~421) — out of scope for this issue; that path is reachable only when its own themed modal fails. A future refactor could route it through WhiteboardPromptModal too.
  • rust-embed deploy: a NEW static file is added, so touch src/assets.rs before the release build to bust the macro cache.
## Implementation Spec for Issue #205 ### Objective Replace the two native `window.prompt('Group name:', 'Group')` call sites (right-click "Group Selected" in `contextmenu.js` and Ctrl+G in `shortcuts.js`) with a themed, in-board modal. The modal must visually match the rest of the whiteboard (via the existing `--wb-*` CSS variables), capture the selected nodes at trigger time, and route them to `WhiteboardGroups.createGroup(capturedNodes, name)` on confirm. ### Requirements - Themed modal using the existing `--wb-bg`, `--wb-surface`, `--wb-border`, `--wb-text`, `--wb-text-muted`, `--wb-primary`, `--wb-primary-text` CSS variables so dark/light themes work automatically. - Pre-filled with `Group`; input text fully selected on open. - Primary **Create**, secondary **Cancel**; Enter confirms; Escape cancels; backdrop click cancels. - Single-instance guard: a second `open()` while one is already open returns `Promise<null>` without disturbing the first. - Whitespace trimmed; empty falls back to `Group`. - Selection is snapshot at trigger time (Ctrl+G keypress / right-click), not re-derived on confirm — changing the selection between trigger and confirm doesn't affect the result. - Focus returns to the previously focused element after close (close via any path). - No native `prompt()` in the new code path. No Bootstrap added to `board.html`. ### Files to Modify/Create - Create: `crates/hero_whiteboard_admin/static/web/js/whiteboard/prompt_modal.js` — new ES5 IIFE module exposing `WhiteboardPromptModal.open({title,label,defaultValue,confirmLabel,cancelLabel}) -> Promise<string|null>`. Generic helper (cohesive with other in-board modals; reusable for future rename/etc. prompts). - Modify: `crates/hero_whiteboard_admin/templates/web/board.html` — add the prompt-modal markup near the existing `#webframe-url-modal` block; add the new script tag BEFORE `groups.js`/`contextmenu.js`/`shortcuts.js`. - Modify: `crates/hero_whiteboard_admin/static/web/css/whiteboard.css` — small `.wb-prompt-modal*` rules using the `--wb-*` vars. - Modify: `crates/hero_whiteboard_admin/static/web/js/whiteboard/contextmenu.js` — replace `prompt(...)` at line ~104. - Modify: `crates/hero_whiteboard_admin/static/web/js/whiteboard/shortcuts.js` — replace `prompt(...)` at line ~117. ### Implementation Plan #### Step 1: Add CSS for the prompt modal (independent) Files: `static/web/css/whiteboard.css` - Append rules for `.wb-prompt-modal` (backdrop, `position:fixed; inset:0; rgba(0,0,0,.5); z-index:9999; display:none; align-items:center; justify-content:center;` + `.is-open { display:flex; }`), `.wb-prompt-modal__dialog` (uses `--wb-surface/--wb-border/--wb-text`), `__title/__label/__input/__actions/__btn/__btn--primary` styled with the `--wb-*` vars. Dependencies: none #### Step 2: Create the helper module (independent from Step 1; needed by Steps 3-5) Files: `static/web/js/whiteboard/prompt_modal.js` - ES5 IIFE `var WhiteboardPromptModal = (function(){ ... return { open: open }; })();` - Lazy-bind to the markup ids `#wb-prompt-modal`, `#wb-prompt-modal-title/-label/-input/-confirm/-cancel`. - `open(opts)` defaults: title `Name`, label `Name`, defaultValue `''`, confirmLabel `Create`, cancelLabel `Cancel`. Single-instance guard returns `Promise.resolve(null)`. Sets text, value, save `previouslyFocused`, adds `is-open` class, attaches `click` on backdrop + `keydown` with `{capture:true}` (Enter/Escape stop propagation; other keys flow through so typing works and `shortcuts.js` doesn't fire). `setTimeout` 0 → `input.focus()` + `input.select()`. Buttons bound per call (close over `opts.defaultValue`). Returns `Promise<string|null>`. - `close(value)`: remove class + listeners, `isOpen=false`, restore focus, resolve. #### Step 3: Add modal markup + script tag (depends on Steps 1-2) Files: `templates/web/board.html` - After `#webframe-url-modal`, add `<div id="wb-prompt-modal" class="wb-prompt-modal" role="dialog" aria-modal="true" aria-labelledby="wb-prompt-modal-title">` with `__dialog/__title/__label/__input/__actions/__btn(Cancel/Confirm)`. - In the scripts block, add `<script src="{{ base_path }}/js/whiteboard/prompt_modal.js"></script>` immediately above `groups.js`. #### Step 4: Replace `prompt()` in contextmenu.js (parallelizable with Step 5; depends on Steps 2-3) Files: `static/web/js/whiteboard/contextmenu.js` - Around line ~104: keep the `var selectedNodes = (transformer ? transformer.nodes() : []).slice();` snapshot; replace the `action` body with `WhiteboardPromptModal.open({title:'Group Selected', label:'Group name', defaultValue:'Group', confirmLabel:'Create'}).then(function(name){ if (name === null) return; WhiteboardGroups.createGroup(selectedNodes, name || 'Group'); });` — `selectedNodes` is the captured snapshot from the outer scope. #### Step 5: Replace `prompt()` in shortcuts.js (parallelizable with Step 4; depends on Steps 2-3) Files: `static/web/js/whiteboard/shortcuts.js` - Around line ~117 (Ctrl+G branch): capture `var capturedNodes = transformer.nodes().slice();` (same snapshot pattern), then `WhiteboardPromptModal.open({...}).then(name => { if (name === null) return; WhiteboardGroups.createGroup(capturedNodes, name || 'Group'); });` #### Step 6: Build & verify (depends on all) - `touch crates/hero_whiteboard_admin/src/assets.rs` (new file added → rust-embed re-scan), then `cargo build --release -p hero_whiteboard_admin`. - Manual: right-click Group + Ctrl+G both open the themed modal; Enter/Escape/backdrop behavior; theme switch; capture-at-trigger preserved if selection changes before confirm; second-open no-op. ### Acceptance Criteria - [ ] No `window.prompt` or bare `prompt(` left in `contextmenu.js` (~line 104) or `shortcuts.js` (~line 117). - [ ] Both right-click "Group Selected" and Ctrl+G open the themed modal. - [ ] Modal pre-fills with `Group` and selects the text on open. - [ ] Enter confirms (trimmed; empty → `Group`); Escape cancels; backdrop click cancels. - [ ] `WhiteboardGroups.createGroup(capturedNodes, name)` is called with the nodes selected at trigger time, even if the user changes selection before confirming. - [ ] Single-instance guard: a second `open()` returns `Promise<null>` and doesn't disturb the first. - [ ] Focus returns to the previously focused element after close. - [ ] Theme colors track dark/light via the `--wb-*` vars. - [ ] `prompt_modal.js` loaded before `groups.js`/`contextmenu.js`/`shortcuts.js`. No Bootstrap added. - [ ] `cargo build --release -p hero_whiteboard_admin` succeeds. ### Notes - Reference pattern: existing Web Frame URL modal in `board.html` + `webframe.js`. The new modal owns its own keydown listener (registered/unregistered around open/close) — no change to the board.html global Webframe keydown block. - `webframe.js` retains its native-prompt FALLBACK (line ~421) — out of scope for this issue; that path is reachable only when its own themed modal fails. A future refactor could route it through `WhiteboardPromptModal` too. - rust-embed deploy: a NEW static file is added, so touch `src/assets.rs` before the release build to bust the macro cache.
Author
Owner

Test Results

  • Workspace lib tests (cargo test --workspace --lib): PASS (0 passed; 0 failed across hero_whiteboard_admin, hero_whiteboard_examples, hero_whiteboard_sdk)
  • JS syntax (node --check): prompt_modal.js OK, contextmenu.js OK, shortcuts.js OK
  • prompt_modal.js encoding: UTF-8 text, control-byte count = 0
  • No remaining prompt( calls in contextmenu.js or shortcuts.js: yes

Note: change is CSS/HTML/JS-only (no Rust source changed); workspace lib tests run as a regression guard.

## Test Results - Workspace lib tests (cargo test --workspace --lib): PASS (0 passed; 0 failed across hero_whiteboard_admin, hero_whiteboard_examples, hero_whiteboard_sdk) - JS syntax (node --check): prompt_modal.js OK, contextmenu.js OK, shortcuts.js OK - prompt_modal.js encoding: UTF-8 text, control-byte count = 0 - No remaining `prompt(` calls in contextmenu.js or shortcuts.js: yes Note: change is CSS/HTML/JS-only (no Rust source changed); workspace lib tests run as a regression guard.
Author
Owner

Implementation Summary

Replaced the native window.prompt for group naming with a themed in-board modal.

Changes

  • New crates/hero_whiteboard_admin/static/web/js/whiteboard/prompt_modal.js — reusable ES5 IIFE exposing WhiteboardPromptModal.open({ title, label, defaultValue, confirmLabel, cancelLabel }) -> Promise<string|null>. Lazy DOM lookup, single-instance guard, focus + select on open, focus restoration on close, capture-phase Enter/Escape handler that doesn't block typing.
  • crates/hero_whiteboard_admin/static/web/css/whiteboard.css — added .wb-prompt-modal* rules using the existing --wb-* theme variables so dark/light themes are inherited automatically.
  • crates/hero_whiteboard_admin/templates/web/board.html — added the modal markup near the existing Web Frame URL modal, and a <script> tag loading prompt_modal.js before groups.js / contextmenu.js / shortcuts.js.
  • crates/hero_whiteboard_admin/static/web/js/whiteboard/contextmenu.js — replaced the prompt('Group name:', 'Group') in the right-click "Group Selected" action.
  • crates/hero_whiteboard_admin/static/web/js/whiteboard/shortcuts.js — replaced the prompt('Group name:', 'Group') in the Ctrl+G handler.
  • Both call sites snapshot the selected nodes at trigger time and pass that snapshot into WhiteboardGroups.createGroup on confirm; cancel/Escape/backdrop click no-ops.

Behavior

  • Modal is pre-filled with Group, input selected on open.
  • Enter confirms (whitespace trimmed; empty falls back to Group); Escape and backdrop click cancel.
  • Single-instance guard: a second open() returns Promise<null> without disturbing the first.
  • Theme colors track dark/light through the existing --wb-* variables.
  • No more prompt( calls in contextmenu.js or shortcuts.js.

Test results

  • cargo test --workspace --lib: compiled cleanly, no failures (change is CSS/HTML/JS-only; runs as a regression guard).
  • node --check passed for prompt_modal.js, contextmenu.js, shortcuts.js.
  • prompt_modal.js verified UTF-8 with zero control bytes.
  • Rebuilt and redeployed; the modal markup, script tag, and updated call sites are served.
## Implementation Summary Replaced the native `window.prompt` for group naming with a themed in-board modal. ### Changes - New `crates/hero_whiteboard_admin/static/web/js/whiteboard/prompt_modal.js` — reusable ES5 IIFE exposing `WhiteboardPromptModal.open({ title, label, defaultValue, confirmLabel, cancelLabel }) -> Promise<string|null>`. Lazy DOM lookup, single-instance guard, focus + select on open, focus restoration on close, capture-phase Enter/Escape handler that doesn't block typing. - `crates/hero_whiteboard_admin/static/web/css/whiteboard.css` — added `.wb-prompt-modal*` rules using the existing `--wb-*` theme variables so dark/light themes are inherited automatically. - `crates/hero_whiteboard_admin/templates/web/board.html` — added the modal markup near the existing Web Frame URL modal, and a `<script>` tag loading `prompt_modal.js` before `groups.js` / `contextmenu.js` / `shortcuts.js`. - `crates/hero_whiteboard_admin/static/web/js/whiteboard/contextmenu.js` — replaced the `prompt('Group name:', 'Group')` in the right-click "Group Selected" action. - `crates/hero_whiteboard_admin/static/web/js/whiteboard/shortcuts.js` — replaced the `prompt('Group name:', 'Group')` in the Ctrl+G handler. - Both call sites snapshot the selected nodes at trigger time and pass that snapshot into `WhiteboardGroups.createGroup` on confirm; cancel/Escape/backdrop click no-ops. ### Behavior - Modal is pre-filled with `Group`, input selected on open. - Enter confirms (whitespace trimmed; empty falls back to `Group`); Escape and backdrop click cancel. - Single-instance guard: a second `open()` returns `Promise<null>` without disturbing the first. - Theme colors track dark/light through the existing `--wb-*` variables. - No more `prompt(` calls in `contextmenu.js` or `shortcuts.js`. ### Test results - `cargo test --workspace --lib`: compiled cleanly, no failures (change is CSS/HTML/JS-only; runs as a regression guard). - `node --check` passed for `prompt_modal.js`, `contextmenu.js`, `shortcuts.js`. - `prompt_modal.js` verified UTF-8 with zero control bytes. - Rebuilt and redeployed; the modal markup, script tag, and updated call sites are served.
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#205
No description provided.