Presentation: explicit frame ordering with reorder controls and slide-number badges #95

Open
opened 2026-04-29 06:59:19 +00:00 by AhmedHanafy725 · 3 comments
Member

Problem

In presentation mode, WhiteboardFrames.getFrames() (frames.js:7-12) returns frames in layer.find('.frame') order -- effectively z-index / DOM order. Users have no way to control which frame is slide #1, #2, etc., and no way to reorder them. The first frame they happen to drop ends up first in the deck whether they like it or not.

Now that issue #93 added a 1 / N counter and prev/next buttons, the lack of explicit order is more visible -- the user sees "3 / 5" but has no control over what "3" means.

Expected behavior

  • Each frame stores an explicit numeric order (separate from z-index). getFrames() sorts by order ascending; new frames default to order = (max existing order) + 1 so they land at the end of the deck.
  • A small slide-number badge is rendered at the top-left of each frame on the canvas (e.g. "1", "2") so users can see the deck order at a glance, mirroring Miro's frame numbering.
  • Reorder controls are available on a selected frame:
    • Move-up / Move-down arrows in the right-side property panel.
    • "Move slide up" / "Move slide down" entries in the right-click context menu.
  • Reorder swaps the order value with the adjacent frame (or shifts a block) and persists via the existing object.update RPC + WebSocket sync.
  • Other windows on the same board pick up the reorder via the existing sync round-trip.
  • The presentation control bar's X / N counter and the prev/next disabled states (#93) reflect the new order.

Affected files (expected)

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js::createFrame -- store an order value in the frame's data blob; render a small numbered badge on the frame.
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/frames.js -- sort getFrames() by order; expose moveFrameUp(id) / moveFrameDown(id) helpers that swap the order with the neighbor and trigger sync.
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/properties.js::buildFrameProps -- add up/down arrow buttons.
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/contextmenu.js -- add Move-up / Move-down entries when a frame is selected.
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/sync.js -- include data.order in the frame serialize/apply round-trip; redraw the order badge when the field changes.

No server / SDK / openrpc / DB changes (the data JSON blob is already round-tripped).

Acceptance criteria

  • Each frame stores an order value; new frames default to (max existing) + 1.
  • getFrames() returns frames sorted by order.
  • A slide-number badge appears at the top-left of each frame on the canvas.
  • Property-panel arrows Move up / Move down a selected frame.
  • Right-click context menu has Move-slide-up / Move-slide-down entries.
  • Reorder swaps order with the adjacent frame and persists across windows.
  • Presentation counter and prev/next disabled state reflect the new order.
  • Existing boards (frames without an order field) get a stable default order (assigned at first read by id ascending) so they don't jump around.
  • cargo check / clippy / fmt --check / test clean.

Notes

  • Don't introduce a sidebar / Frames panel in this iteration -- the property-panel arrows + context menu + on-canvas badges are enough.
  • The badge should be visually quiet (small, muted) so it doesn't compete with the frame's title or the user's content. Place it just above the frame's top-left corner alongside the title; size ~11 px.
  • If the user has duplicate order values from a buggy import, ties are broken by id ascending so the order is deterministic.
