You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
86 lines
3.6 KiB
86 lines
3.6 KiB
---
|
|
title: Track Context Menu on Bottom Controls
|
|
date: 2026-05-30
|
|
status: approved
|
|
---
|
|
|
|
## Goal
|
|
|
|
Right-clicking the now-playing area (bottom-left of the window) shows the same context menu as right-clicking a track in the track table: add to last playlist, add to any playlist via submenu, and remove from the current playlist when one is open.
|
|
|
|
## Shared Config Struct
|
|
|
|
A new `TrackContextMenuConfig` struct captures everything the menu needs:
|
|
|
|
```swift
|
|
struct TrackContextMenuConfig {
|
|
let playlists: [Playlist]
|
|
let lastUsedPlaylistName: String?
|
|
let selectedPlaylist: Playlist?
|
|
let onAddToPlaylist: (Track, Playlist) -> Void
|
|
let onAddToLastPlaylist: ((Track) -> Void)?
|
|
let onRemoveFromPlaylist: ((Track) -> Void)?
|
|
}
|
|
```
|
|
|
|
This is the single source of truth for menu data. Both `TrackTableView` and `PlayerControlsView` receive one instance, constructed by `ContentView`.
|
|
|
|
## Shared ViewModifier
|
|
|
|
`TrackContextMenuModifier` is a SwiftUI `ViewModifier` that takes a `Track?` and `TrackContextMenuConfig?` and applies `.contextMenu` when both are non-nil:
|
|
|
|
- **"Add to [last]"** button — shown only when `lastUsedPlaylistName` is set and `onAddToLastPlaylist` is non-nil.
|
|
- **"Add to Playlist"** submenu — one `Button` per playlist in `playlists`. Calls `onAddToPlaylist(track, playlist)`.
|
|
- **Divider + "Remove from Playlist"** — shown only when `selectedPlaylist != nil` and `onRemoveFromPlaylist` is non-nil.
|
|
|
|
Menu is omitted entirely (no empty menu flicker) when `track` or `config` is nil.
|
|
|
|
## PlayerControlsView Changes
|
|
|
|
`PlayerControlsView` gains one new optional parameter:
|
|
|
|
```swift
|
|
let contextMenuConfig: TrackContextMenuConfig?
|
|
```
|
|
|
|
The `nowPlayingSection` view applies `.trackContextMenu(track: currentTrack, config: contextMenuConfig)` (a convenience extension wrapping `TrackContextMenuModifier`).
|
|
|
|
## TrackTableView Refactor
|
|
|
|
`TrackTableView`'s existing four playlist-related parameters (`playlists`, `lastUsedPlaylistName`, `selectedPlaylist`, `onAddToLastPlaylist`, `onRemoveFromPlaylist`) are replaced by a single `contextMenuConfig: TrackContextMenuConfig?`. The `Coordinator.menuNeedsUpdate` builds its `NSMenu` from this config. This makes both call sites symmetric.
|
|
|
|
> The AppKit `NSMenu` path in `TrackTableView` is kept — SwiftUI `.contextMenu` does not attach per-row in an `NSTableView`, so the table continues using `menuNeedsUpdate`.
|
|
|
|
## ContentView Changes
|
|
|
|
`ContentView` constructs one `TrackContextMenuConfig` and passes it to both views:
|
|
|
|
```swift
|
|
let menuConfig = TrackContextMenuConfig(
|
|
playlists: playlist.allPlaylists,
|
|
lastUsedPlaylistName: playlist.lastUsedPlaylistName,
|
|
selectedPlaylist: playlist.selectedPlaylist,
|
|
onAddToPlaylist: { track, pl in try? playlist.addTrack(track, to: pl) },
|
|
onAddToLastPlaylist: { track in try? playlist.addTrackToLastUsedPlaylist(track) },
|
|
onRemoveFromPlaylist: playlist.selectedPlaylist != nil ? { track in
|
|
if let selected = playlist.selectedPlaylist {
|
|
try? playlist.removeTrack(track, from: selected)
|
|
}
|
|
} : nil
|
|
)
|
|
```
|
|
|
|
## Files Affected
|
|
|
|
| File | Change |
|
|
|------|--------|
|
|
| `Music/Models/TrackContextMenuConfig.swift` | New file — struct definition |
|
|
| `Music/Views/TrackContextMenuModifier.swift` | New file — SwiftUI ViewModifier |
|
|
| `Music/Views/PlayerControlsView.swift` | Add `contextMenuConfig` param, apply modifier to `nowPlayingSection` |
|
|
| `Music/Views/TrackTableView.swift` | Replace individual playlist params with `contextMenuConfig`, adapt `menuNeedsUpdate` |
|
|
| `Music/ContentView.swift` | Construct and pass `TrackContextMenuConfig` to both views |
|
|
|
|
## Out of Scope
|
|
|
|
- Keyboard shortcut for the menu
|
|
- Any new menu items not already in the track table menu
|
|
|