|
84 | 84 | } |
85 | 85 | else if (_candidates != null && _candidates.Length > 0) |
86 | 86 | { |
| 87 | + <RadzenText Text="@L("ArtistLookup.SelectToMerge")" class="rz-text-secondary rz-mb-2" TextStyle="TextStyle.Caption"/> |
87 | 88 | <RadzenStack Gap="0.5rem" class="rz-mt-2" Style="max-height: 400px; overflow-y: auto;"> |
88 | 89 | @foreach (var candidate in _candidates) |
89 | 90 | { |
90 | | - <RadzenCard class="candidate-card" Click="@(() => SelectCandidate(candidate))"> |
| 91 | + <RadzenCard class="@GetCandidateCardClass(candidate)" Click="@(() => ToggleCandidateSelection(candidate))"> |
91 | 92 | <RadzenStack Gap="0.5rem"> |
92 | 93 | <RadzenStack Orientation="Orientation.Horizontal" Gap="0.5rem" AlignItems="AlignItems.Center"> |
| 94 | + <RadzenCheckBox TValue="bool" |
| 95 | + Value="@_selectedCandidates.Contains(candidate)" |
| 96 | + Change="@((bool _) => ToggleCandidateSelection(candidate))" |
| 97 | + class="rz-mr-2"/> |
93 | 98 | @if (!string.IsNullOrEmpty(candidate.ThumbnailUrl)) |
94 | 99 | { |
95 | 100 | <RadzenImage Path="@candidate.ThumbnailUrl" |
|
112 | 117 | </RadzenStack> |
113 | 118 | </RadzenStack> |
114 | 119 |
|
115 | | - <RadzenStack Orientation="Orientation.Horizontal" Gap="0.25rem" class="rz-mt-1"> |
| 120 | + <RadzenStack Orientation="Orientation.Horizontal" Gap="0.25rem" class="rz-mt-1" Wrap="FlexWrap.Wrap"> |
116 | 121 | @if (candidate.MusicBrainzId.HasValue) |
117 | 122 | { |
118 | 123 | <RadzenBadge BadgeStyle="BadgeStyle.Info" Text="@($"MB: {candidate.MusicBrainzId.Value.ToString()[..8]}...")"/> |
|
133 | 138 | { |
134 | 139 | <RadzenBadge BadgeStyle="BadgeStyle.Secondary" Text="@($"iTunes: {candidate.ItunesId}")"/> |
135 | 140 | } |
| 141 | + @if (!string.IsNullOrEmpty(candidate.WikiDataId)) |
| 142 | + { |
| 143 | + <RadzenBadge BadgeStyle="BadgeStyle.Light" Text="@($"WikiData: {candidate.WikiDataId}")"/> |
| 144 | + } |
| 145 | + @if (!string.IsNullOrEmpty(candidate.LastFmId)) |
| 146 | + { |
| 147 | + <RadzenBadge BadgeStyle="BadgeStyle.Dark" Text="@($"Last.fm: {candidate.LastFmId}")"/> |
| 148 | + } |
136 | 149 | </RadzenStack> |
137 | 150 | </RadzenStack> |
138 | 151 | </RadzenCard> |
|
155 | 168 | <RadzenButton Text="@L("Actions.Cancel")" |
156 | 169 | ButtonStyle="ButtonStyle.Light" |
157 | 170 | Click="@(() => DialogService.Close(null))"/> |
| 171 | + @if (_selectedCandidates.Count > 0) |
| 172 | + { |
| 173 | + <RadzenButton Text="@L("Actions.Save")" |
| 174 | + Icon="save" |
| 175 | + ButtonStyle="ButtonStyle.Primary" |
| 176 | + Click="@SaveSelectedCandidates"/> |
| 177 | + } |
158 | 178 | </RadzenStack> |
159 | 179 | </RadzenStack> |
160 | 180 |
|
|
170 | 190 | private bool _hasPartialFailures; |
171 | 191 | private CancellationTokenSource? _searchCts; |
172 | 192 | private IArtistSearchEnginePlugin[] _plugins = []; |
| 193 | + private readonly HashSet<ArtistLookupCandidate> _selectedCandidates = new(); |
173 | 194 |
|
174 | 195 | [Parameter] |
175 | 196 | public string? InitialArtistName { get; set; } |
|
226 | 247 |
|
227 | 248 | _searchCts?.Cancel(); |
228 | 249 | _searchCts = new CancellationTokenSource(); |
| 250 | + _selectedCandidates.Clear(); |
229 | 251 |
|
230 | 252 | _isLoading = true; |
231 | 253 | _hasError = false; |
|
284 | 306 |
|
285 | 307 | _searchCts?.Cancel(); |
286 | 308 | _searchCts = new CancellationTokenSource(); |
| 309 | + _selectedCandidates.Clear(); |
287 | 310 |
|
288 | 311 | _isLoading = true; |
289 | 312 | _hasError = false; |
|
330 | 353 | } |
331 | 354 | } |
332 | 355 |
|
333 | | - private void SelectCandidate(ArtistLookupCandidate candidate) |
| 356 | + private void ToggleCandidateSelection(ArtistLookupCandidate candidate) |
| 357 | + { |
| 358 | + if (!_selectedCandidates.Remove(candidate)) |
| 359 | + { |
| 360 | + _selectedCandidates.Add(candidate); |
| 361 | + } |
| 362 | + } |
| 363 | + |
| 364 | + private string GetCandidateCardClass(ArtistLookupCandidate candidate) |
| 365 | + { |
| 366 | + return _selectedCandidates.Contains(candidate) |
| 367 | + ? "candidate-card candidate-card-selected" |
| 368 | + : "candidate-card"; |
| 369 | + } |
| 370 | + |
| 371 | + private void SaveSelectedCandidates() |
334 | 372 | { |
335 | | - DialogService.Close(candidate); |
| 373 | + if (_selectedCandidates.Count == 0) |
| 374 | + { |
| 375 | + return; |
| 376 | + } |
| 377 | + |
| 378 | + // Merge all selected candidates into a single result with combined provider IDs |
| 379 | + var merged = new ArtistLookupCandidate |
| 380 | + { |
| 381 | + ProviderDisplayName = string.Join(", ", _selectedCandidates.Select(c => c.ProviderDisplayName).Distinct()), |
| 382 | + ProviderId = _selectedCandidates.FirstOrDefault(c => c.ProviderId != null)?.ProviderId, |
| 383 | + Name = _selectedCandidates.First().Name, |
| 384 | + SortName = _selectedCandidates.FirstOrDefault(c => c.SortName != null)?.SortName, |
| 385 | + ImageUrl = _selectedCandidates.FirstOrDefault(c => c.ImageUrl != null)?.ImageUrl, |
| 386 | + ThumbnailUrl = _selectedCandidates.FirstOrDefault(c => c.ThumbnailUrl != null)?.ThumbnailUrl, |
| 387 | + MusicBrainzId = _selectedCandidates.FirstOrDefault(c => c.MusicBrainzId.HasValue)?.MusicBrainzId, |
| 388 | + SpotifyId = _selectedCandidates.FirstOrDefault(c => !string.IsNullOrEmpty(c.SpotifyId))?.SpotifyId, |
| 389 | + DiscogsId = _selectedCandidates.FirstOrDefault(c => !string.IsNullOrEmpty(c.DiscogsId))?.DiscogsId, |
| 390 | + AmgId = _selectedCandidates.FirstOrDefault(c => !string.IsNullOrEmpty(c.AmgId))?.AmgId, |
| 391 | + WikiDataId = _selectedCandidates.FirstOrDefault(c => !string.IsNullOrEmpty(c.WikiDataId))?.WikiDataId, |
| 392 | + ItunesId = _selectedCandidates.FirstOrDefault(c => !string.IsNullOrEmpty(c.ItunesId))?.ItunesId, |
| 393 | + LastFmId = _selectedCandidates.FirstOrDefault(c => !string.IsNullOrEmpty(c.LastFmId))?.LastFmId |
| 394 | + }; |
| 395 | + |
| 396 | + DialogService.Close(merged); |
336 | 397 | } |
337 | 398 | } |
338 | 399 |
|
339 | 400 | <style> |
340 | 401 | .candidate-card { |
341 | 402 | cursor: pointer; |
342 | | - transition: background-color 0.2s; |
| 403 | + transition: background-color 0.2s, border-color 0.2s; |
| 404 | + border: 2px solid transparent; |
343 | 405 | } |
344 | 406 |
|
345 | 407 | .candidate-card:hover { |
346 | 408 | background-color: var(--rz-base-200); |
347 | 409 | } |
| 410 | +
|
| 411 | + .candidate-card-selected { |
| 412 | + border-color: var(--rz-primary); |
| 413 | + background-color: var(--rz-primary-lighter); |
| 414 | + } |
| 415 | +
|
| 416 | + .candidate-card-selected:hover { |
| 417 | + background-color: var(--rz-primary-light); |
| 418 | + } |
348 | 419 | </style> |
0 commit comments