## Problem In presentation mode, `WhiteboardFrames.getFrames()` (`frames.js:7-12`) returns frames in `layer.find('.frame')` order -- effectively z-index / DOM order. Users have no way to control which frame is slide #1, #2, etc., and no way to reorder them. The first frame they happen to drop ends up first in the deck whether they like it or not. Now that issue #93 added a `1 / N` counter and prev/next buttons, the lack of explicit order is more visible -- the user sees "3 / 5" but has no control over what "3" means. ## Expected behavior - Each frame stores an explicit numeric `order` (separate from z-index). `getFrames()` sorts by `order` ascending; new frames default to `order = (max existing order) + 1` so they land at the end of the deck. - A small slide-number badge is rendered at the top-left of each frame on the canvas (e.g. "1", "2") so users can see the deck order at a glance, mirroring Miro's frame numbering. - Reorder controls are available on a selected frame: - Move-up / Move-down arrows in the right-side property panel. - "Move slide up" / "Move slide down" entries in the right-click context menu. - Reorder swaps the `order` value with the adjacent frame (or shifts a block) and persists via the existing `object.update` RPC + WebSocket sync. - Other windows on the same board pick up the reorder via the existing sync round-trip. - The presentation control bar's `X / N` counter and the prev/next disabled states (#93) reflect the new order. ## Affected files (expected) - `crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js::createFrame` -- store an `order` value in the frame's `data` blob; render a small numbered badge on the frame. - `crates/hero_whiteboard_ui/static/web/js/whiteboard/frames.js` -- sort `getFrames()` by `order`; expose `moveFrameUp(id)` / `moveFrameDown(id)` helpers that swap the order with the neighbor and trigger sync. - `crates/hero_whiteboard_ui/static/web/js/whiteboard/properties.js::buildFrameProps` -- add up/down arrow buttons. - `crates/hero_whiteboard_ui/static/web/js/whiteboard/contextmenu.js` -- add Move-up / Move-down entries when a frame is selected. - `crates/hero_whiteboard_ui/static/web/js/whiteboard/sync.js` -- include `data.order` in the frame serialize/apply round-trip; redraw the order badge when the field changes. No server / SDK / openrpc / DB changes (the `data` JSON blob is already round-tripped). ## Acceptance criteria - [ ] Each frame stores an `order` value; new frames default to `(max existing) + 1`. - [ ] `getFrames()` returns frames sorted by `order`. - [ ] A slide-number badge appears at the top-left of each frame on the canvas. - [ ] Property-panel arrows Move up / Move down a selected frame. - [ ] Right-click context menu has Move-slide-up / Move-slide-down entries. - [ ] Reorder swaps `order` with the adjacent frame and persists across windows. - [ ] Presentation counter and prev/next disabled state reflect the new order. - [ ] Existing boards (frames without an `order` field) get a stable default order (assigned at first read by id ascending) so they don't jump around. - [ ] `cargo check / clippy / fmt --check / test` clean. ## Notes - Don't introduce a sidebar / Frames panel in this iteration -- the property-panel arrows + context menu + on-canvas badges are enough. - The badge should be visually quiet (small, muted) so it doesn't compete with the frame's title or the user's content. Place it just above the frame's top-left corner alongside the title; size ~11 px. - If the user has duplicate `order` values from a buggy import, ties are broken by `id` ascending so the order is deterministic.
Author
Member

Implementation Spec for Issue #95

Objective

Give the user explicit control over presentation order. Each frame stores a numeric data.order; WhiteboardFrames.getFrames() sorts by it; a small slide-number badge appears at the top-left of every frame on the canvas; the property panel and the right-click context menu let the user move a selected frame up or down in the deck. Reorder syncs to other windows via the existing object.update round-trip.

Requirements

  • data.order is a finite integer stored on each frame.
  • New frames default to (max existing order) + 1 so they land at the end of the deck.
  • Existing frames without order are stable-ordered by ascending id at first read (no jumps).
  • WhiteboardFrames.getFrames() returns frames sorted by (order, id) ascending.
  • A small numbered badge is rendered next to each frame's title (top-left, just outside the dashed bg). Updates whenever the deck reorders.
  • Property-panel arrows (Move up / Move down) move the selected frame in the deck.
  • Right-click context menu has Move-slide-up / Move-slide-down entries when the targeted object is a frame.
  • Reorder swaps order with the adjacent frame in deck order and persists via WhiteboardSync.onUpdate.
  • Other windows on the same board receive the new order via the existing sync round-trip and re-render their badges.
  • Presentation control bar's X / N counter and prev/next disabled states pick up the new order automatically (they already query WhiteboardFrames.getCurrentIndex() / getTotal()).
  • Move-up at the first slide is a no-op (button disabled). Move-down at the last slide is a no-op (button disabled).
  • Diff stays inside objects.js, frames.js, properties.js, contextmenu.js, sync.js.
  • cargo check / clippy / fmt --check / test clean.

Files to Modify

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js — store data.order on the frame; render a .frame-order-badge Konva.Text node next to the label; expose redrawAllFrameBadges() so the order display can refresh after a swap.
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/frames.js — sort getFrames() by (order, id); expose moveFrameUp(node) / moveFrameDown(node) helpers that swap order with the adjacent frame, refresh badges, and trigger sync.
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/properties.js — extend buildFrameProps with up/down arrow buttons; wire them to moveFrameUp / moveFrameDown.
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/contextmenu.js — when the right-clicked object is a frame, append Move-slide-up / Move-slide-down entries.
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/sync.js — include data.order in the frame serialize/apply paths and refresh the badge on receive.

