feat: bulk multi-select + drag in PlaylistDetail, Favorites, and artist context menu (#157)

- PlaylistDetail: Ctrl/Cmd+Click enters select mode; bulk drag emits
  { type: 'songs', tracks } when ≥2 selected; filtered-view rows now
  also draggable as single songs
- Favorites songs: full multi-select system — Ctrl+Click, Shift+Click,
  header toggle-all checkbox, bulk-selected highlight, bulk drag to
  queue, bulk-bar with Add to Playlist + Clear
- ContextMenu: ArtistToPlaylistSubmenu resolves all artist album songs
  and forwards to AddToPlaylistSubmenu
- Locale: common.clearSelection + playlists.addSelected in all 7 locales

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-04-12 12:29:39 +02:00
parent 2dd993257a
commit 600af0e527
12 changed files with 276 additions and 23 deletions
+2
View File
@@ -363,6 +363,7 @@ export const deTranslation = {
filterNoGenres: 'Keine Genres gefunden',
filterClear: 'Zurücksetzen',
bulkSelected: '{{count}} ausgewählt',
clearSelection: 'Auswahl aufheben',
bulkAddToPlaylist: 'Zur Playlist hinzufügen',
bulkRemoveFromPlaylist: 'Aus Playlist entfernen',
bulkClear: 'Auswahl aufheben',
@@ -901,6 +902,7 @@ export const deTranslation = {
titleBadge: 'Playlist',
refreshSuggestions: 'Neue Vorschläge',
addSong: 'Zur Playlist hinzufügen',
addSelected: 'Ausgewählte hinzufügen',
cacheOffline: 'Playlist offline speichern',
offlineCached: 'Playlist gecacht',
removeOffline: 'Aus Offline-Cache entfernen',
+2
View File
@@ -364,6 +364,7 @@ export const enTranslation = {
filterNoGenres: 'No genres match',
filterClear: 'Clear',
bulkSelected: '{{count}} selected',
clearSelection: 'Clear selection',
bulkAddToPlaylist: 'Add to Playlist',
bulkRemoveFromPlaylist: 'Remove from Playlist',
bulkClear: 'Clear selection',
@@ -903,6 +904,7 @@ export const enTranslation = {
titleBadge: 'Playlist',
refreshSuggestions: 'New suggestions',
addSong: 'Add to playlist',
addSelected: 'Add selected',
cacheOffline: 'Cache playlist offline',
offlineCached: 'Playlist cached',
removeOffline: 'Remove from offline cache',
+2
View File
@@ -356,6 +356,7 @@ export const esTranslation = {
filterNoGenres: 'Ningún género coincide',
filterClear: 'Limpiar',
bulkSelected: '{{count}} seleccionados',
clearSelection: 'Limpiar selección',
bulkAddToPlaylist: 'Agregar a Lista',
bulkRemoveFromPlaylist: 'Quitar de Lista',
bulkClear: 'Limpiar selección',
@@ -890,6 +891,7 @@ export const esTranslation = {
titleBadge: 'Lista',
refreshSuggestions: 'Nuevas sugerencias',
addSong: 'Agregar a lista',
addSelected: 'Agregar seleccionados',
cacheOffline: 'Guardar lista offline',
offlineCached: 'Lista guardada',
removeOffline: 'Quitar de caché offline',
+2
View File
@@ -363,6 +363,7 @@ export const frTranslation = {
filterNoGenres: 'Aucun genre trouvé',
filterClear: 'Effacer',
bulkSelected: '{{count}} sélectionné(s)',
clearSelection: 'Effacer la sélection',
bulkAddToPlaylist: 'Ajouter à la playlist',
bulkRemoveFromPlaylist: 'Retirer de la playlist',
bulkClear: 'Désélectionner',
@@ -899,6 +900,7 @@ export const frTranslation = {
titleBadge: 'Playlist',
refreshSuggestions: 'Nouvelles suggestions',
addSong: 'Ajouter à la playlist',
addSelected: 'Ajouter la sélection',
cacheOffline: 'Mettre la playlist hors ligne',
offlineCached: 'Playlist en cache',
removeOffline: 'Retirer du cache hors ligne',
+2
View File
@@ -363,6 +363,7 @@ export const nbTranslation = {
filterNoGenres: 'Ingen sjangre samsvarer',
filterClear: 'Tøm',
bulkSelected: '{{count}} valgt',
clearSelection: 'Fjern utvalg',
bulkAddToPlaylist: 'Legg til i spilleliste',
bulkRemoveFromPlaylist: 'Fjern fra spilleliste',
bulkClear: 'Tøm utvalg',
@@ -898,6 +899,7 @@ export const nbTranslation = {
titleBadge: 'Spilleliste',
refreshSuggestions: 'Nye forslag',
addSong: 'Legg til i spilleliste',
addSelected: 'Legg til valgte',
cacheOffline: 'Bufre spilleliste offline',
offlineCached: 'Spilleliste bufret',
removeOffline: 'Fjern fra offline-buffer',
+2
View File
@@ -363,6 +363,7 @@ export const nlTranslation = {
filterNoGenres: 'Geen genres gevonden',
filterClear: 'Wissen',
bulkSelected: '{{count}} geselecteerd',
clearSelection: 'Selectie wissen',
bulkAddToPlaylist: 'Toevoegen aan afspeellijst',
bulkRemoveFromPlaylist: 'Verwijderen uit afspeellijst',
bulkClear: 'Selectie wissen',
@@ -899,6 +900,7 @@ export const nlTranslation = {
titleBadge: 'Playlist',
refreshSuggestions: 'Nieuwe suggesties',
addSong: 'Toevoegen aan playlist',
addSelected: 'Geselecteerde toevoegen',
cacheOffline: 'Playlist offline opslaan',
offlineCached: 'Playlist gecached',
removeOffline: 'Verwijder uit offline cache',
+2
View File
@@ -377,6 +377,7 @@ export const ruTranslation = {
filterNoGenres: 'Нет совпадений',
filterClear: 'Сбросить',
bulkSelected: 'Выбрано: {{count}}',
clearSelection: 'Сбросить выбор',
bulkAddToPlaylist: 'В плейлист',
bulkRemoveFromPlaylist: 'Убрать из плейлиста',
bulkClear: 'Снять выделение',
@@ -958,6 +959,7 @@ export const ruTranslation = {
titleBadge: 'Плейлист',
refreshSuggestions: 'Обновить подборку',
addSong: 'В плейлист',
addSelected: 'Добавить выбранные',
cacheOffline: 'Сохранить плейлист офлайн',
offlineCached: 'Плейлист сохранён',
removeOffline: 'Удалить из офлайн-кэша',
+2
View File
@@ -359,6 +359,7 @@ export const zhTranslation = {
filterNoGenres: '未找到匹配流派',
filterClear: '清除',
bulkSelected: '已选 {{count}} 首',
clearSelection: '清除选择',
bulkAddToPlaylist: '添加到播放列表',
bulkRemoveFromPlaylist: '从播放列表移除',
bulkClear: '取消选择',
@@ -895,6 +896,7 @@ export const zhTranslation = {
titleBadge: '播放列表',
refreshSuggestions: '新建议',
addSong: '添加到播放列表',
addSelected: '添加所选',
cacheOffline: '离线缓存播放列表',
offlineCached: '播放列表已缓存',
removeOffline: '从离线缓存中移除',