Merge branch 'develop' into feature/cascade-visibility-skip-deleted
This commit is contained in:
@@ -1,37 +0,0 @@
|
||||
@* Generic collapsible section with clickable header and chevron toggle *@
|
||||
<div class="@Class">
|
||||
<div class="d-flex align-items-center gap-2 cursor-pointer" @onclick="Toggle" role="button">
|
||||
@if (TitleContent is not null) {
|
||||
@TitleContent
|
||||
} else if (!string.IsNullOrEmpty(Title)) {
|
||||
<span>@Title</span>
|
||||
}
|
||||
<span class="ms-auto small @(Expanded ? "bi-chevron-up" : "bi-chevron-down")"></span>
|
||||
</div>
|
||||
@if (Expanded) {
|
||||
<div class="@ContentClass">
|
||||
@ChildContent
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public string? Title { get; set; }
|
||||
[Parameter] public RenderFragment? TitleContent { get; set; }
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
[Parameter] public bool InitiallyExpanded { get; set; }
|
||||
[Parameter] public string? Class { get; set; }
|
||||
[Parameter] public string? ContentClass { get; set; }
|
||||
[Parameter] public EventCallback<bool> ExpandedChanged { get; set; }
|
||||
|
||||
bool Expanded { get; set; }
|
||||
|
||||
protected override void OnInitialized() {
|
||||
Expanded = InitiallyExpanded;
|
||||
}
|
||||
|
||||
async Task Toggle() {
|
||||
Expanded = !Expanded;
|
||||
await ExpandedChanged.InvokeAsync(Expanded);
|
||||
}
|
||||
}
|
||||
@@ -113,8 +113,6 @@
|
||||
|
||||
[Parameter]
|
||||
public FolderFullDto Folder { get; set; } = new FolderFullDto();
|
||||
[Parameter]
|
||||
public EventCallback<Guid> OnDeleted { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<Guid> OnDeleteRequested { get; set; }
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
@* Section wrapper for a group of jobs with title and count *@
|
||||
<div class="mb-3">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<h6 class="mb-0 text-muted text-uppercase small">@Title @(Count is not null ? $"({Count})" : "")</h6>
|
||||
@if (Extra is not null) {
|
||||
<span class="ms-auto">@Extra</span>
|
||||
}
|
||||
</div>
|
||||
<div class="border rounded">
|
||||
@ChildContent
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public required string Title { get; set; }
|
||||
[Parameter] public int? Count { get; set; }
|
||||
[Parameter] public RenderFragment? Extra { get; set; }
|
||||
[Parameter] public required RenderFragment ChildContent { get; set; }
|
||||
}
|
||||
@@ -2,14 +2,13 @@
|
||||
@using Butter.Dtos.Album
|
||||
@using Butter.Dtos.Asset
|
||||
@using Butter.Types
|
||||
@using Microsoft.Extensions.Options
|
||||
@implements IAsyncDisposable
|
||||
|
||||
@inject AlbumService albumService
|
||||
@inject AssetService assetService
|
||||
@inject LoginService loginService
|
||||
@inject IJSRuntime jsRuntime
|
||||
@inject IOptions<ServiceOptions> serviceOptions
|
||||
@inject MediaService mediaService
|
||||
@inject NavigationManager navigationManager
|
||||
|
||||
<PageTitle>@(album?.Name ?? "Album")</PageTitle>
|
||||
@@ -157,7 +156,7 @@
|
||||
style="aspect-ratio:@GetAspectRatio(preview)"
|
||||
@onclick="() => HandleTileClick(index, assetId)">
|
||||
@if (preview == null || preview.HasThumbnail) {
|
||||
<img src="@ThumbUrl(assetId)"
|
||||
<img src="@mediaService.ThumbUrl(assetId)"
|
||||
loading="lazy" alt=""
|
||||
onerror="this.style.display='none';this.nextElementSibling.style.display='flex'" />
|
||||
<div class="tile-fallback" style="display:none">
|
||||
@@ -249,10 +248,6 @@
|
||||
HashSet<Guid> selectedImageIds = [];
|
||||
Guid? selectedAssetId;
|
||||
int selectedIndex = -1;
|
||||
string token = string.Empty;
|
||||
string apiBase => serviceOptions.Value.BaseUrl.TrimEnd('/');
|
||||
|
||||
string TokenParam => string.IsNullOrEmpty(token) ? "" : $"?token={token}";
|
||||
string viewMode = "masonry";
|
||||
bool _masonrySetup;
|
||||
|
||||
@@ -293,7 +288,6 @@
|
||||
|
||||
protected override async Task OnParametersSetAsync() {
|
||||
await jsRuntime.InvokeVoidAsync("masonryObserver.unlockBodyScroll");
|
||||
token = loginService.AuthInfo?.Token ?? string.Empty;
|
||||
await LoadAlbum();
|
||||
}
|
||||
|
||||
@@ -309,9 +303,6 @@
|
||||
|
||||
protected override void OnInitialized() {
|
||||
loginService.LoggedUserChanged += (_, _) => StateHasChanged();
|
||||
loginService.AuthInfoChanged += (_, info) => {
|
||||
token = info?.Token ?? string.Empty;
|
||||
};
|
||||
}
|
||||
|
||||
async Task LoadAlbum() {
|
||||
@@ -504,8 +495,6 @@
|
||||
return "4 / 3";
|
||||
}
|
||||
|
||||
string ThumbUrl(Guid id) => $"{apiBase}/api/media/thumb/{id}{TokenParam}";
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await jsRuntime.InvokeVoidAsync("masonryLayout.teardown", ".album-assets-grid.masonry");
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
@using Butter.Dtos.Asset
|
||||
@using Butter.Dtos.Person
|
||||
@using Butter.Types
|
||||
@using Microsoft.Extensions.Options
|
||||
@using System.Globalization
|
||||
@inject PersonService personService
|
||||
@inject AlbumService albumService
|
||||
@inject AssetService assetService
|
||||
@inject LoginService loginService
|
||||
@inject IOptions<ServiceOptions> serviceOptions
|
||||
@inject MediaService mediaService
|
||||
@inject NavigationManager navigationManager
|
||||
|
||||
<PageTitle>@(person?.Name ?? "Cosplayer")</PageTitle>
|
||||
@@ -40,7 +39,7 @@
|
||||
var cy = person.ProfileCropY ?? 50;
|
||||
var z = person.ProfileCropZoom ?? 0.6f;
|
||||
<div class="cosplayer-avatar-clip">
|
||||
<img src="@ThumbUrl(person.ProfileAssetId.Value)" alt=""
|
||||
<img src="@mediaService.ThumbUrl(person.ProfileAssetId.Value)" alt=""
|
||||
class="cosplayer-avatar-img"
|
||||
style="width: calc(100% / @z.ToString(CultureInfo.InvariantCulture)); transform: translate(calc(-@cx.ToString(CultureInfo.InvariantCulture) * 1%), calc(-@cy.ToString(CultureInfo.InvariantCulture) * 1%))" />
|
||||
</div>
|
||||
@@ -317,8 +316,6 @@
|
||||
Func<Task>? _pendingDeleteAction;
|
||||
bool cascadeToAssets;
|
||||
EVisibility selectedAlbumVisibility = EVisibility.Public;
|
||||
string token = string.Empty;
|
||||
string apiBase => serviceOptions.Value.BaseUrl.TrimEnd('/');
|
||||
string viewMode = "masonry";
|
||||
bool showAlbumAssigner;
|
||||
List<AlbumPreviewDto>? unassignedAlbums;
|
||||
@@ -341,8 +338,6 @@
|
||||
|
||||
int albumLoadVersion;
|
||||
|
||||
string TokenParam => string.IsNullOrEmpty(token) ? "" : $"?token={token}";
|
||||
|
||||
List<Guid>? allAssetIds => person?.Albums?
|
||||
.SelectMany(a => Enumerable.Repeat(a.CoverAssetId ?? Guid.Empty, 1))
|
||||
.Where(id => id != Guid.Empty)
|
||||
@@ -350,15 +345,11 @@
|
||||
.ToList();
|
||||
|
||||
protected override async Task OnParametersSetAsync() {
|
||||
token = loginService.AuthInfo?.Token ?? string.Empty;
|
||||
await LoadPerson();
|
||||
}
|
||||
|
||||
protected override void OnInitialized() {
|
||||
loginService.LoggedUserChanged += (_, _) => StateHasChanged();
|
||||
loginService.AuthInfoChanged += (_, info) => {
|
||||
token = info?.Token ?? string.Empty;
|
||||
};
|
||||
}
|
||||
|
||||
async Task LoadPerson() {
|
||||
@@ -372,7 +363,7 @@
|
||||
.Where(a => a.CoverAssetId.HasValue)
|
||||
.OrderBy(_ => Random.Shared.Next())
|
||||
.Take(3)
|
||||
.Select(a => ThumbUrl(a.CoverAssetId!.Value))
|
||||
.Select(a => mediaService.ThumbUrl(a.CoverAssetId!.Value))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -522,6 +513,4 @@
|
||||
if (_pendingDeleteAction != null)
|
||||
await _pendingDeleteAction();
|
||||
}
|
||||
|
||||
string ThumbUrl(Guid id) => $"{apiBase}/api/media/thumb/{id}{TokenParam}";
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
@page "/"
|
||||
@using Butter.Dtos.Asset
|
||||
@using Butter.Types
|
||||
@using Microsoft.Extensions.Options
|
||||
@implements IAsyncDisposable
|
||||
|
||||
@inject AssetService assetService
|
||||
@inject LoginService loginService
|
||||
@inject IJSRuntime jsRuntime
|
||||
@inject IOptions<ServiceOptions> serviceOptions
|
||||
@inject IJSRuntime jsRuntime
|
||||
@inject MediaService mediaService
|
||||
|
||||
<PageTitle>Home</PageTitle>
|
||||
|
||||
@@ -36,7 +35,7 @@
|
||||
style="aspect-ratio:@GetAspectRatio(asset)"
|
||||
@onclick="() => OpenPreview(asset, capturedIndex)">
|
||||
@if (asset.HasThumbnail) {
|
||||
<img src="@ThumbUrl(asset.Id)"
|
||||
<img src="@mediaService.ThumbUrl(asset.Id)"
|
||||
loading="lazy" alt=""
|
||||
onerror="this.style.display='none';this.nextElementSibling.style.display='flex'" />
|
||||
<div class="tile-fallback" style="display:none">
|
||||
@@ -136,18 +135,10 @@
|
||||
ElementReference topSentinelRef;
|
||||
DotNetObjectReference<Home>? dotNetRef;
|
||||
Guid _randomSeed;
|
||||
string token = string.Empty;
|
||||
string apiBase => serviceOptions.Value.BaseUrl.TrimEnd('/');
|
||||
|
||||
string TokenParam => string.IsNullOrEmpty(token) ? "" : $"?token={token}";
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
loginService.LoggedUserChanged += (_, _) => StateHasChanged();
|
||||
loginService.AuthInfoChanged += (_, info) => {
|
||||
token = info?.Token ?? string.Empty;
|
||||
StateHasChanged();
|
||||
};
|
||||
token = loginService.AuthInfo?.Token ?? string.Empty;
|
||||
loginService.AuthInfoChanged += (_, _) => StateHasChanged();
|
||||
await LoadFirstPage();
|
||||
}
|
||||
|
||||
@@ -277,8 +268,6 @@
|
||||
return "4 / 3";
|
||||
}
|
||||
|
||||
string ThumbUrl(Guid id) => $"{apiBase}/api/media/thumb/{id}{TokenParam}";
|
||||
|
||||
public async ValueTask DisposeAsync() {
|
||||
await jsRuntime.InvokeVoidAsync("masonryObserver.unlockBodyScroll");
|
||||
if (dotNetRef != null) {
|
||||
|
||||
@@ -10,16 +10,13 @@
|
||||
</div>
|
||||
} else {
|
||||
<div class="progress" role="progressbar" style="height: @Height">
|
||||
<div class="progress-bar @BgClass" style="width: @((Value * 100).ToString(CultureInfo.InvariantCulture))%">
|
||||
@if (ShowPercent) { @($"{Value * 100:F0}%") }
|
||||
</div>
|
||||
<div class="progress-bar @BgClass" style="width: @((Value * 100).ToString(CultureInfo.InvariantCulture))%"></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public float Value { get; set; }
|
||||
[Parameter] public string? Color { get; set; }
|
||||
[Parameter] public bool ShowPercent { get; set; }
|
||||
[Parameter] public string Size { get; set; } = "sm";
|
||||
[Parameter] public IReadOnlyList<(float Width, string Color)>? Segments { get; set; }
|
||||
|
||||
|
||||
@@ -124,13 +124,6 @@ public static class RegexHighlighter
|
||||
return new MarkupString(sb.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the set of CSS class names used by the highlighter for embedding in isolated CSS.
|
||||
/// </summary>
|
||||
public static string CssClasses => string.Join(" ",
|
||||
ClassAnchor, ClassQuantifier, ClassCharClass, ClassEscape,
|
||||
ClassNamedGroup, ClassGroupRef, ClassOperator, ClassLiteral);
|
||||
|
||||
private static int FindNamedGroupEnd(ReadOnlySpan<char> span, int start) =>
|
||||
FindMatchingParen(span, start);
|
||||
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
@using Butter.Dtos.Asset
|
||||
@using Butter.Dtos.Person
|
||||
@using Butter.Types
|
||||
@using Microsoft.Extensions.Options
|
||||
@implements IDisposable
|
||||
|
||||
@inject AssetService assetService
|
||||
@inject AlbumService albumService
|
||||
@inject PersonService personService
|
||||
@inject LoginService loginService
|
||||
@inject IOptions<ServiceOptions> serviceOptions
|
||||
@inject LoginService loginService
|
||||
@inject MediaService mediaService
|
||||
|
||||
@if (Embedded) {
|
||||
<div class="picker-embedded">
|
||||
@@ -86,7 +85,7 @@
|
||||
<div class="picker-tile @(selectedIds.Contains(asset.Id) ? "selected" : "")"
|
||||
@key="asset.Id" @onclick="() => ToggleSelection(asset.Id)">
|
||||
@if (asset.HasThumbnail) {
|
||||
<img src="@ThumbUrl(asset.Id)" loading="lazy" alt=""
|
||||
<img src="@mediaService.ThumbUrl(asset.Id)" loading="lazy" alt=""
|
||||
onerror="this.style.display='none';this.nextElementSibling.style.display='flex'" />
|
||||
<div class="tile-fallback" style="display:none"><i class="bi bi-image"></i></div>
|
||||
} else {
|
||||
@@ -137,7 +136,7 @@
|
||||
<div class="picker-tile @(selectedIds.Contains(asset.Id) ? "selected" : "")"
|
||||
@key="asset.Id" @onclick="() => ToggleSelection(asset.Id)">
|
||||
@if (asset.HasThumbnail) {
|
||||
<img src="@ThumbUrl(asset.Id)" loading="lazy" alt=""
|
||||
<img src="@mediaService.ThumbUrl(asset.Id)" loading="lazy" alt=""
|
||||
onerror="this.style.display='none';this.nextElementSibling.style.display='flex'" />
|
||||
<div class="tile-fallback" style="display:none"><i class="bi bi-image"></i></div>
|
||||
} else {
|
||||
@@ -254,7 +253,7 @@
|
||||
<div class="picker-tile @(selectedIds.Contains(asset.Id) ? "selected" : "")"
|
||||
@key="asset.Id" @onclick="() => ToggleSelection(asset.Id)">
|
||||
@if (asset.HasThumbnail) {
|
||||
<img src="@ThumbUrl(asset.Id)" loading="lazy" alt=""
|
||||
<img src="@mediaService.ThumbUrl(asset.Id)" loading="lazy" alt=""
|
||||
onerror="this.style.display='none';this.nextElementSibling.style.display='flex'" />
|
||||
<div class="tile-fallback" style="display:none"><i class="bi bi-image"></i></div>
|
||||
} else {
|
||||
@@ -305,7 +304,7 @@
|
||||
<div class="picker-tile @(selectedIds.Contains(asset.Id) ? "selected" : "")"
|
||||
@key="asset.Id" @onclick="() => ToggleSelection(asset.Id)">
|
||||
@if (asset.HasThumbnail) {
|
||||
<img src="@ThumbUrl(asset.Id)" loading="lazy" alt=""
|
||||
<img src="@mediaService.ThumbUrl(asset.Id)" loading="lazy" alt=""
|
||||
onerror="this.style.display='none';this.nextElementSibling.style.display='flex'" />
|
||||
<div class="tile-fallback" style="display:none"><i class="bi bi-image"></i></div>
|
||||
} else {
|
||||
@@ -429,7 +428,6 @@
|
||||
string currentPath = "";
|
||||
HashSet<Guid> selectedIds = [];
|
||||
string searchQuery = string.Empty;
|
||||
string token = string.Empty;
|
||||
int currentPage;
|
||||
bool isLoading;
|
||||
bool hasMore = true;
|
||||
@@ -455,15 +453,12 @@
|
||||
const int PageSize = 150;
|
||||
|
||||
bool isAdminOrCurator => loginService.IsAdminOrCurator;
|
||||
string apiBase => serviceOptions.Value.BaseUrl.TrimEnd('/');
|
||||
string TokenParam => string.IsNullOrEmpty(token) ? "" : $"?token={token}";
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
if (Embedded) await InitializeAsync();
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync() {
|
||||
token = loginService.AuthInfo?.Token ?? string.Empty;
|
||||
if (!Embedded && Show && !_initializing) {
|
||||
_initializing = true;
|
||||
await InitializeAsync();
|
||||
@@ -613,8 +608,6 @@
|
||||
_personSearchTimer?.Dispose();
|
||||
}
|
||||
|
||||
string ThumbUrl(Guid id) => $"{apiBase}/api/media/thumb/{id}{TokenParam}";
|
||||
|
||||
static string TruncateName(string name, int maxLen = 40) {
|
||||
if (name.Length <= maxLen) return name;
|
||||
var lastSlash = name.LastIndexOf('/');
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
@using Butter.Dtos.Album
|
||||
@using Butter.Types
|
||||
@using Microsoft.Extensions.Options
|
||||
|
||||
@inject IOptions<ServiceOptions> serviceOptions
|
||||
@inject LoginService loginService
|
||||
@inject LoginService loginService
|
||||
@inject MediaService mediaService
|
||||
|
||||
<div class="@GetCardClass()" @key="Album.Id" style="@GetCardStyle()">
|
||||
<a href="/albums/@Album.Id" class="album-card-overlay" aria-label="View album @Album.Name"
|
||||
@onclick="HandleOverlayClick" @onclick:preventDefault="SelectionMode"></a>
|
||||
@if (Album.CoverAssetId.HasValue) {
|
||||
<img src="@ThumbUrl(Album.CoverAssetId.Value)"
|
||||
<img src="@mediaService.ThumbUrl(Album.CoverAssetId.Value)"
|
||||
loading="lazy" alt=""
|
||||
onerror="this.style.display='none';this.nextElementSibling.style.display='flex'" />
|
||||
<div class="album-card-fallback" style="display:none">
|
||||
@@ -54,12 +53,6 @@
|
||||
[Parameter]
|
||||
public string ViewMode { get; set; } = "masonry";
|
||||
|
||||
string apiBase => serviceOptions.Value.BaseUrl.TrimEnd('/');
|
||||
string token => loginService.AuthInfo?.Token ?? string.Empty;
|
||||
string TokenParam => string.IsNullOrEmpty(token) ? "" : $"?token={token}";
|
||||
|
||||
string ThumbUrl(Guid id) => $"{apiBase}/api/media/thumb/{id}{TokenParam}";
|
||||
|
||||
string GetCardClass() {
|
||||
var classes = new List<string> { "album-card" };
|
||||
if (SelectionMode) {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
@using Microsoft.Extensions.Options
|
||||
@using MilkStream.Client.Services
|
||||
@using System.Linq
|
||||
|
||||
@inject LoginService loginService
|
||||
@inject IOptions<ServiceOptions> serviceOptions
|
||||
@inject MediaService mediaService
|
||||
|
||||
<ModalFrame Title="Select Cover Image" Show="Show" Size="lg" OnClose="OnCancel">
|
||||
<Body>
|
||||
@@ -15,7 +12,7 @@
|
||||
<div class="picker-tile @(SelectedAssetId == id ? "selected" : "")"
|
||||
@key="id"
|
||||
@onclick="() => SelectAsset(id)">
|
||||
<img src="@ThumbUrl(id)"
|
||||
<img src="@mediaService.ThumbUrl(id)"
|
||||
loading="lazy" alt=""
|
||||
onerror="this.style.display='none';this.nextElementSibling.style.display='flex'" />
|
||||
<div class="tile-fallback" style="display:none">
|
||||
@@ -47,15 +44,6 @@
|
||||
[Parameter] public EventCallback<Guid?> OnSelected { get; set; }
|
||||
[Parameter] public EventCallback OnClosed { get; set; }
|
||||
|
||||
string token = string.Empty;
|
||||
string apiBase => serviceOptions.Value.BaseUrl.TrimEnd('/');
|
||||
|
||||
string TokenParam => string.IsNullOrEmpty(token) ? "" : $"?token={token}";
|
||||
|
||||
protected override void OnParametersSet() {
|
||||
token = loginService.AuthInfo?.Token ?? string.Empty;
|
||||
}
|
||||
|
||||
void SelectAsset(Guid id) {
|
||||
SelectedAssetId = id;
|
||||
}
|
||||
@@ -68,6 +56,4 @@
|
||||
async Task OnCancel() {
|
||||
await OnClosed.InvokeAsync();
|
||||
}
|
||||
|
||||
string ThumbUrl(Guid id) => $"{apiBase}/api/media/thumb/{id}{TokenParam}";
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
@using Butter.Dtos.Person
|
||||
@using Butter.Types
|
||||
@using Microsoft.Extensions.Options
|
||||
@using System.Globalization
|
||||
@implements IAsyncDisposable
|
||||
|
||||
@inject PersonService personService
|
||||
@inject LoginService loginService
|
||||
@inject IOptions<ServiceOptions> serviceOptions
|
||||
@inject MediaService mediaService
|
||||
@inject IJSRuntime jsRuntime
|
||||
|
||||
@if (isLoading) {
|
||||
@@ -34,7 +33,7 @@
|
||||
var cy1 = person.ProfileCropY ?? 50;
|
||||
var zoom1 = person.ProfileCropZoom ?? 0.6f;
|
||||
<div class="cosplayer-card-img-wrap">
|
||||
<img src="@ThumbUrl(person.ProfileAssetId.Value)" alt=""
|
||||
<img src="@mediaService.ThumbUrl(person.ProfileAssetId.Value)" alt=""
|
||||
class="cosplayer-card-img"
|
||||
style="width: calc(100% / @zoom1.ToString(CultureInfo.InvariantCulture)); transform: translate(calc(-@cx1.ToString(CultureInfo.InvariantCulture) * 1%), calc(-@cy1.ToString(CultureInfo.InvariantCulture) * 1%))"
|
||||
onerror="this.style.display='none';this.nextElementSibling.style.display='flex'" />
|
||||
@@ -92,14 +91,6 @@
|
||||
string? _prevSearch;
|
||||
string? _prevSortBy;
|
||||
bool _prevSortAsc;
|
||||
string _token = string.Empty;
|
||||
string _apiBase => serviceOptions.Value.BaseUrl.TrimEnd('/');
|
||||
string TokenParam => string.IsNullOrEmpty(_token) ? "" : $"?token={_token}";
|
||||
|
||||
protected override void OnInitialized() {
|
||||
loginService.AuthInfoChanged += OnAuthInfoChanged;
|
||||
_token = loginService.AuthInfo?.Token ?? string.Empty;
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync() {
|
||||
bool filterChanged = _prevLoadVersion != LoadVersion
|
||||
@@ -183,8 +174,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
string ThumbUrl(Guid id) => $"{_apiBase}/api/media/thumb/{id}{TokenParam}";
|
||||
|
||||
string GetCardClass(PersonPreviewDto person) {
|
||||
var classes = new List<string> { "cosplayer-card" };
|
||||
if (SelectionMode) {
|
||||
@@ -198,10 +187,6 @@
|
||||
return string.Join(" ", classes);
|
||||
}
|
||||
|
||||
void OnAuthInfoChanged(object? _, AuthInfo? info) {
|
||||
_token = info?.Token ?? string.Empty;
|
||||
}
|
||||
|
||||
async Task DisposeObserver() {
|
||||
if (dotNetRef != null) {
|
||||
await jsRuntime.InvokeVoidAsync("cosplayerObserver.dispose");
|
||||
@@ -211,7 +196,6 @@
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync() {
|
||||
loginService.AuthInfoChanged -= OnAuthInfoChanged;
|
||||
await DisposeObserver();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
@using Microsoft.Extensions.Options
|
||||
|
||||
@inject IOptions<ServiceOptions> Options
|
||||
@inject LoginService LoginService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject MediaService mediaService
|
||||
|
||||
@if (Show && AssetId != Guid.Empty) {
|
||||
<div class="preview-overlay" @onkeydown="OnKeyDown" tabindex="0" @onclick="OnCloseCallback">
|
||||
@@ -18,10 +15,10 @@
|
||||
|
||||
<div class="preview-image-wrapper">
|
||||
@if (HasPreview) {
|
||||
<img class="preview-img" src="@PreviewUrl" alt=""
|
||||
onerror="this.onerror=null;this.src='@ThumbUrl'" />
|
||||
<img class="preview-img" src="@mediaService.PreviewUrl(AssetId)" alt=""
|
||||
onerror="this.onerror=null;this.src='@mediaService.ThumbUrl(AssetId)'" />
|
||||
} else {
|
||||
<img class="preview-img" src="@ThumbUrl" alt="" />
|
||||
<img class="preview-img" src="@mediaService.ThumbUrl(AssetId)" alt="" />
|
||||
}
|
||||
<div class="preview-info">
|
||||
<div class="preview-info-text">
|
||||
@@ -32,7 +29,7 @@
|
||||
<span class="preview-info-filename">@Filename</span>
|
||||
}
|
||||
<div class="preview-info-actions">
|
||||
<a class="preview-action-btn" href="@OriginalUrl" target="_blank"
|
||||
<a class="preview-action-btn" href="@mediaService.OriginalUrl(AssetId)" target="_blank"
|
||||
title="View full size" @onclick:stopPropagation="true">
|
||||
<i class="bi bi-arrows-fullscreen"></i>
|
||||
</a>
|
||||
@@ -59,18 +56,6 @@
|
||||
[Parameter] public EventCallback OnClose { get; set; }
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
string token = string.Empty;
|
||||
string apiBase => Options.Value.BaseUrl.TrimEnd('/');
|
||||
string TokenParam => string.IsNullOrEmpty(token) ? "" : $"?token={token}";
|
||||
string ThumbUrl => $"{apiBase}/api/media/thumb/{AssetId}{TokenParam}";
|
||||
string PreviewUrl => $"{apiBase}/api/media/preview/{AssetId}{TokenParam}";
|
||||
string OriginalUrl => $"{apiBase}/api/media/original/{AssetId}{TokenParam}";
|
||||
|
||||
protected override void OnInitialized() {
|
||||
LoginService.AuthInfoChanged += (_, info) => { token = info?.Token ?? string.Empty; };
|
||||
token = LoginService.AuthInfo?.Token ?? string.Empty;
|
||||
}
|
||||
|
||||
async Task OnCloseCallback() {
|
||||
await OnClose.InvokeAsync();
|
||||
}
|
||||
@@ -90,6 +75,6 @@
|
||||
}
|
||||
|
||||
async Task Download() {
|
||||
await JSRuntime.InvokeVoidAsync("masonryObserver.downloadFile", OriginalUrl);
|
||||
await JSRuntime.InvokeVoidAsync("masonryObserver.downloadFile", mediaService.OriginalUrl(AssetId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
@using Microsoft.Extensions.Options
|
||||
@inject IJSRuntime jsRuntime
|
||||
@inject IOptions<ServiceOptions> serviceOptions
|
||||
@inject LoginService loginService
|
||||
@inject IJSRuntime jsRuntime
|
||||
@inject MediaService mediaService
|
||||
|
||||
@if (Show && AssetId.HasValue) {
|
||||
<div class="modal-overlay" style="background: rgba(0, 0, 0, 0.85)" @onclick="OnCancel">
|
||||
@@ -72,11 +70,6 @@
|
||||
/// </summary>
|
||||
[Parameter] public EventCallback OnClosed { get; set; }
|
||||
|
||||
string apiBase => serviceOptions.Value.BaseUrl.TrimEnd('/');
|
||||
string token => loginService.AuthInfo?.Token ?? string.Empty;
|
||||
string TokenParam => string.IsNullOrEmpty(token) ? "" : $"?token={token}";
|
||||
string ImageUrl => $"{apiBase}/api/media/thumb/{AssetId!.Value}{TokenParam}";
|
||||
|
||||
protected override void OnInitialized() {
|
||||
_instanceId = ++_counter;
|
||||
}
|
||||
@@ -93,7 +86,7 @@
|
||||
if (_pendingInit) {
|
||||
_pendingInit = false;
|
||||
await jsRuntime.InvokeVoidAsync("profileCropper.initialize",
|
||||
ContainerId, ImageUrl, CropX ?? 50, CropY ?? 50, CropZoom ?? 0.6f);
|
||||
ContainerId, mediaService.ThumbUrl(AssetId!.Value), CropX ?? 50, CropY ?? 50, CropZoom ?? 0.6f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
@using Butter.Dtos.Album
|
||||
@using Butter.Dtos.Person
|
||||
@using Microsoft.Extensions.Options
|
||||
@using MilkStream.Client.Services
|
||||
|
||||
@namespace MilkStream.Client.Components.Shared
|
||||
|
||||
@inject PersonService PersonSvc
|
||||
@inject AlbumService AlbumSvc
|
||||
@inject NavigationManager Nav
|
||||
@inject IOptions<ServiceOptions> ServiceOptions
|
||||
@inject LoginService LoginSvc
|
||||
@inject PersonService PersonSvc
|
||||
@inject AlbumService AlbumSvc
|
||||
@inject NavigationManager Nav
|
||||
@inject MediaService mediaService
|
||||
|
||||
<div class="search-dropdown-container position-relative">
|
||||
<div class="d-flex">
|
||||
@@ -67,7 +65,7 @@
|
||||
@onmouseenter="() => OnItemHover(idx)">
|
||||
@if (item.ImageId.HasValue)
|
||||
{
|
||||
<img class="search-dropdown-thumb" src="@ThumbUrl(item.ImageId.Value)" alt="" loading="lazy" />
|
||||
<img class="search-dropdown-thumb" src="@mediaService.ThumbUrl(item.ImageId.Value)" alt="" loading="lazy" />
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -85,7 +83,7 @@
|
||||
@onmouseenter="() => OnItemHover(idx)">
|
||||
@if (item.ImageId.HasValue)
|
||||
{
|
||||
<img class="search-dropdown-thumb-rounded" src="@ThumbUrl(item.ImageId.Value)" alt="" loading="lazy" />
|
||||
<img class="search-dropdown-thumb-rounded" src="@mediaService.ThumbUrl(item.ImageId.Value)" alt="" loading="lazy" />
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -117,12 +115,6 @@
|
||||
int _selectedIndex = -1;
|
||||
CancellationTokenSource? _blurCts;
|
||||
|
||||
string ApiBase => ServiceOptions.Value.BaseUrl.TrimEnd('/');
|
||||
string Token => LoginSvc.AuthInfo?.Token ?? string.Empty;
|
||||
string TokenParam => string.IsNullOrEmpty(Token) ? "" : $"?token={Token}";
|
||||
|
||||
string ThumbUrl(Guid id) => $"{ApiBase}/api/media/thumb/{id}{TokenParam}";
|
||||
|
||||
async Task OnInput(ChangeEventArgs e)
|
||||
{
|
||||
_query = e.Value?.ToString() ?? string.Empty;
|
||||
|
||||
@@ -12,15 +12,6 @@ public sealed class FoldersService(
|
||||
LoginService loginService,
|
||||
ILogger<FoldersService> logger
|
||||
) : AuthServiceBase(options, httpClientFactory, loginService, logger) {
|
||||
/// <summary>
|
||||
/// Fired when folders are saved.
|
||||
/// </summary>
|
||||
public event EventHandler? SaveFolders;
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="SaveFolders"/> event.
|
||||
/// </summary>
|
||||
public void OnSaveFolders() => SaveFolders?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all tracked folders.
|
||||
/// </summary>
|
||||
@@ -33,19 +24,6 @@ public sealed class FoldersService(
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a folder by its ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The folder ID.</param>
|
||||
/// <returns>The folder details, or null if not found.</returns>
|
||||
public async Task<FolderFullDto?> GetFolderById(Guid id) {
|
||||
var response = await Client.GetAsync($"/api/folder/{id}");
|
||||
|
||||
if (response.IsSuccessStatusCode) return await response.Content.ReadFromJsonAsync<FolderFullDto>();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new folder.
|
||||
/// </summary>
|
||||
|
||||
@@ -3,28 +3,37 @@ using Microsoft.Extensions.Options;
|
||||
namespace MilkStream.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to media files (original, thumbnails, previews) via the API.
|
||||
/// Builds media URLs (original, thumbnail, preview) authenticated via a token query parameter.
|
||||
/// Media is served by <c>MediaController</c> through <c><img></c>-accessible URLs, so the
|
||||
/// JWT is passed as a <c>token</c> query parameter rather than an Authorization header.
|
||||
/// </summary>
|
||||
public sealed class MediaService : AuthServiceBase {
|
||||
/// <inheritdoc />
|
||||
protected override HttpClient Client { get; init; }
|
||||
/// <inheritdoc />
|
||||
protected override ILogger Logger { get; init; }
|
||||
/// <param name="options">Service configuration options.</param>
|
||||
/// <param name="loginService">The login service providing the current auth token.</param>
|
||||
public sealed class MediaService(IOptions<ServiceOptions> options, LoginService loginService) {
|
||||
string BaseUrl => options.Value.BaseUrl.TrimEnd('/');
|
||||
|
||||
string TokenParam {
|
||||
get {
|
||||
var token = loginService.AuthInfo?.Token;
|
||||
return string.IsNullOrEmpty(token) ? "" : $"?token={token}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialises a new instance of the <see cref="MediaService"/> class.
|
||||
/// Gets the thumbnail URL for an asset.
|
||||
/// </summary>
|
||||
/// <param name="options">Service configuration options.</param>
|
||||
/// <param name="httpClientFactory">Factory for creating HTTP clients.</param>
|
||||
/// <param name="loginService">The login service for authentication.</param>
|
||||
/// <param name="logger">Logger instance.</param>
|
||||
public MediaService(
|
||||
IOptions<ServiceOptions> options,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
LoginService loginService,
|
||||
ILogger<MediaService> logger
|
||||
) : base(options, httpClientFactory, loginService, logger) {
|
||||
Client = base.Client;
|
||||
Logger = logger;
|
||||
}
|
||||
/// <param name="id">The asset ID.</param>
|
||||
public string ThumbUrl(Guid id) => $"{BaseUrl}/api/media/thumb/{id}{TokenParam}";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the preview URL for an asset.
|
||||
/// </summary>
|
||||
/// <param name="id">The asset ID.</param>
|
||||
public string PreviewUrl(Guid id) => $"{BaseUrl}/api/media/preview/{id}{TokenParam}";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the original image URL for an asset.
|
||||
/// </summary>
|
||||
/// <param name="id">The asset ID.</param>
|
||||
public string OriginalUrl(Guid id) => $"{BaseUrl}/api/media/original/{id}{TokenParam}";
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace MilkStream.Client;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for string operations.
|
||||
/// </summary>
|
||||
public class StringExtensions {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user