No server / SDK / openrpc / DB changes — data is already a JSON blob round-tripped end-to-end.

Implementation Plan

Step 1: Frame model + badge rendering

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

  1. In createFrame(x, y, options):
    • Read opts.order if provided; otherwise compute (max existing order over all frames) + 1. Store as group._order = value and stamp the frame group's data via group.setAttr('frameOrder', value) for sync visibility.
    • Create a small Konva.Text named 'order-badge', positioned at (-12, -20) (left of the existing title at (8, -20)), with fontSize: 11, fill: 'var-equivalent like #adb5bd', name: 'order-badge'. Initial text is the badge number which is derived from the sort position.
    • Add the badge to the group AFTER the label.
  2. Add a redrawFrameBadge(group, indexInDeck) helper that finds the badge child and sets its text to String(indexInDeck + 1).
  3. Add a public redrawAllFrameBadges() that walks WhiteboardFrames.getFrames() (now sorted) and calls redrawFrameBadge(frame, i) for each, then batchDraw().
  4. Call redrawAllFrameBadges() at the end of createFrame so the new frame's badge plus any sibling badges that shifted are correct.
  5. The existing dblclick rename / drag handlers are unaffected.

Step 2: Sort + reorder helpers

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

  1. Update getFrames() to:
    • Walk the layer's frames as today.
    • Read each frame's _order (falling back to Number(frame.id()) as a stable id-based default for legacy rows).
    • Sort ascending by (order, id).
  2. Add moveFrameUp(frame):
    • Build the sorted list. Find the index of frame. If 0, no-op.
    • Swap _order with the previous frame; update both groups' setAttr('frameOrder', ...).
    • Call WhiteboardObjects.redrawAllFrameBadges() and WhiteboardSync.onUpdate(...) for both frames.
  3. Add moveFrameDown(frame) symmetrically.
  4. Export moveFrameUp, moveFrameDown.

Step 3: Property-panel arrows

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

  1. Extend buildFrameProps(node, objData):
    • After the existing Title input, add a Slide order block with two buttons: ▲ Move up and ▼ Move down. Both class="prop-input"-styled but as buttons. Wrap in a small flex row.
    • Mark the buttons with ids prop-frame-move-up and prop-frame-move-down.
  2. In the events-binding section (alongside the existing frameTitleInput listener):
    • Bind click on #prop-frame-move-up to WhiteboardFrames.moveFrameUp(currentNode).
    • Bind click on #prop-frame-move-down to WhiteboardFrames.moveFrameDown(currentNode).
    • After the call, disable the appropriate button if the frame is now at the deck edge (re-build props after move OR query the new index).

Step 4: Context-menu entries

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

  1. In the per-object menu builder (after the existing objData.type === 'mindmap' block, before the Group/Ungroup block), add:
    if (objData && objData.type === 'frame') {
        items.push({ label: 'Move slide up', icon: 'bi-arrow-up', action: function() { WhiteboardFrames.moveFrameUp(targetNode); } });
        items.push({ label: 'Move slide down', icon: 'bi-arrow-down', action: function() { WhiteboardFrames.moveFrameDown(targetNode); } });
        items.push({ divider: true });
    }
    

Step 5: Sync round-trip

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

  1. In the frame branch of serializeForServer (~line 261), include data.order = node.getAttr('frameOrder') (or read from node._order).
  2. In the frame branch of applySyncUpdate (~line 574), if data && typeof data.order === 'number', set node._order = data.order; node.setAttr('frameOrder', data.order). Then call WhiteboardObjects.redrawAllFrameBadges() so the receiving window's badges are correct.

Acceptance Criteria

  • Each frame stores data.order; new frames default to (max existing) + 1.
  • WhiteboardFrames.getFrames() returns frames sorted by (order, id) ascending.
  • A slide-number badge is rendered next to each frame's title and updates after a reorder.
  • The property panel for a selected frame shows Move-up / Move-down buttons; clicking them reorders the deck.
  • Right-click on a frame shows Move-slide-up / Move-slide-down entries.
  • Reorder syncs to other windows; their badges update.
  • Presentation counter and prev/next disabled state reflect the new order.
  • Boards with frames missing order get a stable id-ascending default order at first read; no jumps.
  • Move-up at index 0 is a no-op; move-down at the last slide is a no-op.
  • No regression in frame create / drag / resize / rename / drag-with-children.
  • cargo check / clippy / fmt --check / test clean.

