Add collections list route and support playlist/track collection items#59
Add collections list route and support playlist/track collection items#59yajathb wants to merge 1 commit intoswingmx:masterfrom
Conversation
|
Original SwingMusic Server-side Git Issue with which I have added the UI elements. |
There was a problem hiding this comment.
Pull request overview
Adds a first-class Collections list page/route and extends collection item support to include playlists and tracks, integrating these into navigation and context menus.
Changes:
- Introduces
/collectionslist route + new Collections list view UI and navigation entries. - Extends collection item handling to support playlists and tracks (requests, store updates, card rendering).
- Adds playlist context menu wiring and collection add/remove actions for playlists/tracks.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/views/Collections/List.vue | New collections list page fetching and rendering all collections. |
| src/stores/pages/collections.ts | Expands local collection item removal to handle playlist/track items. |
| src/router/index.ts | Adds CollectionList route and adjusts route constants/aliases for collections. |
| src/requests/collections.ts | Adds Track support and improves payload handling for collection requests. |
| src/helpers/contextMenuHandler.ts | Adds playlist context-menu entry point. |
| src/context_menus/track.ts | Adds track “Add to Collection” and “Remove item” behavior. |
| src/context_menus/playlist.ts | Implements playlist collection add/remove context menu options. |
| src/components/shared/CardRow.vue | Adds rendering support for playlist and track items. |
| src/components/modals/CrudPage.vue | Allows creating an empty collection (no initial item required). |
| src/components/PlaylistsList/PlaylistCard.vue | Adds playlist context menu + minor accessibility improvements. |
| src/components/LeftSidebar/navitems.ts | Adds Collections to sidebar menus/top nav items. |
| src/components/HomeView/Browse.vue | Adds Collections entry to “Browse Library”. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| async removeLocalItem( | ||
| item: Album | Artist | Playlist | Track, | ||
| type: 'album' | 'artist' | 'playlist' | 'track' | ||
| ) { | ||
| if (!this.collection) return | ||
|
|
||
| if (type == 'album') { | ||
| this.collection.items = this.collection.items.filter(i => { | ||
| return (i as Album).albumhash != (item as Album).albumhash | ||
| return !('albumhash' in i) || i.albumhash != (item as Album).albumhash | ||
| }) | ||
| } else if (type == 'artist') { | ||
| this.collection.items = this.collection.items.filter(i => { | ||
| return !('artisthash' in i) || i.artisthash != (item as Artist).artisthash | ||
| }) | ||
| } else if (type == 'playlist') { | ||
| this.collection.items = this.collection.items.filter(i => { | ||
| return !('id' in i && 'trackhashes' in i) || i.id != (item as Playlist).id | ||
| }) | ||
| } else { | ||
| this.collection.items = this.collection.items.filter(i => { | ||
| return (i as Artist).artisthash != (item as Artist).artisthash | ||
| return !('trackhash' in i) || i.trackhash != (item as Track).trackhash | ||
| }) |
There was a problem hiding this comment.
removeLocalItem now handles 'track', but this.collection.items is typed from Collection and currently doesn't include track items; under strict TS this makes the 'track' branch (i.trackhash) fail type-checking. Update the Collection/collection-item typing to include Track (and any playlist shape used here), then remove the casts so this remains type-safe.
| }) | ||
| } else if (type == 'playlist') { | ||
| this.collection.items = this.collection.items.filter(i => { | ||
| return !('id' in i && 'trackhashes' in i) || i.id != (item as Playlist).id |
There was a problem hiding this comment.
The playlist removal filter checks ('id' in i && 'trackhashes' in i), but Playlist in @/interfaces doesn't define trackhashes (it uses tracks). If collection playlist items don't have trackhashes, this branch will never match and the playlist won't be removed locally. Prefer a guard that matches the actual playlist shape used in collections (e.g., by checking typeof i.id === 'number' or the presence of tracks/name).
| return !('id' in i && 'trackhashes' in i) || i.id != (item as Playlist).id | |
| return !('id' in i && 'tracks' in i) || i.id != (item as Playlist).id |
| return props.items.map((item: any) => { | ||
| const i = { | ||
| component: <any>null, | ||
| props: {}, | ||
| key: '', | ||
| } | ||
|
|
||
| switch (item['type']) { | ||
| case 'album': | ||
| i.component = AlbumCard | ||
| i.key = item.albumhash | ||
| i.props = { | ||
| album: item, | ||
| } | ||
| break | ||
| case 'artist': | ||
| i.component = ArtistCard | ||
| i.key = item.artisthash | ||
| i.props = { | ||
| artist: item, | ||
| } | ||
| break | ||
| case 'mix': | ||
| i.component = MixCard | ||
| i.key = item.sourcehash | ||
| i.props = { | ||
| mix: item, | ||
| } | ||
| break | ||
| case 'playlist': | ||
| i.component = PlaylistCard | ||
| i.key = item.id | ||
| i.props = { | ||
| playlist: item, | ||
| } | ||
| break | ||
| case 'track': | ||
| i.component = TrackCard | ||
| i.key = item.trackhash | ||
| i.props = { | ||
| track: item, | ||
| playSource: playSources.track, | ||
| } |
There was a problem hiding this comment.
i.key is inferred as a string from its initializer (key: ''), but the new playlist case assigns item.id (a number). With strict: true this will be a TS error. Define key as string | number (or initialize it in a way that matches both) so the computed mapping type-checks.
| ) | ||
|
|
||
| if (success) { | ||
| useCollection().removeLocalItem(track as any, 'track' as any) |
There was a problem hiding this comment.
Now that removeLocalItem accepts (track, 'track'), the as any casts here are no longer needed and hide real typing issues. Remove the casts and let TypeScript enforce the correct item/type pairing.
| useCollection().removeLocalItem(track as any, 'track' as any) | |
| useCollection().removeLocalItem(track, 'track') |
| ) | ||
|
|
||
| if (success) { | ||
| useCollection().removeLocalItem(playlist as any, 'playlist' as any) |
There was a problem hiding this comment.
removeLocalItem has been updated to accept (playlist, 'playlist'), so the as any casts are unnecessary here and can mask type mismatches. Call removeLocalItem(playlist, 'playlist') directly.
| useCollection().removeLocalItem(playlist as any, 'playlist' as any) | |
| useCollection().removeLocalItem(playlist, 'playlist') |
| export async function addOrRemoveItemFromCollection( | ||
| collection_id: number, | ||
| item: Album | Artist | Mix | Playlist, | ||
| item: Album | Artist | Mix | Playlist | Track, | ||
| type: string, | ||
| command: 'add' | 'remove' |
There was a problem hiding this comment.
addOrRemoveItemFromCollection takes type: string, but only a fixed set of values is valid and the function logic depends on it. Consider typing this as a string-literal union (e.g. album/artist/mix/playlist/track) to prevent invalid calls at compile time, especially now that more item types are supported.
| @@ -60,6 +66,7 @@ export const menus = [ | |||
| useDialog().showSettingsModal() | |||
| } | |||
| }, | |||
| collections, | |||
| ]; | |||
There was a problem hiding this comment.
collections is appended as the last menus entry. In LeftSidebar/NavButtons.vue the CSS hides .circular.nav-item:last-child on phones, so this will likely hide the new Collections nav item instead of the Settings item. Reorder menus (or adjust the selector) so the intended item is hidden on mobile.
No description provided.