Presentation: explicit frame ordering with reorder controls and slide-number badges #95
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_whiteboard#95
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
In presentation mode,
WhiteboardFrames.getFrames()(frames.js:7-12) returns frames inlayer.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 / Ncounter 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
order(separate from z-index).getFrames()sorts byorderascending; new frames default toorder = (max existing order) + 1so they land at the end of the deck.ordervalue with the adjacent frame (or shifts a block) and persists via the existingobject.updateRPC + WebSocket sync.X / Ncounter 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 anordervalue in the frame'sdatablob; render a small numbered badge on the frame.crates/hero_whiteboard_ui/static/web/js/whiteboard/frames.js-- sortgetFrames()byorder; exposemoveFrameUp(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-- includedata.orderin the frame serialize/apply round-trip; redraw the order badge when the field changes.No server / SDK / openrpc / DB changes (the
dataJSON blob is already round-tripped).Acceptance criteria
ordervalue; new frames default to(max existing) + 1.getFrames()returns frames sorted byorder.orderwith the adjacent frame and persists across windows.orderfield) get a stable default order (assigned at first read by id ascending) so they don't jump around.cargo check / clippy / fmt --check / testclean.Notes
ordervalues from a buggy import, ties are broken byidascending so the order is deterministic.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 existingobject.updateround-trip.Requirements
data.orderis a finite integer stored on each frame.(max existing order) + 1so they land at the end of the deck.orderare stable-ordered by ascending id at first read (no jumps).WhiteboardFrames.getFrames()returns frames sorted by(order, id)ascending.orderwith the adjacent frame in deck order and persists viaWhiteboardSync.onUpdate.X / Ncounter and prev/next disabled states pick up the new order automatically (they already queryWhiteboardFrames.getCurrentIndex()/getTotal()).objects.js,frames.js,properties.js,contextmenu.js,sync.js.cargo check / clippy / fmt --check / testclean.Files to Modify
crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js— storedata.orderon the frame; render a.frame-order-badgeKonva.Text node next to the label; exposeredrawAllFrameBadges()so the order display can refresh after a swap.crates/hero_whiteboard_ui/static/web/js/whiteboard/frames.js— sortgetFrames()by(order, id); exposemoveFrameUp(node)/moveFrameDown(node)helpers that swaporderwith the adjacent frame, refresh badges, and trigger sync.crates/hero_whiteboard_ui/static/web/js/whiteboard/properties.js— extendbuildFramePropswith up/down arrow buttons; wire them tomoveFrameUp/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— includedata.orderin the frame serialize/apply paths and refresh the badge on receive.No server / SDK / openrpc / DB changes —
datais 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.jscreateFrame(x, y, options):opts.orderif provided; otherwise compute(max existing order over all frames) + 1. Store asgroup._order = valueand stamp the frame group's data viagroup.setAttr('frameOrder', value)for sync visibility.Konva.Textnamed'order-badge', positioned at(-12, -20)(left of the existing title at(8, -20)), withfontSize: 11,fill: 'var-equivalent like #adb5bd',name: 'order-badge'. Initial text is the badge number which is derived from the sort position.redrawFrameBadge(group, indexInDeck)helper that finds the badge child and sets its text toString(indexInDeck + 1).redrawAllFrameBadges()that walksWhiteboardFrames.getFrames()(now sorted) and callsredrawFrameBadge(frame, i)for each, thenbatchDraw().redrawAllFrameBadges()at the end ofcreateFrameso the new frame's badge plus any sibling badges that shifted are correct.dblclickrename / drag handlers are unaffected.Step 2: Sort + reorder helpers
Files:
crates/hero_whiteboard_ui/static/web/js/whiteboard/frames.jsgetFrames()to:_order(falling back toNumber(frame.id())as a stable id-based default for legacy rows).(order, id).moveFrameUp(frame):frame. If 0, no-op._orderwith the previous frame; update both groups'setAttr('frameOrder', ...).WhiteboardObjects.redrawAllFrameBadges()andWhiteboardSync.onUpdate(...)for both frames.moveFrameDown(frame)symmetrically.moveFrameUp,moveFrameDown.Step 3: Property-panel arrows
Files:
crates/hero_whiteboard_ui/static/web/js/whiteboard/properties.jsbuildFrameProps(node, objData):Slide orderblock with two buttons:▲ Move upand▼ Move down. Bothclass="prop-input"-styled but as buttons. Wrap in a small flex row.prop-frame-move-upandprop-frame-move-down.frameTitleInputlistener):#prop-frame-move-uptoWhiteboardFrames.moveFrameUp(currentNode).#prop-frame-move-downtoWhiteboardFrames.moveFrameDown(currentNode).Step 4: Context-menu entries
Files:
crates/hero_whiteboard_ui/static/web/js/whiteboard/contextmenu.jsobjData.type === 'mindmap'block, before the Group/Ungroup block), add:Step 5: Sync round-trip
Files:
crates/hero_whiteboard_ui/static/web/js/whiteboard/sync.jsserializeForServer(~line 261), includedata.order = node.getAttr('frameOrder')(or read fromnode._order).applySyncUpdate(~line 574), ifdata && typeof data.order === 'number', setnode._order = data.order; node.setAttr('frameOrder', data.order). Then callWhiteboardObjects.redrawAllFrameBadges()so the receiving window's badges are correct.Acceptance Criteria
data.order; new frames default to(max existing) + 1.WhiteboardFrames.getFrames()returns frames sorted by(order, id)ascending.orderget a stable id-ascending default order at first read; no jumps.cargo check / clippy / fmt --check / testclean.Notes
(label.x() - 18, label.y())so it sits left of the title without overlapping.redrawAllFrameBadgeswhich is cheap because frames are few).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.orderget a stable default order at read time. The default isNumber(frame.id()). After the first reorder, the frames are written back with explicit order values via the sync round-trip.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 --checkon all modified JS modules: parses cleanly.This is a UI-only behavior; no server-side tests needed. Manual verification recommended:
1,2,3badge above its top-left corner.2, clickMove upin the property panel — frame2becomes1, the previous1becomes2. Counter and prev/next disabled state in presentation mode reflect the new order.Move slide up/Move slide downwork the same way.data.order), the badges show1, 2, 3based on id ascending; reordering then writes explicit orders that survive a refresh.Implementation Summary
6 files changed, +150 / -0.
objects.js::createFramedata.orderstorage on each frame, captured on the Konva group viasetAttr('frameOrder', N). Defaults to(max existing order) + 1so newly-dropped frames land at the end of the deck.Konva.Textwithname: 'order-badge'placed at(-18, -19)(just left of the title), 11 px bold muted, non-listening so it doesn't intercept clicks.redrawAllFrameBadges()after creation so existing siblings' badges reflect the new deck size.redrawAllFrameBadges()iteratesWhiteboardFrames.getFrames()(sorted) and writes each frame's badge toString(i + 1).redrawAllFrameBadgesexported on the public API.frames.jsgetFrames()now sorts by(order, id)ascending. Legacy frames missingframeOrderfall back toNumber(frame.id())so they sort deterministically until the user reorders._orderKey(frame)helper.moveFrameUp(frame)/moveFrameDown(frame)helpers that swapframeOrderwith the adjacent frame, redraw badges, and fireWhiteboardSync.onUpdate(...)for both swapped frames.properties.js::buildFramePropsSlide orderblock withUpandDownbuttons (idsprop-frame-move-up,prop-frame-move-down).WhiteboardFrames.moveFrameUp(currentNode)/moveFrameDown(currentNode).contextmenu.jsMove slide upandMove slide downentries.sync.jsdata.orderwhenframeOrderattr is set.frameOrderfromdata.orderand triggersredrawAllFrameBadges()so a remote reorder updates the receiving window's badges.app.jscase 'frame'now passesorder: data.orderintocreateFrameso 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 --checkon every modified JS module: clean.Notes / caveats
dataJSON blob already round-trips end-to-end).data.orderget a stable id-ascending default at first read. The first reorder writes explicit orders that survive subsequent reads.WhiteboardFrames.getCurrentIndex/getTotal).