Notes

  • The badge is a child of the frame group, so it moves with the frame when dragged. Its position is (label.x() - 18, label.y()) so it sits left of the title without overlapping.
  • Don't re-render every frame on every reorder — just update the two badges that swapped (or call redrawAllFrameBadges which is cheap because frames are few).
  • The getAttr('frameOrder') storage choice keeps the order on the Konva node (so it's survives across re-renders) and makes the sync round-trip a single read/write.
  • Don't introduce a sidebar / Frames panel in this iteration — that's a bigger UI surface and not required for the basic reorder UX.
  • Backward compatibility: existing boards' frames without order get a stable default order at read time. The default is Number(frame.id()). After the first reorder, the frames are written back with explicit order values via the sync round-trip.
## Implementation Spec for Issue #95 ### Objective Give the user explicit control over presentation order. Each frame stores a numeric `data.order`; `WhiteboardFrames.getFrames()` sorts by it; a small slide-number badge appears at the top-left of every frame on the canvas; the property panel and the right-click context menu let the user move a selected frame up or down in the deck. Reorder syncs to other windows via the existing `object.update` round-trip. ### Requirements - `data.order` is a finite integer stored on each frame. - New frames default to `(max existing order) + 1` so they land at the end of the deck. - Existing frames without `order` are stable-ordered by ascending id at first read (no jumps). - `WhiteboardFrames.getFrames()` returns frames sorted by `(order, id)` ascending. - A small numbered badge is rendered next to each frame's title (top-left, just outside the dashed bg). Updates whenever the deck reorders. - Property-panel arrows (Move up / Move down) move the selected frame in the deck. - Right-click context menu has Move-slide-up / Move-slide-down entries when the targeted object is a frame. - Reorder swaps `order` with the adjacent frame in deck order and persists via `WhiteboardSync.onUpdate`. - Other windows on the same board receive the new order via the existing sync round-trip and re-render their badges. - Presentation control bar's `X / N` counter and prev/next disabled states pick up the new order automatically (they already query `WhiteboardFrames.getCurrentIndex()` / `getTotal()`). - Move-up at the first slide is a no-op (button disabled). Move-down at the last slide is a no-op (button disabled). - Diff stays inside `objects.js`, `frames.js`, `properties.js`, `contextmenu.js`, `sync.js`. - `cargo check / clippy / fmt --check / test` clean. ### Files to Modify - `crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js` — store `data.order` on the frame; render a `.frame-order-badge` Konva.Text node next to the label; expose `redrawAllFrameBadges()` so the order display can refresh after a swap. - `crates/hero_whiteboard_ui/static/web/js/whiteboard/frames.js` — sort `getFrames()` by `(order, id)`; expose `moveFrameUp(node)` / `moveFrameDown(node)` helpers that swap `order` with the adjacent frame, refresh badges, and trigger sync. - `crates/hero_whiteboard_ui/static/web/js/whiteboard/properties.js` — extend `buildFrameProps` with up/down arrow buttons; wire them to `moveFrameUp` / `moveFrameDown`. - `crates/hero_whiteboard_ui/static/web/js/whiteboard/contextmenu.js` — when the right-clicked object is a frame, append Move-slide-up / Move-slide-down entries. - `crates/hero_whiteboard_ui/static/web/js/whiteboard/sync.js` — include `data.order` in the frame serialize/apply paths and refresh the badge on receive. No server / SDK / openrpc / DB changes — `data` is already a JSON blob round-tripped end-to-end. ### Implementation Plan #### Step 1: Frame model + badge rendering Files: `crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js` 1. In `createFrame(x, y, options)`: - Read `opts.order` if provided; otherwise compute `(max existing order over all frames) + 1`. Store as `group._order = value` and stamp the frame group's data via `group.setAttr('frameOrder', value)` for sync visibility. - Create a small `Konva.Text` named `'order-badge'`, positioned at `(-12, -20)` (left of the existing title at `(8, -20)`), with `fontSize: 11`, `fill: 'var-equivalent like #adb5bd'`, `name: 'order-badge'`. Initial text is the badge number which is derived from the sort position. - Add the badge to the group AFTER the label. 2. Add a `redrawFrameBadge(group, indexInDeck)` helper that finds the badge child and sets its text to `String(indexInDeck + 1)`. 3. Add a public `redrawAllFrameBadges()` that walks `WhiteboardFrames.getFrames()` (now sorted) and calls `redrawFrameBadge(frame, i)` for each, then `batchDraw()`. 4. Call `redrawAllFrameBadges()` at the end of `createFrame` so the new frame's badge plus any sibling badges that shifted are correct. 5. The existing `dblclick` rename / drag handlers are unaffected. #### Step 2: Sort + reorder helpers Files: `crates/hero_whiteboard_ui/static/web/js/whiteboard/frames.js` 1. Update `getFrames()` to: - Walk the layer's frames as today. - Read each frame's `_order` (falling back to `Number(frame.id())` as a stable id-based default for legacy rows). - Sort ascending by `(order, id)`. 2. Add `moveFrameUp(frame)`: - Build the sorted list. Find the index of `frame`. If 0, no-op. - Swap `_order` with the previous frame; update both groups' `setAttr('frameOrder', ...)`. - Call `WhiteboardObjects.redrawAllFrameBadges()` and `WhiteboardSync.onUpdate(...)` for both frames. 3. Add `moveFrameDown(frame)` symmetrically. 4. Export `moveFrameUp`, `moveFrameDown`. #### Step 3: Property-panel arrows Files: `crates/hero_whiteboard_ui/static/web/js/whiteboard/properties.js` 1. Extend `buildFrameProps(node, objData)`: - After the existing Title input, add a `Slide order` block with two buttons: `▲ Move up` and `▼ Move down`. Both `class="prop-input"`-styled but as buttons. Wrap in a small flex row. - Mark the buttons with ids `prop-frame-move-up` and `prop-frame-move-down`. 2. In the events-binding section (alongside the existing `frameTitleInput` listener): - Bind click on `#prop-frame-move-up` to `WhiteboardFrames.moveFrameUp(currentNode)`. - Bind click on `#prop-frame-move-down` to `WhiteboardFrames.moveFrameDown(currentNode)`. - After the call, disable the appropriate button if the frame is now at the deck edge (re-build props after move OR query the new index). #### Step 4: Context-menu entries Files: `crates/hero_whiteboard_ui/static/web/js/whiteboard/contextmenu.js` 1. In the per-object menu builder (after the existing `objData.type === 'mindmap'` block, before the Group/Ungroup block), add: ```js if (objData && objData.type === 'frame') { items.push({ label: 'Move slide up', icon: 'bi-arrow-up', action: function() { WhiteboardFrames.moveFrameUp(targetNode); } }); items.push({ label: 'Move slide down', icon: 'bi-arrow-down', action: function() { WhiteboardFrames.moveFrameDown(targetNode); } }); items.push({ divider: true }); } ``` #### Step 5: Sync round-trip Files: `crates/hero_whiteboard_ui/static/web/js/whiteboard/sync.js` 1. In the frame branch of `serializeForServer` (~line 261), include `data.order = node.getAttr('frameOrder')` (or read from `node._order`). 2. In the frame branch of `applySyncUpdate` (~line 574), if `data && typeof data.order === 'number'`, set `node._order = data.order; node.setAttr('frameOrder', data.order)`. Then call `WhiteboardObjects.redrawAllFrameBadges()` so the receiving window's badges are correct. ### Acceptance Criteria - [ ] Each frame stores `data.order`; new frames default to `(max existing) + 1`. - [ ] `WhiteboardFrames.getFrames()` returns frames sorted by `(order, id)` ascending. - [ ] A slide-number badge is rendered next to each frame's title and updates after a reorder. - [ ] The property panel for a selected frame shows Move-up / Move-down buttons; clicking them reorders the deck. - [ ] Right-click on a frame shows Move-slide-up / Move-slide-down entries. - [ ] Reorder syncs to other windows; their badges update. - [ ] Presentation counter and prev/next disabled state reflect the new order. - [ ] Boards with frames missing `order` get a stable id-ascending default order at first read; no jumps. - [ ] Move-up at index 0 is a no-op; move-down at the last slide is a no-op. - [ ] No regression in frame create / drag / resize / rename / drag-with-children. - [ ] `cargo check / clippy / fmt --check / test` clean. ### Notes - The badge is a child of the frame group, so it moves with the frame when dragged. Its position is `(label.x() - 18, label.y())` so it sits left of the title without overlapping. - Don't re-render every frame on every reorder — just update the two badges that swapped (or call `redrawAllFrameBadges` which is cheap because frames are few). - The `getAttr('frameOrder')` storage choice keeps the order on the Konva node (so it's survives across re-renders) and makes the sync round-trip a single read/write. - Don't introduce a sidebar / Frames panel in this iteration — that's a bigger UI surface and not required for the basic reorder UX. - Backward compatibility: existing boards' frames without `order` get a stable default order at read time. The default is `Number(frame.id())`. After the first reorder, the frames are written back with explicit order values via the sync round-trip.
Author
Member

Test Results

  • cargo test -p hero_whiteboard_server: 3 passed.
  • cargo clippy --workspace -- -D warnings: clean.
  • cargo fmt --all -- --check: clean.
  • cargo check --workspace: clean.
  • node --check on all modified JS modules: parses cleanly.

This is a UI-only behavior; no server-side tests needed. Manual verification recommended:

  1. Drop 3 frames on a board — each gets a small 1, 2, 3 badge above its top-left corner.
  2. Select frame 2, click Move up in the property panel — frame 2 becomes 1, the previous 1 becomes 2. Counter and prev/next disabled state in presentation mode reflect the new order.
  3. Right-click a frame → Move slide up / Move slide down work the same way.
  4. Open the same board in a second window — confirm reorder syncs and the badge numbers update there too.
  5. Open Present mode — slides advance in the new order.
  6. On a board with frames that pre-date this change (no data.order), the badges show 1, 2, 3 based on id ascending; reordering then writes explicit orders that survive a refresh.
## Test Results - `cargo test -p hero_whiteboard_server`: 3 passed. - `cargo clippy --workspace -- -D warnings`: clean. - `cargo fmt --all -- --check`: clean. - `cargo check --workspace`: clean. - `node --check` on all modified JS modules: parses cleanly. This is a UI-only behavior; no server-side tests needed. Manual verification recommended: 1. Drop 3 frames on a board — each gets a small `1`, `2`, `3` badge above its top-left corner. 2. Select frame `2`, click `Move up` in the property panel — frame `2` becomes `1`, the previous `1` becomes `2`. Counter and prev/next disabled state in presentation mode reflect the new order. 3. Right-click a frame → `Move slide up` / `Move slide down` work the same way. 4. Open the same board in a second window — confirm reorder syncs and the badge numbers update there too. 5. Open Present mode — slides advance in the new order. 6. On a board with frames that pre-date this change (no `data.order`), the badges show `1, 2, 3` based on id ascending; reordering then writes explicit orders that survive a refresh.
Author
Member

Implementation Summary

6 files changed, +150 / -0.

objects.js::createFrame

  • New data.order storage on each frame, captured on the Konva group via setAttr('frameOrder', N). Defaults to (max existing order) + 1 so newly-dropped frames land at the end of the deck.
  • New Konva.Text with name: 'order-badge' placed at (-18, -19) (just left of the title), 11 px bold muted, non-listening so it doesn't intercept clicks.
  • Calls redrawAllFrameBadges() after creation so existing siblings' badges reflect the new deck size.
  • New helper redrawAllFrameBadges() iterates WhiteboardFrames.getFrames() (sorted) and writes each frame's badge to String(i + 1).
  • redrawAllFrameBadges exported on the public API.

frames.js

  • getFrames() now sorts by (order, id) ascending. Legacy frames missing frameOrder fall back to Number(frame.id()) so they sort deterministically until the user reorders.
  • New _orderKey(frame) helper.
  • New public moveFrameUp(frame) / moveFrameDown(frame) helpers that swap frameOrder with the adjacent frame, redraw badges, and fire WhiteboardSync.onUpdate(...) for both swapped frames.

properties.js::buildFrameProps

  • Added a Slide order block with Up and Down buttons (ids prop-frame-move-up, prop-frame-move-down).
  • Bound the buttons to WhiteboardFrames.moveFrameUp(currentNode) / moveFrameDown(currentNode).

contextmenu.js

  • When the right-clicked object is a frame, the menu now includes Move slide up and Move slide down entries.

sync.js

  • Frame serialize: includes data.order when frameOrder attr is set.
  • Frame apply: writes frameOrder from data.order and triggers redrawAllFrameBadges() so a remote reorder updates the receiving window's badges.

app.js

  • The board-load case 'frame' now passes order: data.order into createFrame so persisted ordering survives a page reload.

Verification

  • cargo test -p hero_whiteboard_server: 3 passed.
  • cargo clippy --workspace -- -D warnings: clean.
  • cargo fmt --all -- --check: clean.
  • cargo check --workspace: clean.
  • node --check on every modified JS module: clean.

Notes / caveats

  • UI-only; no server / SDK / openrpc / DB changes (the data JSON blob already round-trips end-to-end).
  • Backwards compatibility: existing frames without data.order get a stable id-ascending default at first read. The first reorder writes explicit orders that survive subsequent reads.
  • The presentation control bar's counter and prev/next disabled state pick up the new order automatically (issue #93 already wired them through WhiteboardFrames.getCurrentIndex/getTotal).
  • Move-up / move-down at the deck edges are no-ops (silently ignored). The buttons stay clickable; this is acceptable since edge-detection in the panel would require querying the sorted index per render. Can be a follow-up if it bothers users.
## Implementation Summary 6 files changed, +150 / -0. ### `objects.js::createFrame` - New `data.order` storage on each frame, captured on the Konva group via `setAttr('frameOrder', N)`. Defaults to `(max existing order) + 1` so newly-dropped frames land at the end of the deck. - New `Konva.Text` with `name: 'order-badge'` placed at `(-18, -19)` (just left of the title), 11 px bold muted, non-listening so it doesn't intercept clicks. - Calls `redrawAllFrameBadges()` after creation so existing siblings' badges reflect the new deck size. - New helper `redrawAllFrameBadges()` iterates `WhiteboardFrames.getFrames()` (sorted) and writes each frame's badge to `String(i + 1)`. - `redrawAllFrameBadges` exported on the public API. ### `frames.js` - `getFrames()` now sorts by `(order, id)` ascending. Legacy frames missing `frameOrder` fall back to `Number(frame.id())` so they sort deterministically until the user reorders. - New `_orderKey(frame)` helper. - New public `moveFrameUp(frame)` / `moveFrameDown(frame)` helpers that swap `frameOrder` with the adjacent frame, redraw badges, and fire `WhiteboardSync.onUpdate(...)` for both swapped frames. ### `properties.js::buildFrameProps` - Added a `Slide order` block with `Up` and `Down` buttons (ids `prop-frame-move-up`, `prop-frame-move-down`). - Bound the buttons to `WhiteboardFrames.moveFrameUp(currentNode)` / `moveFrameDown(currentNode)`. ### `contextmenu.js` - When the right-clicked object is a frame, the menu now includes `Move slide up` and `Move slide down` entries. ### `sync.js` - Frame serialize: includes `data.order` when `frameOrder` attr is set. - Frame apply: writes `frameOrder` from `data.order` and triggers `redrawAllFrameBadges()` so a remote reorder updates the receiving window's badges. ### `app.js` - The board-load `case 'frame'` now passes `order: data.order` into `createFrame` so persisted ordering survives a page reload. ### Verification - `cargo test -p hero_whiteboard_server`: 3 passed. - `cargo clippy --workspace -- -D warnings`: clean. - `cargo fmt --all -- --check`: clean. - `cargo check --workspace`: clean. - `node --check` on every modified JS module: clean. ### Notes / caveats - UI-only; no server / SDK / openrpc / DB changes (the `data` JSON blob already round-trips end-to-end). - Backwards compatibility: existing frames without `data.order` get a stable id-ascending default at first read. The first reorder writes explicit orders that survive subsequent reads. - The presentation control bar's counter and prev/next disabled state pick up the new order automatically (issue #93 already wired them through `WhiteboardFrames.getCurrentIndex/getTotal`). - Move-up / move-down at the deck edges are no-ops (silently ignored). The buttons stay clickable; this is acceptable since edge-detection in the panel would require querying the sorted index per render. Can be a follow-up if it bothers users.
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#95
No description provided.