2026-06-15 21:45:14 -05:00
@page "/Settings/Tools/OptimizeImages"
@using Hangfire
@using LANCommander.Server.Jobs.Background
@using LANCommander.Server.Services.Models
@inject MediaService MediaService
@inject IMessageService MessageService
@inject ILogger<OptimizeImages> Logger
@attribute [Authorize(Roles = RoleService.AdministratorRoleName)]
<PageHeader Title="Optimize Images" />
<PageContent>
<p>
Reclaim disk space while preserving art quality. Opaque PNG art (backgrounds, covers, screenshots) is converted to high-quality JPEG, oversized images are downscaled, and metadata is stripped. Images with transparency (icons, logos) are never converted. Optimization replaces the original files in place and runs in the background.
</p>
<Form Layout="@FormLayout.Vertical" Model="@Options">
<FormItem Label="Convert opaque PNG to JPEG">
<Switch @bind-Value="@Options.ConvertPngToJpeg" />
</FormItem>
<FormItem Label="Downscale oversized images">
<Switch @bind-Value="@Options.Downscale" />
</FormItem>
<FormItem Label="Recompress existing JPEGs (lossy)">
<Switch @bind-Value="@Options.RecompressJpeg" />
</FormItem>
2026-06-22 17:05:34 -05:00
<FormItem Label="Recompress existing WebP (lossy)">
<Switch @bind-Value="@Options.RecompressWebp" />
</FormItem>
2026-06-15 21:45:14 -05:00
<FormItem Label="Strip metadata (EXIF/ICC/XMP)">
<Switch @bind-Value="@Options.StripMetadata" />
</FormItem>
<FormItem Label="JPEG quality">
<AntDesign.InputNumber @bind-Value="@Options.JpegQuality" Min="1" Max="100" />
</FormItem>
2026-06-22 17:05:34 -05:00
<FormItem Label="WebP quality">
<AntDesign.InputNumber @bind-Value="@Options.WebpQuality" Min="1" Max="100" />
</FormItem>
2026-06-15 21:45:14 -05:00
<FormItem Label="Max long edge (px)">
<AntDesign.InputNumber @bind-Value="@Options.MaxLongEdge" Min="256" />
</FormItem>
</Form>
<Space Align="SpaceAlign.Center" Style="margin-bottom: 16px">
<SpaceItem>
<Button Type="ButtonType.Primary" OnClick="Scan" Loading="@Loading">Scan</Button>
</SpaceItem>
<SpaceItem>
<Popconfirm OnConfirm="Optimize" Title="Optimize all listed images? Originals will be replaced in place." Disabled="@(Candidates == null || !Candidates.Any())">
<Button Disabled="@(Candidates == null || !Candidates.Any())">Optimize Listed</Button>
</Popconfirm>
</SpaceItem>
</Space>
</PageContent>
<Table TItem="MediaOptimizationCandidate" DataSource="@Candidates" Loading="@Loading" PageSize="25" Responsive>
<PropertyColumn Property="c => c.GameTitle" Title="Game" />
<PropertyColumn Property="c => c.Type" Title="Type" />
<PropertyColumn Property="c => c.MimeType" Title="Format" />
<Column TData="string" Title="Dimensions">
@context.Width x @context.Height
</Column>
<PropertyColumn Property="c => c.Size" Title="Size" Sortable>
<ByteSize Value="context.Size" />
</PropertyColumn>
<PropertyColumn Property="c => c.PlannedAction" Title="Planned Action" />
</Table>
@code {
MediaOptimizationOptions Options = new();
ICollection<MediaOptimizationCandidate> Candidates;
bool Loading = false;
async Task Scan()
{
Loading = true;
await Task.Yield();
await InvokeAsync(StateHasChanged);
try
{
Candidates = await MediaService.ScanOptimizationCandidatesAsync(Options);
}
catch (Exception ex)
{
Logger.LogError(ex, "Could not scan media for optimization candidates");
MessageService.Error("Could not scan media.");
}
Loading = false;
await Task.Yield();
await InvokeAsync(StateHasChanged);
}
void Optimize()
{
BackgroundJob.Enqueue<OptimizeImagesJob>(x => x.ExecuteAsync(Options));
MessageService.Success("Optimization is running in the background.");
}
}