@inherits FeedbackComponent @using LANCommander.Server.Services.Abstractions @using LANCommander.Server.Services.Models @using LANCommander.SDK.Enums @inject IMediaGrabberService MediaGrabberService @inject IMessageService MessageService @inject ILogger Logger } else { } } } else { } } @if (Loading && _groupedResults.Any()) {
} @if (!_groupedResults.Any() && !Loading) { } @if (_isDownloading) {
@_downloadStatus
} @code { [Parameter] public string Search { get; set; } [Parameter] public MediaType Type { get; set; } MediaGrabberResult Media { get; set; } MediaGrabberResult _preview { get; set; } bool MultiSelect { get; set; } HashSet _selectedKeys = new(); double Size { get; set; } = 200; bool Loading { get; set; } = true; bool _isDownloading; double _downloadPercent; string _downloadStatus = "Downloading..."; CancellationTokenSource _searchCts; List _allResults = new(); List _groupedResults = new(); string _selectedProvider = string.Empty; string[] _availableProviders = []; protected override async Task OnFirstAfterRenderAsync() { Type = Options.Type; Search = Options.Search; MultiSelect = Options.MultiSelect; _availableProviders = new[] { "All" }.Concat(MediaGrabberService.GetGrabberNames()).ToArray(); _selectedProvider = _availableProviders.First(); await GetResults(Type, Search); } private async Task OnProviderChanged(string provider) { _selectedProvider = provider; if (!string.IsNullOrWhiteSpace(Search)) await GetResults(Type, Search); } private async Task GetResults(MediaType type, string search) { _searchCts?.Cancel(); _searchCts = new CancellationTokenSource(); var token = _searchCts.Token; try { Loading = true; _preview = null; _allResults.Clear(); _groupedResults.Clear(); _selectedKeys.Clear(); await InvokeAsync(StateHasChanged); if (String.IsNullOrWhiteSpace(search)) return; var grabberName = _selectedProvider == "All" ? null : _selectedProvider; await foreach (var batch in MediaGrabberService.SearchStreamAsync(type, search, grabberName, token)) { if (token.IsCancellationRequested) break; foreach (var result in batch) result.UniqueKey = $"{result.GrabberName}:{result.Id}"; _allResults.AddRange(batch); RebuildGroups(); await InvokeAsync(StateHasChanged); } Loading = false; await InvokeAsync(StateHasChanged); } catch (OperationCanceledException) { } catch (Exception ex) { MessageService.Error("An unknown error occurred when trying to get media results"); Logger.LogError(ex, "An unknown error occurred when trying to get media results"); Loading = false; } } private void RebuildGroups() { _groupedResults = _allResults .GroupBy(r => new { r.GrabberName, r.Group }) .Select(g => new GroupedResults { GrabberName = g.Key.GrabberName, Group = g.Key.Group, Results = g.DistinctBy(r => r.Id).ToList() }) .ToList(); } private void OnResultSelected(MediaGrabberResult result) { Media = result; if (MultiSelect) { if (!_selectedKeys.Add(result.UniqueKey)) _selectedKeys.Remove(result.UniqueKey); } } private async Task OnResultPicked(MediaGrabberResult result) { Media = result; await DownloadAndClose(); } private void OnImageSelected(string uniqueKey) { Media = _allResults.FirstOrDefault(r => r.UniqueKey == uniqueKey); } private void OnMultiImageSelectionChanged(HashSet selectedKeys) { _selectedKeys = selectedKeys; } private async Task OnImagePicked(string uniqueKey) { if (MultiSelect) { if (!_selectedKeys.Add(uniqueKey)) _selectedKeys.Remove(uniqueKey); await InvokeAsync(StateHasChanged); } else { OnImageSelected(uniqueKey); await DownloadAndClose(); } } private void PreviewVideo(MediaGrabberResult result) { _preview = _preview?.Id == result.Id ? null : result; } private static bool IsYouTubeUrl(string url) => url.Contains("youtube.com/") || url.Contains("youtu.be/"); private static string GetYouTubeEmbedUrl(string url) { var uri = new Uri(url); var query = System.Web.HttpUtility.ParseQueryString(uri.Query); var videoId = query["v"] ?? uri.Segments.LastOrDefault(); return $"https://www.youtube.com/embed/{videoId}?autoplay=1"; } public override async Task OnFeedbackOkAsync(ModalClosingEventArgs args) { args.Cancel = true; await DownloadAndClose(); } public override async Task OnFeedbackCancelAsync(ModalClosingEventArgs args) { _preview = null; await base.OnFeedbackCancelAsync(args); } private async Task DownloadAndClose() { if (MultiSelect) { await DownloadMultipleAndClose(); return; } if (Media == null) return; _preview = null; _isDownloading = true; _downloadPercent = 0; _downloadStatus = "Downloading..."; await InvokeAsync(StateHasChanged); try { var progress = new Progress(p => { _downloadPercent = p.Percent ?? 0; _downloadStatus = p.Status ?? "Downloading..."; InvokeAsync(StateHasChanged); }); var download = await MediaGrabberService.DownloadAsync(Media, progress); var result = new MediaGrabberDownloadResult { Result = Media, Download = download }; await base.OkCancelRefWithResult!.OnOk(result); await CloseFeedbackAsync(); } catch (Exception ex) { _isDownloading = false; await InvokeAsync(StateHasChanged); MessageService.Error("Download failed. Please try again."); Logger.LogError(ex, "Failed to download media"); } } private async Task DownloadMultipleAndClose() { var selectedResults = _allResults.Where(r => _selectedKeys.Contains(r.UniqueKey)).ToList(); if (!selectedResults.Any()) return; _preview = null; _isDownloading = true; _downloadPercent = 0; await InvokeAsync(StateHasChanged); try { var downloadResult = new MediaGrabberDownloadResult(); for (int i = 0; i < selectedResults.Count; i++) { var selected = selectedResults[i]; _downloadStatus = $"Downloading {i + 1} of {selectedResults.Count}..."; _downloadPercent = 0; await InvokeAsync(StateHasChanged); var progress = new Progress(p => { _downloadPercent = p.Percent ?? 0; InvokeAsync(StateHasChanged); }); var download = await MediaGrabberService.DownloadAsync(selected, progress); downloadResult.Results.Add(new MediaGrabberDownloadResultEntry { Result = selected, Download = download }); } await base.OkCancelRefWithResult!.OnOk(downloadResult); await CloseFeedbackAsync(); } catch (Exception ex) { _isDownloading = false; await InvokeAsync(StateHasChanged); MessageService.Error("Download failed. Please try again."); Logger.LogError(ex, "Failed to download media"); } } private class GroupedResults { public string GrabberName { get; set; } public string Group { get; set; } public List Results { get; set; } } }