Merge pull request #269 from RattleSN4K3/feature/MediaMultiselectDelete

Option to delete selected Media entries
This commit is contained in:
Pat Hartl 2025-05-17 23:42:54 -05:00 committed by GitHub
commit 56bd6fd76d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 119 additions and 51 deletions

View file

@ -34,5 +34,7 @@ namespace LANCommander.Server.Services.Abstractions
Task<T> UpdateAsync(T entity);
Task DeleteAsync(T entity);
// TODO: Add IBaseDatabaseService<>.DeleteRangeAsync
//Task DeleteRangeAsync(IEnumerable<T> entities);
}
}

View file

@ -71,6 +71,17 @@ namespace LANCommander.Server.Services
await base.DeleteAsync(entity);
}
public override async Task DeleteRangeAsync(IEnumerable<Media> entities)
{
DeleteLocalMediaFiles(entities);
var gameIds = entities.Select(x => x.GameId).Distinct();
var expirationTasks = gameIds.Select(gameId => cache.ExpireGameCacheAsync(gameId));
await Task.WhenAll(expirationTasks);
await base.DeleteRangeAsync(entities);
}
public static bool FileExists(Media entity)
{
var path = GetMediaPath(entity);
@ -264,6 +275,14 @@ namespace LANCommander.Server.Services
FileHelpers.DeleteIfExists(GetThumbnailPath(media));
}
public void DeleteLocalMediaFiles(IEnumerable<Media> medias)
{
foreach (var media in medias)
{
DeleteLocalMediaFile(media);
}
}
public async Task<Media> DownloadMediaAsync(string sourceUrl, Media media)
{
using (var http = new HttpClient())

View file

@ -340,7 +340,25 @@ namespace LANCommander.Server.Services
Reset();
}
}
public virtual async Task DeleteRangeAsync(IEnumerable<T> entities)
{
try
{
await cache.ExpireAsync($"{typeof(T).FullName}");
using var context = await dbContextFactory.CreateDbContextAsync();
context.Set<T>().RemoveRange(entities);
await context.SaveChangesAsync();
}
finally
{
Reset();
}
}
private async Task<User?> GetCurrentUserAsync(DatabaseContext context)
{
var httpContext = httpContextAccessor?.HttpContext;

View file

@ -6,60 +6,74 @@
@inject ILogger<MediaEditor> Logger
<Flex Vertical Gap="FlexGap.Large" Style="width: 100%" Class="media-editor">
<Table TItem="Media" DataSource="@Values" RowKey="c => c.Id" HidePagination="true" Responsive>
<PropertyColumn Property="p => p.Id" Title="Preview" Width="100px" Align="ColumnAlign.Center">
@if (MediaService.FileExists(context))
{
string originalPath = $"/api/Media/{context.Id}/Download?fileId={context.FileId}";
string thumbnailPath = context.Thumbnail != null
? $"/api/Media/{context.Thumbnail?.Id}/Download?fileId={context.Thumbnail?.FileId}"
: $"/api/Media/{context.Id}/Thumbnail?fileId={context.FileId}";
if (context.Type == MediaType.Manual)
<Table @ref="ResultsTable" TItem="Media" DataSource="@Values" RowKey="c => c.Id" @bind-SelectedRows="Selected" HidePagination="true" Responsive>
<TitleTemplate>
<Flex Justify="FlexJustify.End" Align="FlexAlign.Center" Gap="@("10")">
@if (Selected.Any())
{
<Image Width="100px" Src="@thumbnailPath" PreviewVisible="false" OnClick="() => OpenManual(context)" />
<span>@Selected.Count() selected</span>
}
else
{
<Image Width="100px" Src="@thumbnailPath" PreviewSrc="@originalPath" />
}
}
</PropertyColumn>
<PropertyColumn Property="p => p.Type">
<Select @key="context.Id" @bind-Value="context.Type" TItem="MediaType" TItemValue="MediaType" DataSource="AllowedMediaTypes" />
</PropertyColumn>
<PropertyColumn Property="p => p.Name">
@if (context.Type == MediaType.Manual)
{
<Input @bind-Value="context.Name" OnBlur="() => Update(context)"/>
}
</PropertyColumn>
<ActionColumn>
<Flex Gap="FlexGap.Small" Justify="FlexJustify.End">
@{
var fileInputId = Guid.NewGuid();
}
<InputFile id="@fileInputId" OnChange="(e) => UploadMedia(e, context)" hidden accept=".jpg,.jpeg,.png,.gif,.pdf,.ico"/>
<label class="ant-btn ant-btn-text ant-btn-icon-only" for="@fileInputId">
<Icon Type="@IconType.Outline.Upload"/>
</label>
@if (String.IsNullOrWhiteSpace(Settings.Media.SteamGridDbApiKey))
{
<Popconfirm OnConfirm="() => NavigateToSettings()" Title="Invalid SteamGridDB credentials. Setup now?">
<Button Type="ButtonType.Text" Icon="@IconType.Outline.Search"/>
</Popconfirm>
}
else
{
<Button OnClick="() => SearchMedia(context)" Type="ButtonType.Text" Icon="@IconType.Outline.Search"/>
}
<Popconfirm OnConfirm="() => RemoveMedia(context)" Title="Are you sure you want to delete this media?">
<Button Type="ButtonType.Text" Danger Icon="@IconType.Outline.Close"/>
<Popconfirm Disabled="!Selected.Any()" OnConfirm="DeleteAll" Title="Are you sure you want to delete all selected media?">
<Button Disabled="!Selected.Any()" Danger>Delete</Button>
</Popconfirm>
</Flex>
</ActionColumn>
</TitleTemplate>
<ColumnDefinitions Context="row">
<Selection Type="SelectionType.Checkbox" />
<PropertyColumn Property="p => p.Id" Title="Preview" Width="100px" Align="ColumnAlign.Center">
@if (MediaService.FileExists(row))
{
string originalPath = $"/api/Media/{row.Id}/Download?fileId={row.FileId}";
string thumbnailPath = row.Thumbnail != null
? $"/api/Media/{row.Thumbnail?.Id}/Download?fileId={row.Thumbnail?.FileId}"
: $"/api/Media/{row.Id}/Thumbnail?fileId={row.FileId}";
if (row.Type == MediaType.Manual)
{
<Image Width="100px" Src="@thumbnailPath" PreviewVisible="false" OnClick="() => OpenManual(row)" />
}
else
{
<Image Width="100px" Src="@thumbnailPath" PreviewSrc="@originalPath" />
}
}
</PropertyColumn>
<PropertyColumn Property="p => p.Type">
<Select @key="row.Id" @bind-Value="row.Type" TItem="MediaType" TItemValue="MediaType" DataSource="AllowedMediaTypes" />
</PropertyColumn>
<PropertyColumn Property="p => p.Name">
@if (row.Type == MediaType.Manual)
{
<Input @bind-Value="row.Name" OnBlur="() => Update(row)" />
}
</PropertyColumn>
<ActionColumn>
<Flex Gap="FlexGap.Small" Justify="FlexJustify.End">
@{
var fileInputId = Guid.NewGuid();
}
<InputFile id="@fileInputId" OnChange="(e) => UploadMedia(e, row)" hidden accept=".jpg,.jpeg,.png,.gif,.pdf,.ico"/>
<label class="ant-btn ant-btn-text ant-btn-icon-only" for="@fileInputId">
<Icon Type="@IconType.Outline.Upload"/>
</label>
@if (String.IsNullOrWhiteSpace(Settings.Media.SteamGridDbApiKey))
{
<Popconfirm OnConfirm="() => NavigateToSettings()" Title="Invalid SteamGridDB credentials. Setup now?">
<Button Type="ButtonType.Text" Icon="@IconType.Outline.Search"/>
</Popconfirm>
}
else
{
<Button OnClick="() => SearchMedia(row)" Type="ButtonType.Text" Icon="@IconType.Outline.Search"/>
}
<Popconfirm OnConfirm="() => RemoveMedia(row)" Title="Are you sure you want to delete this media?">
<Button Type="ButtonType.Text" Danger Icon="@IconType.Outline.Close"/>
</Popconfirm>
</Flex>
</ActionColumn>
</ColumnDefinitions>
</Table>
<GridRow Justify="RowJustify.End">
@ -76,6 +90,9 @@
[Parameter] public Guid GameId { get; set; }
[Parameter] public string GameTitle { get; set; }
IEnumerable<Media> Selected = new List<Media>();
ITable? ResultsTable;
Settings Settings = SettingService.GetSettings();
MediaType[] AllowedMediaTypes = new MediaType[]
@ -237,6 +254,18 @@
await ValuesChanged.InvokeAsync(Values);
}
private async Task DeleteAll()
{
if (!Selected.Any())
return;
Values = Values.Except(Selected).ToList();
await MediaService.DeleteRangeAsync(Selected);
Selected = [];
ResultsTable.ReloadData();
}
private async Task RemoveMedia(Media media)
{
Values.Remove(media);