feat(schedule): right-click menu + drag-to-resize on EPG event blocks #25

Merged
zgaetano merged 1 commit from feat/epg-resize-and-ctxmenu into main 2026-05-23 16:34:48 -04:00
Owner

Summary

Two interactions added to the EPG event blocks on Schedule:

Right-click context menu

Right-click any event block to open a menu (Edit, Cancel, Copy schedule ID, Delete). Per-status filtering mirrors the buttons rendered in the List view so the two surfaces stay consistent:

Status Edit Cancel Delete
pending
running
completed
failed
cancelled

Menu is viewport-clamped (won't fall off the right/bottom edges) and dismisses on outside click, right-click elsewhere, or scroll. Same dismissal pattern as AssetContextMenu in the Library.

Drag-to-resize event blocks

Pending blocks gain three drag affordances:

  • Left edge → moves start_at
  • Right edge → moves end_at
  • Body → shifts both, preserving duration

Behaviour:

  • Snaps to 15-min increments (matches the new-schedule click snap)
  • Minimum duration clamped to 5 minutes
  • Block clamps to the visible day on both edges
  • While dragging, the title shows the preview range (9:00 AM → 10:30 AM) and the block lifts with a project-tinted shadow
  • A short pointer (< 4 px travel) demotes back to a click that opens the edit modal — single pointerdown for both gestures, the user never has to know which they made
  • Optimistic local update so the block stays put when the cursor releases; PUT /schedules/:id with just { start_at, end_at }; refetch reconciles
  • Running / completed / failed / cancelled blocks have no drag handles (the API rejects PUTs on running rows anyway). Hover still shows them and click still opens edit/view.

Implementation notes

  • _EventBlock swapped from <button> to <div> hosting three zones (.epg-block-handle.left / .epg-block-body / .epg-block-handle.right)
  • pointerdown / pointermove / pointerup with setPointerCapture so drags survive losing the cursor over the element
  • New _ScheduleContextMenu component, same shape as AssetContextMenu
  • Schedule gains ctxMenu state, openCtx, copyId, handleResize (optimistic PUT)
  • .epg-row switched from onClick to onPointerUp with an e.target !== e.currentTarget guard so empty-row clicks still create-on-click but the tail of a block drag doesn't spawn a phantom modal

Test plan

  • Right-click a pending block → menu shows Edit / Cancel / Copy ID / Delete; click outside dismisses
  • Right-click a running block → menu shows Cancel / Copy ID only
  • Right-click a failed block → menu shows Edit / Copy ID / Delete
  • Drag the right edge of a pending block → end time changes, snapped to 15-min, persisted on release
  • Drag the left edge → start time changes, can't go past end_at - 5min or before midnight
  • Drag the body → block shifts, duration preserved, can't escape the day
  • Click without dragging → edit modal opens (no spurious resize)
  • Try to drag a running block → no handles, no body drag; clicking it still opens edit (which the API will refuse — separate behaviour)
  • Title tooltip during drag shows start → end preview

🤖 Generated with Claude Code

## Summary Two interactions added to the EPG event blocks on Schedule: ### Right-click context menu Right-click any event block to open a menu (Edit, Cancel, Copy schedule ID, Delete). Per-status filtering mirrors the buttons rendered in the List view so the two surfaces stay consistent: | Status | Edit | Cancel | Delete | |---|---|---|---| | pending | ✓ | ✓ | ✓ | | running | | ✓ | | | completed | | | ✓ | | failed | ✓ | | ✓ | | cancelled | | | ✓ | Menu is viewport-clamped (won't fall off the right/bottom edges) and dismisses on outside click, right-click elsewhere, or scroll. Same dismissal pattern as `AssetContextMenu` in the Library. ### Drag-to-resize event blocks Pending blocks gain three drag affordances: - **Left edge** → moves `start_at` - **Right edge** → moves `end_at` - **Body** → shifts both, preserving duration Behaviour: - Snaps to **15-min** increments (matches the new-schedule click snap) - Minimum duration clamped to **5 minutes** - Block clamps to the visible day on both edges - While dragging, the title shows the preview range (`9:00 AM → 10:30 AM`) and the block lifts with a project-tinted shadow - A short pointer (< 4 px travel) demotes back to a **click** that opens the edit modal — single pointerdown for both gestures, the user never has to know which they made - Optimistic local update so the block stays put when the cursor releases; `PUT /schedules/:id` with just `{ start_at, end_at }`; refetch reconciles - Running / completed / failed / cancelled blocks have **no** drag handles (the API rejects PUTs on running rows anyway). Hover still shows them and click still opens edit/view. ### Implementation notes - `_EventBlock` swapped from `<button>` to `<div>` hosting three zones (`.epg-block-handle.left` / `.epg-block-body` / `.epg-block-handle.right`) - `pointerdown` / `pointermove` / `pointerup` with `setPointerCapture` so drags survive losing the cursor over the element - New `_ScheduleContextMenu` component, same shape as `AssetContextMenu` - `Schedule` gains `ctxMenu` state, `openCtx`, `copyId`, `handleResize` (optimistic PUT) - `.epg-row` switched from `onClick` to `onPointerUp` with an `e.target !== e.currentTarget` guard so empty-row clicks still create-on-click but the tail of a block drag doesn't spawn a phantom modal ## Test plan - [ ] Right-click a pending block → menu shows Edit / Cancel / Copy ID / Delete; click outside dismisses - [ ] Right-click a running block → menu shows Cancel / Copy ID only - [ ] Right-click a failed block → menu shows Edit / Copy ID / Delete - [ ] Drag the right edge of a pending block → end time changes, snapped to 15-min, persisted on release - [ ] Drag the left edge → start time changes, can't go past `end_at - 5min` or before midnight - [ ] Drag the body → block shifts, duration preserved, can't escape the day - [ ] Click without dragging → edit modal opens (no spurious resize) - [ ] Try to drag a running block → no handles, no body drag; clicking it still opens edit (which the API will refuse — separate behaviour) - [ ] Title tooltip during drag shows `start → end` preview 🤖 Generated with [Claude Code](https://claude.com/claude-code)
zgaetano added 1 commit 2026-05-23 16:34:36 -04:00
Right-click any event block to open a context menu (Edit, Cancel,
Copy schedule ID, Delete) — actions per status mirror the List view so
the two surfaces stay in lockstep. Menu is viewport-clamped and
dismisses on outside click / scroll, same pattern as the asset menu in
the Library.

Drag-to-resize works for pending schedules only (the schedules PUT
rejects edits to running rows, and terminal statuses are read-only):
- Drag the left edge to move the start time
- Drag the right edge to move the end time
- Drag the body to shift the whole block in time
All gestures snap to 15-minute increments to match the new-schedule
click snap. Minimum duration is clamped to 5 minutes; the block clamps
to the visible day on both edges. While dragging the title shows the
preview range ("Start time → end time") and the block lifts with a
project-tinted shadow.

A short pointer click (< 4px travel) still opens the edit modal — the
click and drag share the same pointerdown so the operator never has
to know which gesture they made first.

Implementation: replaces the <button> block with a <div> hosting three
zones (left handle / body / right handle). Pointer events with
setPointerCapture so drags survive losing the cursor over the block,
and pointerup demotes back to click if travel was below threshold.
Optimistic local update on resize, PUT /schedules/:id with just the
two changed time fields, refetch to reconcile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zgaetano merged commit 0537378d82 into main 2026-05-23 16:34:48 -04:00
zgaetano deleted branch feat/epg-resize-and-ctxmenu 2026-05-23 16:34:48 -04: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: WildDragonLLC/dragonflight#25
No description provided.