feat(emoji): Miro-style emoji picker tool #44

Merged
AhmedHanafy725 merged 3 commits from development_emoji_picker_tool into development 2026-04-22 09:21:24 +00:00
Member

Summary

Adds a Miro-style emoji picker tool: a new toolbar button opens a popover with search + 9 category tabs + ~300 curated emojis. Picking an emoji and clicking the canvas drops it as a first-class emoji object — draggable, resizable, rotatable, deletable, erasable, rubber-band selectable, undo/redo, multi-user syncable, and persistent across reload. Zero server changes; reuses the existing objects table with type='emoji' and data={char, fontSize}.

Closes #43

Changes

  • New module: crates/hero_whiteboard_ui/static/web/js/whiteboard/emoji_picker.js — IIFE with 9 categories x ~33 emojis (~300 total), a small keyword map for search, tabs, an 8-column grid, outside-click-to-close, re-positioning to sit next to the toolbar button.
  • templates/web/board.html — new toolbar button, picker container div, script include.
  • toolbar.js — when the emoji tool is activated, open the picker; when switched away, close it.
  • app.jsWhiteboardEmojiPicker.init() and case 'emoji': in createObjectFromData (restores char, fontSize, width/height on load).
  • objects.jscreateEmoji(x, y, opts) builds a Konva.Group name='object emoji' containing a Konva.Text (font-family stack: Apple/Segoe/Noto Color Emoji) + an invisible .bg hit rect; new applyTransform branch enforces uniform scale via min(scaleX, scaleY) on the fontSize, keeps .bg in sync; exported in the return object.
  • tools.jsemojiChar state; new 'emoji' branch in onMouseDown: drops the armed emoji at the click position, closes the picker, switches back to select; setEmojiChar / getEmojiChar exports.
  • shortcuts.js':' opens the picker (subject to the existing INPUT/TEXTAREA/contentEditable guard); Escape closes the picker first before falling through to deselect.
  • sync.js — serialize reads _emojiChar / _emojiFontSize off the group; applySyncUpdate applies incoming char/fontSize changes to the Konva text and resizes the .bg rect.
  • whiteboard.css.wb-emoji-picker (popover + search + tabs + grid + cell) styles using theme vars.

Test Results

  • cargo check --workspace: pass
  • cargo clippy --workspace -- -D warnings: pass
  • cargo fmt --check: pass

Entirely vanilla JS / CSS / HTML; UI behavior needs manual verification in the browser.

Manual verification

  • New smiley button appears next to the eraser in the toolbar.
  • Clicking the button opens the picker popover next to it, with search + 9 tabs + emoji grid.
  • Clicking a grid cell highlights it; clicking the canvas drops the emoji at that point, picker closes, tool returns to select.
  • Dropped emoji can be dragged, resized (stays square), rotated, deleted with Delete/Backspace, erased, rubber-band selected, copy/paste/duplicated.
  • Ctrl+Z/Ctrl+Y undo/redo works.
  • Reload: all emojis reappear at saved position, size, rotation, char.
  • Multi-user: emoji create/move/resize/delete propagates live via WebSocket.
  • Typing : on the canvas opens the picker; typing : inside a sticky/text/document editor does NOT.
  • Escape closes the picker without switching tools.
  • Clicking outside the picker (not on the canvas) closes it.
