Skip to content

Commit a6b5850

Browse files
feat: add context menu to now playing view
1 parent b44c270 commit a6b5850

File tree

7 files changed

+1187
-89
lines changed

7 files changed

+1187
-89
lines changed
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
# Now Playing Context Menu Implementation Guide
2+
3+
## Overview
4+
Task-304 requires adding right-click context menu support to tracks in the Now Playing / Up Next queue list. The context menu should reuse existing library view logic with proper viewport-aware positioning.
5+
6+
## Architecture
7+
8+
### 1. Context Menu Implementation in Library View
9+
10+
#### HTML Template (`app/frontend/views/library.html` lines 330-395)
11+
- Track context menu div with classes: `context-menu track-context-menu bg-card`
12+
- Positioned using `:style="contextMenu ? left: ${contextMenu.x}px; top: ${contextMenu.y}px : ''"`
13+
- Uses `@click.outside` to dismiss menu
14+
- Renders menu items from `contextMenu.items` array
15+
- Playlist submenu rendered separately with `getSubmenuStyle()`
16+
17+
#### CSS Styles (`app/frontend/styles.css` lines 579-630)
18+
- `.context-menu`: fixed positioning, z-index 100, min-width 180px, borders, shadows, padding 0.25rem
19+
- `.context-menu-item`: flex layout, gap 0.5rem, padding 0.5rem 0.75rem, cursor pointer
20+
- `.context-menu-item:hover`: bg #dfdfdf
21+
- `.context-menu-item.disabled`: opacity 0.5, pointer-events none
22+
- `.context-menu-item.danger`: color destructive
23+
- `.context-menu-separator`: height 1px background border
24+
25+
#### Mixin: `single-track-context-menu.js`
26+
Located at `app/frontend/js/mixins/single-track-context-menu.js`
27+
28+
Key properties:
29+
- `contextMenu`: { x, y, track, items }
30+
- `playlists`: array for submenu
31+
- `showPlaylistSubmenu`: boolean to toggle submenu
32+
- `submenuOnLeft`: boolean for flipped positioning
33+
- `submenuCloseTimeout`: for delayed submenu close
34+
35+
Key methods:
36+
- `handleContextMenu(event, track)`: Opens context menu
37+
- Calculates menu position with viewport bounds checking
38+
- Determines if submenu should flip left: `x + menuWidth + 45 + submenuWidth > window.innerWidth`
39+
- Creates menu items array with actions
40+
- Asynchronously checks favorite status
41+
42+
- `_ctxPlayTrack(track)`: Clear queue, add track, play
43+
- `_ctxAddToQueue(track)`: Add to end of queue
44+
- `_ctxPlayNext(track)`: Insert at currentIndex + 1
45+
- `_ctxToggleFavorite(track)`: Toggle favorite status
46+
- `addToPlaylist(playlistId)`: Add track to playlist
47+
- `_ctxShowInFinder(track)`: Reveal in Finder
48+
49+
#### Mixin: `context-menu-actions.js` (more complex version)
50+
Located at `app/frontend/js/mixins/context-menu-actions.js`
51+
52+
Used in library-browser for multi-track selections. More sophisticated approach:
53+
- Handles single and multiple selection
54+
- More menu items including "Go to Album", "Go to Artist", "Edit Metadata", "Remove from Library"
55+
- Track removal with confirmation dialog
56+
- Shows selected track count in menu labels
57+
58+
### 2. Now Playing View Components
59+
60+
#### JavaScript Component (`app/frontend/js/components/now-playing-view.js`)
61+
- Uses `queueDragReorderMixin()` for drag-and-drop
62+
- Virtual scroll state management
63+
- Lyrics fetching and display
64+
- Key properties needed for context menu:
65+
- `contextMenu`: null or { x, y, track, items }
66+
- `showPlaylistSubmenu`: boolean
67+
- `submenuOnLeft`: boolean
68+
69+
#### HTML Template (`app/frontend/views/now-playing.html`)
70+
Queue item rendering (lines 206-270):
71+
- Queue item div with classes: `queue-item-wrapper`, `queue-item`
72+
- Shows drag handle on left
73+
- Track title and artist in middle
74+
- Remove button (X) on right
75+
- Double-click handler: `@dblclick.stop="$store.queue.playIndex(item.originalIndex)"`
76+
- Current track highlighted with `item.isCurrentTrack ? 'bg-primary/20 text-primary' : ''`
77+
78+
Currently NO right-click handler on queue items.
79+
80+
### 3. Queue Store and API
81+
82+
#### Queue Store (`app/frontend/js/stores/queue.js`)
83+
Key properties:
84+
- `items`: tracks in play order
85+
- `currentIndex`: currently playing track index
86+
- `playOrderItems`: computed getter that returns items with metadata for UI
87+
88+
Key methods for context menu actions:
89+
- `async playNextTracks(tracks)`: Insert tracks after current track
90+
- Handles move semantics: removes track from queue before re-inserting at play-next position
91+
- Tracks play-next tracks in `_playNextTrackIds` Set for shuffle preservation
92+
- Uses `_playNextOffset` to append after previously queued-next tracks
93+
94+
- `async remove(index)`: Remove track at index
95+
- Updates local state
96+
- Adjusts currentIndex if needed
97+
- Calls `queueApi.remove(index)`
98+
99+
- `async playIndex(index, fromNavigation)`: Play track at index
100+
- Pushes current track to history (for prev button)
101+
- Resets `_playNextOffset`
102+
- Calls `player.playTrack(track)` and `queueApi.setCurrentIndex()`
103+
104+
#### Queue API (`app/frontend/js/api/queue.js`)
105+
- `queue.add(trackIds, position)`: Add tracks at position
106+
- `queue.remove(position)`: Remove track at position
107+
- `queue.playNextTracks()`: Not exposed directly, handled by store
108+
109+
### 4. Library Browser Implementation Reference
110+
111+
#### Position Calculation Logic (`library-browser.js` lines 164-172)
112+
```javascript
113+
const menuHeight = 320;
114+
const menuWidth = 200;
115+
const submenuWidth = 200;
116+
let x = event.clientX;
117+
let y = event.clientY;
118+
119+
if (x + menuWidth > window.innerWidth) {
120+
x = window.innerWidth - menuWidth - 10;
121+
}
122+
if (y + menuHeight > window.innerHeight) {
123+
y = window.innerHeight - menuHeight - 10;
124+
}
125+
126+
this.submenuOnLeft = x + menuWidth + 45 + submenuWidth > window.innerWidth;
127+
```
128+
129+
#### Submenu Style Calculation (`library-browser.js` lines 642-647)
130+
```javascript
131+
getSubmenuStyle() {
132+
if (!this.contextMenu) return '';
133+
const left = this.submenuOnLeft ?
134+
this.contextMenu.x - 180 :
135+
this.contextMenu.x + 180 + 45;
136+
const maxHeight = window.innerHeight - this.submenuY - 10;
137+
return `left: ${left}px; top: ${this.submenuY}px; max-height: ${maxHeight}px; overflow-y: auto`;
138+
}
139+
```
140+
141+
#### Menu Item Click Handler (`library-browser.js` lines 612-619)
142+
```javascript
143+
handleContextMenuItemClick(item) {
144+
if (!item.disabled && !item.hasSubmenu) {
145+
item.action();
146+
this.contextMenu = null;
147+
} else if (!item.disabled) {
148+
item.action();
149+
}
150+
}
151+
```
152+
153+
#### Submenu Mouse Handlers (`library-browser.js` lines 621-640)
154+
- `handleSubmenuMouseenter()`: Shows submenu, stores Y position for alignment
155+
- `handleSubmenuMouseleave()`: Sets timeout to hide submenu (200ms delay)
156+
- Timeout cleared on submenu enter to prevent flickering
157+
158+
## Implementation Plan
159+
160+
### For Now Playing View
161+
162+
1. **Add context menu data properties** to `nowPlayingView` component
163+
- `contextMenu`: null | { x, y, track, items }
164+
- `playlists`: array
165+
- `showPlaylistSubmenu`: boolean
166+
- `submenuOnLeft`: boolean
167+
- `submenuY`: number
168+
- `submenuCloseTimeout`: null | timeout ID
169+
170+
2. **Add right-click handler to queue items** in now-playing.html
171+
- Add `@contextmenu.prevent="handleContextMenu($event, item.track)"` to queue-item div
172+
173+
3. **Add context menu methods to nowPlayingView**:
174+
- `handleContextMenu(event, track)`: Create menu with queue-specific items
175+
- `handleContextMenuItemClick(item)`: Click handler
176+
- `handleSubmenuMouseenter(item, el)`: Submenu hover enter
177+
- `handleSubmenuMouseleave(item)`: Submenu hover leave
178+
- `getSubmenuStyle()`: Calculate submenu position
179+
180+
4. **Add context menu HTML** to now-playing.html after the queue list div
181+
- Main context menu div (similar to library.html)
182+
- Playlist submenu div
183+
- Reuse exact CSS classes for styling
184+
185+
5. **Menu items for Now Playing context**:
186+
- Play Now (clear queue, add track, play)
187+
- Add to Queue (append to end)
188+
- separator
189+
- Play Next (insert after current)
190+
- Add to Playlist (with submenu)
191+
- Add to Liked Songs
192+
- separator
193+
- Show in Finder
194+
- separator
195+
- Remove from Queue (simple remove)
196+
197+
6. **Action implementations**:
198+
- Use `this.queue` store methods: `playNextTracks()`, `remove()`, `playIndex()`
199+
- Use `favorites.add()`, `favorites.remove()`, `favorites.check()`
200+
- Use `playlists.addTracks()`
201+
- For "Show in Finder": use Tauri `show_in_folder()` command
202+
203+
7. **Mixin approach** (optional, cleaner):
204+
- Create `now-playing-context-menu.js` mixin
205+
- Mix into nowPlayingView alongside `queueDragReorderMixin`
206+
- Keeps code modular and reusable
207+
208+
## Key Differences from Library View
209+
210+
1. **No multi-select**: Now Playing is single-track context menu (like artists/albums)
211+
- Should use `singleTrackContextMenuMixin` pattern
212+
- Simpler menu with fewer items
213+
214+
2. **Queue-specific actions**:
215+
- "Play Next" uses `queue.playNextTracks()` not generic insert
216+
- "Remove from Queue" uses `queue.remove()` not playlist-specific removal
217+
- No "Edit Metadata" (queue tracks are read-only references)
218+
- No "Remove from Library" (would remove from library, not queue)
219+
220+
3. **Position context**:
221+
- Queue list is inside fixed right panel (w-96)
222+
- Menu positioning needs to account for panel boundaries
223+
- Container rect calculations needed for accurate positioning
224+
225+
4. **No drag conflicts**:
226+
- Context menu right-click shouldn't interfere with drag-handle left-side
227+
- Drag handle is on left (0.5rem gap), context menu typically triggered on track content
228+
229+
## Testing Considerations
230+
231+
- AC#1: Right-click on queue track opens menu
232+
- AC#2: Menu items present and functional
233+
- AC#3: Menu flips left when near right edge of screen
234+
- AC#4: Play Next reorders queue visually updates
235+
- AC#5: Click outside or select item dismisses menu
236+
- AC#6: Drag-to-reorder still works, remove button still works
237+
238+
## Files to Modify
239+
240+
1. `app/frontend/js/components/now-playing-view.js` - Add properties and methods
241+
2. `app/frontend/views/now-playing.html` - Add @contextmenu handler and context menu HTML
242+
3. Create or reuse `app/frontend/js/mixins/now-playing-context-menu.js` - Optional mixin file
243+
244+
## CSS Already Available
245+
246+
All context menu styles already exist in `app/frontend/styles.css` lines 579-630.
247+
No new CSS needed.

0 commit comments

Comments
 (0)