@page "/admin/gifs" @attribute [Authorize(Policy = "ClubAdmin")] @using Koogle.Application.DTOs @using Koogle.Application.Interfaces @using Koogle.Domain.Enums @using Koogle.Web.Components.Game @using Koogle.Web.Store.GifState @using Microsoft.AspNetCore.Authorization @inject IClubGifService GifService @inject ICurrentClubContext ClubContext @inject ISnackbar Snackbar @inject IDialogService DialogService @inject IClubTerminologyService Term @inject IDispatcher Dispatcher GIF-Verwaltung GIF-Verwaltung GIFs und Videos fuer Wurf-Ereignisse verwalten @if (_error is not null) { @_error } Aktive GIFs Von URL importieren Hochladen Vorschau Name Ereignisse Bewertung Status Aktionen @if (context.ContentType.StartsWith("image/")) { } else { } @context.Name @FormatFileSize(context.FileSizeBytes) @foreach (var evt in GetAssignedEvents(context.AssignedEvents)) { @GetEventName(evt) } @context.RatingScore (@context.RatingCount) @if (context.IsEnabled) { Aktiv } else { Deaktiviert } Keine GIFs vorhanden Ausstehende Einreichungen Vorschau Name Eingereicht am Aktionen @if (context.ContentType.StartsWith("image/")) { } else { } @context.Name @context.CreatedAt.ToString("dd.MM.yyyy HH:mm") Annehmen Ablehnen Keine ausstehenden Einreichungen Tokens fuer anonyme Einreichungen Neues Token QR-Code Gueltig bis Nutzung Status Aktionen QR anzeigen @context.ExpiresAt.ToString("dd.MM.yyyy HH:mm") @context.SubmissionCount / @(context.MaxSubmissions?.ToString() ?? "∞") @if (context.IsValid) { Aktiv } else { Abgelaufen } Keine Tokens vorhanden @code { private List _gifs = []; private List _tokens = []; private bool _isLoading = true; private string? _error; private int _pendingCount => _gifs.Count(g => g.IsPendingApproval); private Dictionary _eventNames = new(); protected override async Task OnInitializedAsync() { await LoadData(); } private async Task LoadData() { _isLoading = true; try { var clubId = ClubContext.ClubId; _gifs = (await GifService.GetByClubAsync(clubId, includePending: true)).ToList(); _tokens = (await GifService.GetSubmissionTokensByClubAsync(clubId)).ToList(); await LoadEventNames(); } catch (Exception ex) { _error = ex.Message; } finally { _isLoading = false; StateHasChanged(); } } private async Task LoadEventNames() { var eventTypes = new[] { ThrowEventType.Strike, ThrowEventType.Circle, ThrowEventType.Bell, ThrowEventType.Gutter, ThrowEventType.NoWood, ThrowEventType.Cleared }; foreach (var evt in eventTypes) { _eventNames[evt] = await Term.GetThrowEventName(evt); } } private async Task OnFileSelected(IBrowserFile file) { try { var parameters = new DialogParameters { { "FileName", file.Name } }; var dialog = await DialogService.ShowAsync("GIF hochladen", parameters); var result = await dialog.Result; if (result != null && !result.Canceled && result.Data is CreateClubGifDto dto) { _isLoading = true; StateHasChanged(); using var stream = file.OpenReadStream(maxAllowedSize: 20 * 1024 * 1024); using var ms = new MemoryStream(); await stream.CopyToAsync(ms); ms.Position = 0; var formFile = new FormFileFromStream(ms, file.Name, file.ContentType); await GifService.UploadAsync(ClubContext.ClubId, formFile, dto); Snackbar.Add("GIF erfolgreich hochgeladen", Severity.Success); await LoadData(); } } catch (Exception ex) { _error = ex.Message; Snackbar.Add($"Fehler: {ex.Message}", Severity.Error); } finally { _isLoading = false; } } private async Task OpenImportDialog() { var dialog = await DialogService.ShowAsync("GIF von URL importieren"); var result = await dialog.Result; if (result != null && !result.Canceled && result.Data is ImportClubGifDto dto) { try { _isLoading = true; StateHasChanged(); await GifService.ImportFromUrlAsync(ClubContext.ClubId, dto); Snackbar.Add("GIF erfolgreich importiert", Severity.Success); await LoadData(); } catch (Exception ex) { _error = ex.Message; Snackbar.Add($"Fehler: {ex.Message}", Severity.Error); } finally { _isLoading = false; } } } private async Task OpenEditDialog(ClubGifDto gif) { var parameters = new DialogParameters { { "Gif", gif } }; var dialog = await DialogService.ShowAsync("GIF bearbeiten", parameters); var result = await dialog.Result; if (result != null && !result.Canceled && result.Data is UpdateClubGifDto dto) { try { await GifService.UpdateAsync(dto); Snackbar.Add("GIF aktualisiert", Severity.Success); await LoadData(); } catch (Exception ex) { _error = ex.Message; } } } private async Task ToggleEnabled(ClubGifDto gif) { try { var dto = new UpdateClubGifDto { Id = gif.Id, Name = gif.Name, AssignedEvents = gif.AssignedEvents, Description = gif.Description, IsEnabled = !gif.IsEnabled }; await GifService.UpdateAsync(dto); Snackbar.Add(dto.IsEnabled ? "GIF aktiviert" : "GIF deaktiviert", Severity.Success); await LoadData(); } catch (Exception ex) { _error = ex.Message; } } private async Task ConfirmDelete(ClubGifDto gif) { var parameters = new DialogParameters { { "ContentText", $"Soll das GIF \"{gif.Name}\" wirklich geloescht werden?" }, { "ButtonText", "Loeschen" }, { "Color", Color.Error } }; var dialog = await DialogService.ShowAsync("GIF loeschen", parameters); var result = await dialog.Result; if (result != null && !result.Canceled) { try { await GifService.DeleteAsync(gif.Id); Snackbar.Add("GIF geloescht", Severity.Success); await LoadData(); } catch (Exception ex) { _error = ex.Message; } } } private async Task ApproveGif(ClubGifDto gif) { var parameters = new DialogParameters { { "Gif", gif }, { "IsApproval", true } }; var dialog = await DialogService.ShowAsync("GIF genehmigen", parameters); var result = await dialog.Result; if (result != null && !result.Canceled && result.Data is UpdateClubGifDto dto) { try { await GifService.UpdateAsync(dto); await GifService.ApproveAsync(gif.Id); Snackbar.Add("GIF genehmigt", Severity.Success); await LoadData(); } catch (Exception ex) { _error = ex.Message; } } } private async Task RejectGif(ClubGifDto gif) { var parameters = new DialogParameters { { "ContentText", $"Soll das GIF \"{gif.Name}\" abgelehnt werden? Es wird geloescht." }, { "ButtonText", "Ablehnen" }, { "Color", Color.Error } }; var dialog = await DialogService.ShowAsync("GIF ablehnen", parameters); var result = await dialog.Result; if (result != null && !result.Canceled) { try { await GifService.RejectAsync(gif.Id); Snackbar.Add("GIF abgelehnt", Severity.Success); await LoadData(); } catch (Exception ex) { _error = ex.Message; } } } private async Task OpenCreateTokenDialog() { var dialog = await DialogService.ShowAsync("Neues Einreichungs-Token"); var result = await dialog.Result; if (result != null && !result.Canceled && result.Data is CreateGifSubmissionTokenDto dto) { try { dto = dto with { ClubId = ClubContext.ClubId }; var token = await GifService.CreateSubmissionTokenAsync(dto); Snackbar.Add("Token erstellt", Severity.Success); await LoadData(); await ShowQrCode(token); } catch (Exception ex) { _error = ex.Message; } } } private async Task ShowQrCode(GifSubmissionTokenDto token) { var parameters = new DialogParameters { { "Token", token } }; await DialogService.ShowAsync("QR-Code", parameters); } private async Task CopyLink(GifSubmissionTokenDto token) { // Note: Requires JS interop for clipboard Snackbar.Add($"Link: {token.SubmissionUrl}", Severity.Info); } private static List GetAssignedEvents(ThrowEventType events) { var result = new List(); if ((events & ThrowEventType.Strike) != 0) result.Add(ThrowEventType.Strike); if ((events & ThrowEventType.Circle) != 0) result.Add(ThrowEventType.Circle); if ((events & ThrowEventType.Bell) != 0) result.Add(ThrowEventType.Bell); if ((events & ThrowEventType.Gutter) != 0) result.Add(ThrowEventType.Gutter); if ((events & ThrowEventType.NoWood) != 0) result.Add(ThrowEventType.NoWood); if ((events & ThrowEventType.Cleared) != 0) result.Add(ThrowEventType.Cleared); return result; } private string GetEventName(ThrowEventType type) => _eventNames.TryGetValue(type, out var name) ? name : type.ToString(); private static Color GetEventColor(ThrowEventType type) => type switch { ThrowEventType.Strike => Color.Success, ThrowEventType.Circle => Color.Primary, ThrowEventType.Bell => Color.Warning, ThrowEventType.Gutter => Color.Error, ThrowEventType.NoWood => Color.Tertiary, ThrowEventType.Cleared => Color.Info, _ => Color.Default }; private static string FormatFileSize(long bytes) { if (bytes < 1024) return $"{bytes} B"; if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1} KB"; return $"{bytes / (1024.0 * 1024):F1} MB"; } // Helper class to convert IBrowserFile to IFormFile private class FormFileFromStream : IFormFile { private readonly MemoryStream _stream; public FormFileFromStream(MemoryStream stream, string fileName, string contentType) { _stream = stream; Name = fileName; FileName = fileName; ContentType = contentType; Length = stream.Length; } public string ContentType { get; } public string ContentDisposition => ""; public IHeaderDictionary Headers => new HeaderDictionary(); public long Length { get; } public string Name { get; } public string FileName { get; } public Stream OpenReadStream() => new MemoryStream(_stream.ToArray()); public void CopyTo(Stream target) => _stream.CopyTo(target); public async Task CopyToAsync(Stream target, CancellationToken ct = default) => await _stream.CopyToAsync(target, ct); } private async Task TestGif(ClubGifDto gifdto) { var ev = gifdto.AssignedEvents; var p = new GifPlaybackDto { Id = gifdto.Id, Url = gifdto.Url, ContentType = gifdto.ContentType, Name = gifdto.Name, EventType = ev }; Dispatcher.Dispatch(new GifPlaybackStartedAction(p, ev)); } }