## Summary Adds a Miro-style emoji picker tool: a new toolbar button opens a popover with search + 9 category tabs + ~300 curated emojis. Picking an emoji and clicking the canvas drops it as a first-class `emoji` object — draggable, resizable, rotatable, deletable, erasable, rubber-band selectable, undo/redo, multi-user syncable, and persistent across reload. Zero server changes; reuses the existing `objects` table with `type='emoji'` and `data={char, fontSize}`. ## Related Issue Closes https://forge.ourworld.tf/lhumina_code/hero_whiteboard/issues/43 ## Changes - New module: `crates/hero_whiteboard_ui/static/web/js/whiteboard/emoji_picker.js` — IIFE with 9 categories x ~33 emojis (~300 total), a small keyword map for search, tabs, an 8-column grid, outside-click-to-close, re-positioning to sit next to the toolbar button. - `templates/web/board.html` — new toolbar button, picker container div, script include. - `toolbar.js` — when the emoji tool is activated, open the picker; when switched away, close it. - `app.js` — `WhiteboardEmojiPicker.init()` and `case 'emoji':` in `createObjectFromData` (restores `char`, `fontSize`, width/height on load). - `objects.js` — `createEmoji(x, y, opts)` builds a `Konva.Group name='object emoji'` containing a `Konva.Text` (font-family stack: Apple/Segoe/Noto Color Emoji) + an invisible `.bg` hit rect; new `applyTransform` branch enforces uniform scale via `min(scaleX, scaleY)` on the fontSize, keeps `.bg` in sync; exported in the return object. - `tools.js` — `emojiChar` state; new `'emoji'` branch in `onMouseDown`: drops the armed emoji at the click position, closes the picker, switches back to select; `setEmojiChar` / `getEmojiChar` exports. - `shortcuts.js` — `':'` opens the picker (subject to the existing INPUT/TEXTAREA/contentEditable guard); `Escape` closes the picker first before falling through to deselect. - `sync.js` — serialize reads `_emojiChar` / `_emojiFontSize` off the group; applySyncUpdate applies incoming char/fontSize changes to the Konva text and resizes the `.bg` rect. - `whiteboard.css` — `.wb-emoji-picker` (popover + search + tabs + grid + cell) styles using theme vars. ## Test Results - `cargo check --workspace`: pass - `cargo clippy --workspace -- -D warnings`: pass - `cargo fmt --check`: pass Entirely vanilla JS / CSS / HTML; UI behavior needs manual verification in the browser. ## Manual verification - [ ] New smiley button appears next to the eraser in the toolbar. - [ ] Clicking the button opens the picker popover next to it, with search + 9 tabs + emoji grid. - [ ] Clicking a grid cell highlights it; clicking the canvas drops the emoji at that point, picker closes, tool returns to select. - [ ] Dropped emoji can be dragged, resized (stays square), rotated, deleted with Delete/Backspace, erased, rubber-band selected, copy/paste/duplicated. - [ ] Ctrl+Z/Ctrl+Y undo/redo works. - [ ] Reload: all emojis reappear at saved position, size, rotation, char. - [ ] Multi-user: emoji create/move/resize/delete propagates live via WebSocket. - [ ] Typing `:` on the canvas opens the picker; typing `:` inside a sticky/text/document editor does NOT. - [ ] Escape closes the picker without switching tools. - [ ] Clicking outside the picker (not on the canvas) closes it.
feat(emoji): Miro-style emoji picker tool
All checks were successful
CI / build (pull_request) Successful in 2m12s
44c5c02202
New emoji-smile toolbar button opens a picker popover with search, 9
category tabs (~300 curated emojis), and click-to-place. After picking
an emoji the next canvas click drops it at that point as a first-class
'emoji' object (Konva.Text inside a Group with an invisible .bg hit rect
so the generic transformer / rubber-band / eraser / applyTransform paths
all work without special cases).

- New module: emoji_picker.js (IIFE, no external deps).
- Uniform resize: applyTransform takes min(scaleX, scaleY) to keep the
  glyph square.
- Persistence: reuses the existing objects table — type='emoji',
  data={char, fontSize}. No server changes.
- Keyboard: ':' opens the picker (when no input is focused); Escape
  closes it without falling through to deselect.
- Styling: new .wb-emoji-picker CSS; theme vars re-used.

#43
fix(emoji): don't close picker on canvas click — let the stage place first
All checks were successful
CI / build (pull_request) Successful in 2m10s
57e72e0e23
The outside-click handler ran on the document capture phase and fired
before the Konva stage mousedown bubbled, so close() cleared emojiChar
via setEmojiChar(null) before the 'emoji' branch in tools.js onMouseDown
could read it. Result: clicking the canvas placed nothing.

Skip closing when the click target is inside #whiteboard-container.
The stage mousedown drops the emoji and then tools.js calls
WhiteboardEmojiPicker.close() explicitly.

#43
feat(emoji): add Recent tab backed by localStorage
All checks were successful
CI / build (pull_request) Successful in 2m11s
e1ae79b1cf
Adds a 'Recent' category as the first tab in the emoji picker, backed
by localStorage key wb.emoji.recent (cap 24). Every time an emoji is
placed on the canvas, it moves to the front (dedupe + LRU). The Recent
tab becomes the default active tab when history is non-empty.

Shows a helper message in the grid when Recent is active and empty.

#43
AhmedHanafy725 merged commit 425218d673 into development 2026-04-22 09:21:24 +00:00
AhmedHanafy725 deleted branch development_emoji_picker_tool 2026-04-22 09:21:29 +00:00
Sign in to join this conversation.
No reviewers
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!44
No description provided.