@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));
}
}