diff options
| author | Jake Mannens <jake@asger.xyz> | 2026-05-25 01:06:59 +1000 |
|---|---|---|
| committer | Jake Mannens <jake@asger.xyz> | 2026-06-17 02:42:47 +1000 |
| commit | f79643b43cbb5f673d5b9d043d3ee1bdd54af869 (patch) | |
| tree | d3622f5996acc5407f27d28376c8c8ab0d3a58e1 /Pages | |
| parent | 308b0b03ed0875d8cc10ab15586ff12d0ea6ddf8 (diff) | |
Re-implemented existing features via the API
Diffstat (limited to 'Pages')
| -rw-r--r-- | Pages/Component/AboutDialog.razor | 44 | ||||
| -rw-r--r-- | Pages/Component/MediaTagTable.razor | 158 | ||||
| -rw-r--r-- | Pages/Component/TagSelectDialog.razor | 234 | ||||
| -rw-r--r-- | Pages/Component/TagSelectDialog.razor.css | 6 | ||||
| -rw-r--r-- | Pages/Component/Titlebar.razor | 7 | ||||
| -rw-r--r-- | Pages/TagDefinitions.razor | 77 | ||||
| -rw-r--r-- | Pages/ViewMedia.razor | 211 |
7 files changed, 400 insertions, 337 deletions
diff --git a/Pages/Component/AboutDialog.razor b/Pages/Component/AboutDialog.razor index fa7b1ca..888040e 100644 --- a/Pages/Component/AboutDialog.razor +++ b/Pages/Component/AboutDialog.razor @@ -1,9 +1,7 @@ -@using System.Reflection -@* - * @using Microsoft.AspNetCore.Hosting - * @inject IDbContextFactory<HBContext> dbFactory - * @inject IHostingEnvironment hostingEnvironment - *@ +@using Microsoft.AspNetCore.Components.WebAssembly.Hosting +@using System.Reflection +@inject IWebAssemblyHostEnvironment hostEnvironment +@inject HBSession hb @implements IDialog <Dialog @ref=dialog> @@ -676,10 +674,11 @@ </div> <a href="https://gitlab.com/plasmicplexus/HyperBooru-Server" target="_blank">Source</a> <div id="progressContainer"> - @if(progress.HasValue) { - var untagged = progress.Value.Untagged.ToString("N0"); - var total = progress.Value.Total.ToString("N0"); - var percent = (progress.Value.Untagged * 100f / progress.Value.Total).ToString("f1"); + @if(ingestStatistics is not null) { + var untagged = ingestStatistics.UntaggedMediaCount.ToString("N0"); + var total = ingestStatistics.TotalMediaCount.ToString("N0"); + var percent = + (ingestStatistics.UntaggedMediaCount * 100f / ingestStatistics.TotalMediaCount).ToString("f1"); <p id="progress">Untagged: @($"{untagged}/{total} ({percent}%)")</p> } <ProgressBar @ref=progressBar /> @@ -694,23 +693,15 @@ private ProgressBar progressBar; - private (long Untagged, long Total)? progress; + private IngestStatistics? ingestStatistics; public bool Visible { get => dialog.Visible; set { dialog.Visible = value; - if(value) { - // using var db = dbFactory.CreateDbContext(); - // progress = ( - // Untagged: db.Media - // .Where(m => m.Tags.Any(t => t.TagDefinition.ObjectId == (int) HBObjectId.IngestTag)) - // .Count(), - // Total: db.Media.Count() - // ); - // progressBar.Progress = (float) progress.Value!.Untagged / (float) progress.Value!.Total; - // InvokeAsync(() => StateHasChanged()); - } + InvokeAsync(() => StateHasChanged()); + if(value) + LoadProgressAsync(); } } @@ -731,8 +722,15 @@ #if DEBUG return "(Development)"; #else - return hostingEnvironment.IsDevelopment() ? "(Development)" : null; + return hostEnvironment.IsDevelopment() ? "(Development)" : null; #endif } } + + private async void LoadProgressAsync() { + ingestStatistics = await hb.Statistics.GetIngestStatisticsAsync(); + progressBar.Progress = + (float) ingestStatistics.UntaggedMediaCount / (float) ingestStatistics.TotalMediaCount; + await InvokeAsync(() => StateHasChanged()); + } } diff --git a/Pages/Component/MediaTagTable.razor b/Pages/Component/MediaTagTable.razor index 0524739..14ddc6d 100644 --- a/Pages/Component/MediaTagTable.razor +++ b/Pages/Component/MediaTagTable.razor @@ -1,75 +1,111 @@ @inject HBSession hb -<table class="data-table"> - <tr> - <th>Namespace</th> - <th>Tag Name</th> - <th></th> - </tr> - @foreach(var e in tagDefs) { - <tr> - <td> - @if(e.isImplicit) { - <i>@e.tagDef.Namespace</i> - } else { - @e.tagDef.Namespace - } - </td> - <td> - <a href="/Gallery?t=@(e.tagDef.TagDefinitionId)" class="nondecorated"> - @if(e.isImplicit) { - <i>@e.tagDef.Name</i> - } else { - @e.tagDef.Name - } - </a> - </td> - <td> - @if(!e.isImplicit) { - <a href="javascript:;" @onclick=@(() => Delete(e.tagDef))>Delete</a> - } else { - <a href="javascript:;" @onclick=@(() => MakeExplicit(e.tagDef))>Make Explicit</a> - } - </td> - </tr> - } -</table> +<LoadableContent T="List<TagTableEntry>" DataSource=LoadTagDefs @ref=tagContent> + <LoadingState> + <p><i>Loading...</i></p> + </LoadingState> + <ErrorState> + <p><i>Unable to fetch tags for this item!</i></p> + </ErrorState> + <LoadedState> + <table class="data-table"> + <tr> + <th>Namespace</th> + <th>Tag Name</th> + <th></th> + </tr> + @{ + var tags = tagContent.Data.Where(td => td.TagDefinition.Source == TagSource.UserTag); + } + @foreach(var e in tags) { + <tr> + <td> + @if(e.IsImplicit) { + <i>@e.TagDefinition.Namespace</i> + } else { + @e.TagDefinition.Namespace + } + </td> + <td> + <a href="/Gallery?t=@(e.TagDefinition.TagDefinitionId)" class="nondecorated"> + @if(e.IsImplicit) { + <i>@e.TagDefinition.Name</i> + } else { + @e.TagDefinition.Name + } + </a> + </td> + <td> + @if(!e.IsImplicit) { + <a href="javascript:;" @onclick=@(() => Delete(e.TagDefinition))>Delete</a> + } else { + <a href="javascript:;" @onclick=@(() => MakeExplicit(e.TagDefinition))>Make Explicit</a> + } + </td> + </tr> + } + </table> + </LoadedState> +</LoadableContent> @code { - [Parameter] - public Guid MediaId { get; set; } + [Parameter] + public Guid MediaId { get; set; } - private (TagDefinition tagDef, bool isImplicit)[] tagDefs = []; + public List<TagTableEntry> Data => tagContent.Data; - protected override void OnInitialized() => LoadTagDefs(); + private LoadableContent<List<TagTableEntry>> tagContent; - public void Refresh() { - LoadTagDefs(); - // StateHasChanged(); - } + private void Delete(TagDefinition tagDef) { + // tagService.RemoveTag(Media.Guid, tagDef.Guid); + // Refresh(); + } - private void Delete(TagDefinition tagDef) { - // tagService.RemoveTag(Media.Guid, tagDef.Guid); - // Refresh(); - } + private async Task<List<TagTableEntry>> LoadTagDefs() { + var itemTags = await hb.Media.GetTagsAsync(MediaId); + var allTags = await hb.Tag.GetTagDefinitionAsync(); - private async void LoadTagDefs() { - var tags = await hb.Media.GetTagsAsync(MediaId); + var tags = itemTags + .Select(td => new TagTableEntry() { + TagDefinition = td, + IsImplicit = false + }) + .ToList(); - tagDefs = tags.Select(td => (tagDef: td, isImplicit: false)).ToArray(); + while(true) { + var toAdd = allTags + .IntersectBy(tags + .SelectMany(t => t.TagDefinition.ImplicitTags), td => td.TagDefinitionId) + .Where(td => !tags + .Select(t => t.TagDefinition.TagDefinitionId) + .Contains(td.TagDefinitionId)); + if(toAdd.Count() == 0) + break; + tags.AddRange(toAdd.Select(td => new TagTableEntry() { + TagDefinition = td, + IsImplicit = true + })); + } - await InvokeAsync(() => StateHasChanged()); + return tags + .OrderBy(td => td.IsImplicit) + .ThenBy(td => td.TagDefinition.Namespace) + .ThenBy(td => td.TagDefinition.Name) + .ToList(); + } - // using var db = dbFactory.CreateDbContext(); - // var media = db.Media.First(m => m.ObjectId == Media.ObjectId); + private async Task MakeExplicit(TagDefinition tagDef) { + // TODO: Add error handling + await hb.Media.AddTagsAsync(MediaId, [ tagDef.TagDefinitionId ]); + //var index = + // tagDefs.IndexOf(tagDefs.First(td => td.tagDef.TagDefinitionId == tagDef.TagDefinitionId)); + //tagDefs[index].isImplicit = false; + tagContent.Load(); + await InvokeAsync(() => StateHasChanged()); + } - // tagDefs = tagService.GetAllTags(Media) - // .Where(e => e.tagDefinition.Source == TagSource.UserTag) - // .ToArray(); - } - - private void MakeExplicit(TagDefinition tagDef) { - // tagService.AddTag(Media, tagDef); - // Refresh(); - } + public record TagTableEntry { + public required TagDefinition TagDefinition { get; set; } + public required bool IsImplicit { get; set; } + } } diff --git a/Pages/Component/TagSelectDialog.razor b/Pages/Component/TagSelectDialog.razor index 99321fe..003a3b6 100644 --- a/Pages/Component/TagSelectDialog.razor +++ b/Pages/Component/TagSelectDialog.razor @@ -1,51 +1,56 @@ -@* - * @inject IDbContextFactory<HBContext> dbFactory - * @inject ITagService tagService - * @inject IUserService userService - *@ -@implements IDisposable +@inject HBSession hb @implements IDialog -<link rel="stylesheet" href="@(nameof(HyperBooru)).styles.css"/> - <Dialog Title=@(Title ?? "Select one or more tag(s)") @ref=dialog> - <input - type="text" - placeholder="Search" - autocorrect="off" - autocapitalize="off" - autocomplete="off" - @ref=queryInput - @oninput=QueryInput - @onkeypress=QueryKey - value=@query/> - <div class="tag-definitions"> - @for(int i = 0; i < tagDefinitions.Count(); i++) { - if(!MatchesQuery(tagDefinitions[i].tagDefinition)) - continue; - var local = i; - var ns = tagDefinitions[i].tagDefinition.Namespace; - var alias = tagDefinitions[i].tagDefinition.Alias; - var title = string.Join(" ", new[] { - ns, - alias is not null ? $"({alias})" : null - }); -@* - <input - type="checkbox" - id="tagDef-@tagDefinitions[i].tagDefinition.Guid" - @bind=tagDefinitions[local].selected /> - <label - for="tagDef-@tagDefinitions[i].tagDefinition.Guid" - title=@title> - @tagDefinitions[i].tagDefinition.Name - </label> -*@ - } - </div> + <LoadableContent + T="List<SelectableTag>" + DataSource=LoadTagsAsync + @ref=tagDefinitions> + + <LoadingState> + <LoadingSpinner/> + </LoadingState> + <ErrorState> + <ErrorIcon Size=60/> + <p><i>Error loading tags...</i></p> + </ErrorState> + <LoadedState> + <input + type="text" + placeholder="Search" + autocorrect="off" + autocapitalize="off" + autocomplete="off" + @ref=queryInput + @oninput=QueryInput + @onkeypress=QueryKey + value=@query/> + <div class="tag-definitions"> + @foreach(var td in MatchesQuery) { + var ns = td.TagDefinition.Namespace; + var alias = td.TagDefinition.Alias; + var title = string.Join(" ", new[] { + ns, + alias is not null ? $"({alias})" : null + }); + <input + type="checkbox" + id="tagDef-@td.TagDefinition.TagDefinitionId" + @bind=@td.Selected /> + <label + for="tagDef-@td.TagDefinition.TagDefinitionId" + title=@title> + @td.TagDefinition.Name + </label> + } + </div> + </LoadedState> + </LoadableContent> <ButtonContainer> <button @onclick=@(() => dialog.Hide()) class="secondary">Cancel</button> - <button @onclick=@(() => Submit())>Accept</button> + @if(state == ComponentState.Loaded) { + <button @onclick=@(() => Submit())>Accept</button> + } </ButtonContainer> </Dialog> @@ -57,21 +62,54 @@ public EventCallback<TagDefinition[]> OnSubmit { get; set; } public TagDefinition[] SelectedTags { get; set; } = - Array.Empty<TagDefinition>(); + Array.Empty<TagDefinition>(); public bool Visible { get => visible; set { - if(value) - LoadTags(); + if(value && tagDefinitions.Data is not null) + foreach(var td in tagDefinitions.Data) + td.Selected = false; query = null; visible = dialog.Visible = value; + + InvokeAsync(() => StateHasChanged()); } } - private (TagDefinition tagDefinition, bool selected)[] tagDefinitions; + private IEnumerable<SelectableTag> MatchesQuery { + get { + if(string.IsNullOrEmpty(query)) + return tagDefinitions.Data; - // private HBContext db; + var matchesAlias = tagDefinitions.Data + .FirstOrDefault(td => string.Equals( + td.TagDefinition.Alias, + query, + StringComparison.OrdinalIgnoreCase)); + var matchesName = tagDefinitions.Data + .FirstOrDefault(td => string.Equals( + td.TagDefinition.Name, + query, + StringComparison.OrdinalIgnoreCase)); + + if((matchesAlias ?? matchesName) is not null) { + return [ (matchesAlias ?? matchesName!) ]; + } else { + return tagDefinitions.Data.Where(td => { + if(string.Equals(td.TagDefinition.Alias, query, StringComparison.OrdinalIgnoreCase)) + return true; + if(string.Equals(td.TagDefinition.Name, query, StringComparison.OrdinalIgnoreCase)) + return true; + if(td.TagDefinition.Name.ToLower().Contains(query!.ToLower())) + return true; + return false; + }); + } + } + } + + private LoadableContent<List<SelectableTag>> tagDefinitions; private Dialog dialog; @@ -83,39 +121,33 @@ public void Show() => Visible = true; public void Hide() => Visible = false; - protected override void OnInitialized() { - // userService.UserSessionState.OnStateChange += ShowNsfwChanged; - LoadTags(); - } - - private void LoadTags() { - // db = dbFactory.CreateDbContext(); + private async Task<List<SelectableTag>> LoadTagsAsync() { + var selected = SelectedTags.Select(td => td.TagDefinitionId); - // var selected = SelectedTags.Select(td => td.Guid); + // TODO: Factor in whether show NSFW is actually selected + bool showNsfw = true; - // int[] nsfwTags = Array.Empty<int>(); + Guid[] nsfwTags = Array.Empty<Guid>(); // if(!userService.UserSessionState.ShowNsfw) // nsfwTags = tagService.TagsThatImply(HBContext.NsfwTag) // .Select(td => td.ObjectId) // .ToArray(); - // tagDefinitions = db.TagDefinitions - // .Include(td => td.ImplicitTags) - // .Where(td => td.Source == TagSource.UserTag) - // .OrderBy(td => td.Name) - // .AsEnumerable() - // .Where(td => userService.UserSessionState.ShowNsfw || !td.ImplicitTags - // .IntersectBy(nsfwTags, td => td.ObjectId) - // .Any()) - // .Select(td => new Tuple<TagDefinition, bool>( - // td, - // selected.Contains(td.Guid)).ToValueTuple()) - // .ToArray(); + return (await hb.Tag.GetTagDefinitionAsync()) + .Where(td => td.Source == TagSource.UserTag) + .OrderBy(td => td.Name) + .Where(td => td.Source == TagSource.UserTag) + .Where(td => showNsfw || !td.ImplicitTags.Intersect(nsfwTags).Any()) + .Select(td => new SelectableTag() { + TagDefinition = td, + Selected = selected.Contains(td.TagDefinitionId) + }) + .ToList(); } private void QueryInput(ChangeEventArgs e) { query = (string?) e.Value; - StateHasChanged(); + InvokeAsync(() => StateHasChanged()); } private void QueryKey(KeyboardEventArgs e) { @@ -127,61 +159,23 @@ return; } - int c = 0; - int? last = null; - for(int i = 0; i < tagDefinitions.Count(); i++) { - if(!MatchesQuery(tagDefinitions[i].tagDefinition)) - continue; - last = i; - c++; - } - - if(c == 1 && last is not null) - tagDefinitions[(int) last].selected = - !tagDefinitions[(int) last].selected; + if(MatchesQuery.Count() == 1) + MatchesQuery.First().Selected ^= true; query = null; - StateHasChanged(); - } - - private bool MatchesQuery(TagDefinition tagDef) { - // TagDefinition? singleTag = null; - - // if(string.IsNullOrEmpty(query)) - // return true; - - // singleTag = tagDefinitions.FirstOrDefault( - // e => string.Equals( - // e.tagDefinition.Alias, - // query, - // StringComparison.OrdinalIgnoreCase)).tagDefinition; - - // if(singleTag is not null) - // return tagDef.Guid == singleTag.Guid; - - // singleTag = tagDefinitions.FirstOrDefault( - // e => string.Equals( - // e.tagDefinition.Name, - // query, - // StringComparison.OrdinalIgnoreCase)).tagDefinition; - - // if(singleTag is not null) - // return tagDef.Guid == singleTag.Guid; - - // return tagDef.Name.ToLower().Contains(query.ToLower()); - return false; + InvokeAsync(() => StateHasChanged()); } private async void Submit() { await OnSubmit.InvokeAsync( - tagDefinitions - .Where(e => e.selected) - .Select(e => e.tagDefinition) + tagDefinitions.Data + .Where(td => td.Selected) + .Select(td => td.TagDefinition) .ToArray()); - for(int i = 0; i < tagDefinitions.Count(); i++) - tagDefinitions[i].selected = false; + foreach(var td in tagDefinitions.Data) + td.Selected = false; Hide(); - StateHasChanged(); + await InvokeAsync(() => StateHasChanged()); } // public async void ShowNsfwChanged(UserSessionState userSessionState) => @@ -190,8 +184,8 @@ // StateHasChanged(); // }); - public void Dispose() { - // db.Dispose(); - // userService.UserSessionState.OnStateChange -= ShowNsfwChanged; + private record SelectableTag { + public required TagDefinition TagDefinition { get; set; } + public bool Selected { get; set; } = false; } } diff --git a/Pages/Component/TagSelectDialog.razor.css b/Pages/Component/TagSelectDialog.razor.css index dadd0c4..3c8d92a 100644 --- a/Pages/Component/TagSelectDialog.razor.css +++ b/Pages/Component/TagSelectDialog.razor.css @@ -1,4 +1,8 @@ -div.tag-definitions { +p { + text-align: center; +} + +div.tag-definitions { max-height: 450px; overflow-y: auto; user-select: none; diff --git a/Pages/Component/Titlebar.razor b/Pages/Component/Titlebar.razor index 521fb46..65af89a 100644 --- a/Pages/Component/Titlebar.razor +++ b/Pages/Component/Titlebar.razor @@ -51,8 +51,7 @@ <a class="desktop" href="/TagDefinitions">Tags</a> <a class="desktop" href="/Gallery?ingest=true">Ingest</a> <a class="desktop" href="/Upload">Upload</a> - @* <a class="desktop" href="javascript:;" @onclick=@(() => aboutDialog.Show())>About</a> *@ - <a class="desktop" href="javascript:;">About</a> + <a class="desktop" href="javascript:;" @onclick=@(() => aboutDialog.Show())>About</a> <p class="desktop" id="nsfw-label">NSFW</p> <div id="nsfw-switch" class="desktop"> @@ -63,7 +62,7 @@ </form> <a class="desktop" href="javascript:logout();">Logout</a> </div> - @* <AboutDialog @ref=aboutDialog/> *@ + <AboutDialog @ref=aboutDialog/> } else { <div id="navbar"> <h2>Login</h2> @@ -84,7 +83,7 @@ } @code { - // private AboutDialog aboutDialog; + private AboutDialog aboutDialog; public string Username { get; set; } = ""; public string Password { get; set; } = ""; diff --git a/Pages/TagDefinitions.razor b/Pages/TagDefinitions.razor index 7ce5400..b9f90d6 100644 --- a/Pages/TagDefinitions.razor +++ b/Pages/TagDefinitions.razor @@ -5,6 +5,7 @@ * @inject IUserService userService * @implements IDisposable *@ +@inject HBSession hb <PageTitle>Tag Definitions</PageTitle> @@ -24,28 +25,27 @@ <th></th> </tr> @foreach(var tagDef in tagDefinitions.Where(td => td.Namespace == ns)) { - @* <tr data-guid="@tagDef.Guid"> *@ - <tr> + <tr data-guid="@tagDef.TagDefinitionId"> <td>@tagDef.Alias</td> <td> -@* - <a href="/Gallery?t=@tagDef.Guid" class="nondecorated"> + <a href="/Gallery?t=@tagDef.TagDefinitionId" class="nondecorated"> @tagDef.Name </a> -*@ </td> <td> <i> @{ - // var implicitTags = tagDef.ImplicitTags - // .Where(td => td.Source == TagSource.UserTag); - // foreach(var tag in implicitTags) { - // <a href="/Gallery?t=@tag.Guid" class="nondecorated"> - // @tag.Name - // </a> - // if(tag != implicitTags.Last()) - // @(", ") - // } + var implicitTags = tagDefinitions + .IntersectBy(tagDef.ImplicitTags, td => td.TagDefinitionId) + .OrderBy(td => td.Name); + // .Where(td => td.Source == TagSource.UserTag); + foreach(var tag in implicitTags) { + <a href="/Gallery?t=@tag.TagDefinitionId" class="nondecorated"> + @tag.Name + </a> + if(tag != implicitTags.Last()) + @(", ") + } } </i> </td> @@ -97,37 +97,38 @@ private TagDefinition[] tagDefinitions; - private string?[] tagNamespaces; + private string[] tagNamespaces = Array.Empty<string>(); // protected override void OnInitialized() => // userService.UserSessionState.OnStateChange += ShowNsfwChanged; protected override void OnParametersSet() => - LoadTags(); + LoadTagsAsync(); + + private async void LoadTagsAsync() { + bool showNsfw = true; - private void LoadTags() { - // int[] nsfwTags = Array.Empty<int>(); + Guid[] nsfwTags = Array.Empty<Guid>(); // if(!userService.UserSessionState.ShowNsfw) // nsfwTags = tagService.TagsThatImply(HBContext.NsfwTag) // .Select(td => td.ObjectId) // .ToArray(); - // tagDefinitions = dbFactory.CreateDbContext().TagDefinitions - // .Include(td => td.ImplicitTags) - // .Where(td => td.Source == TagSource.UserTag) - // .OrderBy(td => td.Namespace) - // .ThenBy(td => td.Name) - // .AsEnumerable() - // .Where(td => userService.UserSessionState.ShowNsfw || !td.ImplicitTags - // .IntersectBy(nsfwTags, td => td.ObjectId) - // .Any()) - // .ToArray(); + tagDefinitions = (await hb.Tag.GetTagDefinitionAsync()) + // TODO: Limit shown tags to user-tags + //.Where(td => td.Source == TagSource.UserTag) + .OrderBy(td => td.Namespace) + .ThenBy(td => td.Name) + .Where(td => showNsfw || !td.ImplicitTags.Intersect(nsfwTags).Any()) + .ToArray(); + + tagNamespaces = tagDefinitions + .Select(td => td.Namespace) + .Order() + .Distinct() + .ToArray(); - // tagNamespaces = tagDefinitions - // .Select(td => td.Namespace) - // .Order() - // .Distinct() - // .ToArray(); + await InvokeAsync(() => StateHasChanged()); } private void PromptToDelete(TagDefinition toDelete) { @@ -141,7 +142,7 @@ // tagService.DeleteTagDefinition(toDelete); // deleteTagDialog.Hide(); - // LoadTags(); + // LoadTagsAsync(); // StateHasChanged(); } @@ -153,7 +154,7 @@ } private void TagUpdated(object? sender, EventArgs e) { - LoadTags(); + LoadTagsAsync(); StateHasChanged(); } @@ -169,7 +170,7 @@ return; // tagService.SetImplicitTags(toEditImplicit, tagDefs); - LoadTags(); + LoadTagsAsync(); StateHasChanged(); } @@ -178,13 +179,13 @@ // tagService.AddImplicitTag(tagDef.Guid, HBContext.NsfwTag); // else // tagService.RemoveImplicitTag(tagDef.Guid, HBContext.NsfwTag); - // LoadTags(); + // LoadTagsAsync(); // StateHasChanged(); } // private async void ShowNsfwChanged(UserSessionState userSessionState) => // await InvokeAsync(() => { - // LoadTags(); + // LoadTagsAsync(); // StateHasChanged(); // }); diff --git a/Pages/ViewMedia.razor b/Pages/ViewMedia.razor index e210e79..73ded5f 100644 --- a/Pages/ViewMedia.razor +++ b/Pages/ViewMedia.razor @@ -1,9 +1,9 @@ @page "/ViewMedia" -@* @using HyperBooru.Util*@ +@inject ISourceService sourceService @inject HBSession hb @inject IJSRuntime jsRuntime -<PageTitle>@title</PageTitle> +<PageTitle>@Title</PageTitle> <script suppress-warning="BL9992"> function toggleSidebar() { @@ -44,7 +44,6 @@ <div id="metadata-container"> <div id="metadata-fileinfo"> @if(infoEditMode) { -@* <form action="javascript:;" @onsubmit=@(() => ApplyInfoEdit(true))> <table id="edit-metadata"> <tr> @@ -57,13 +56,11 @@ </tr> </table> </form> -*@ } else { <p>Title: <i>@(media?.ShortDescription ?? "None")</i></p> <p class="newlines">Description:<br/><i>@(media?.LongDescription ?? "None")</i></p> } -@* - <p>Resolution: @(media.CurrentUploadedFile.Width)x@(media.CurrentUploadedFile.Height)</p> + @*<p>Resolution: @(media.CurrentUploadedFile.Width)x@(media.CurrentUploadedFile.Height)</p>*@ <p class="heading">Upload history</p> <hr/> <table id="uploaded-files" class="data-table"> @@ -75,36 +72,45 @@ <th>Size</th> <th>Original Checksum</th> </tr> - @foreach(var file in media.UploadedFiles.OrderByDescending(uf => uf.UploadTime)) { - string? sourceUrl = null; - if(file.Filename is not null) - sourceUrl = sourceService.GetUrlFromFilename(file.Filename); - <tr> - <td title=@file.CreateTime?.ToString()> - @(file.CreateTime?.ToString("d") ?? "N/A") - </td> - <td title=@file.LastWriteTime?.ToString()> - @(file.LastWriteTime?.ToString("d") ?? "N/A") - </td> - <td title=@file.UploadTime>@(file.UploadTime.ToString("d"))</td> - <td title=@(file.Path is not null ? $"{file.Path.Replace('\\', '/')}/{file.Filename}" : file.Filename)> - @if(sourceUrl is not null) { - <a class="nondecorated" target="_blank" href=@sourceUrl>@file.Filename</a> - } else { - @file.Filename - } - </td> - <td title=@file.Length>@file.Length.ToBytesSI()</td> - <td - title=@(file.Checksum + (file.ChecksumVerified ? " (verified)" : "")) - class=@(file.ChecksumVerified ? "verified" : null)> - - @file.Checksum.Substring(0, 8) - </td> - </tr> - } + <LoadableContent T="List<UploadedFile>" DataSource=LoadUploadedFiles @ref=uploadedFilesContent> + <LoadingState> + <p><i>Loading...</i></p> + </LoadingState> + <ErrorState> + <p><i>Unable to fetch file info for this item!</i></p> + </ErrorState> + <LoadedState> + @foreach(var file in uploadedFilesContent.Data!) { + string? sourceUrl = null; + if(file.Filename is not null) + sourceUrl = sourceService.GetUrlFromFilename(file.Filename); + <tr> + <td title=@file.CreateTime?.ToString()> + @(file.CreateTime?.ToString("d") ?? "N/A") + </td> + <td title=@file.LastWriteTime?.ToString()> + @(file.LastWriteTime?.ToString("d") ?? "N/A") + </td> + <td title=@file.UploadTime>@(file.UploadTime.ToString("d"))</td> + <td title=@(file.Path is not null ? $"{file.Path.Replace('\\', '/')}/{file.Filename}" : file.Filename)> + @if(sourceUrl is not null) { + <a class="nondecorated" target="_blank" href=@sourceUrl>@file.Filename</a> + } else { + @file.Filename + } + </td> + <td title=@file.Length>@file.Length.ToBytesSI()</td> + <td + title=@(file.Checksum + (file.ChecksumVerified ? " (verified)" : "")) + class=@(file.ChecksumVerified ? "verified" : null)> + + @file.Checksum.Substring(0, 8) + </td> + </tr> + } + </LoadedState> + </LoadableContent> </table> -*@ </div> <div id="metadata-tags"> <p class="heading">Tags</p> @@ -114,8 +120,7 @@ </div> <div id="button-container"> <ButtonContainer> -@* - <button @onclick=@(() => deleteDialog.Show()) class="warning" data-keyboard-shortcut="d"> + <button @onclick=@(() => deleteDialog.Show()) class="warning" data-keyboard-shortcut="d"> <img src="/images/trash.svg"/> <p><u>D</u>elete</p> </button> @@ -127,14 +132,13 @@ <img src="/images/book.svg"/> <p>View <u>O</u>CR</p> </button> -*@ @if(infoEditMode) { @* - <button @onclick=@(() => ApplyInfoEdit(false)) class="secondary"> + <button @onclick=@(() => await ApplyInfoEdit(false)) class="secondary"> <img src="/images/cross.svg"/> <p>Cancel</p> </button> - <button @onclick=@(() => ApplyInfoEdit(true))> + <button @onclick=@(() => await ApplyInfoEdit(true))> <img src="/images/checkmark.svg"/> <p>Apply</p> </button> @@ -168,7 +172,6 @@ </div> </div> -@* <Dialog Title="Delete this media?" @ref=deleteDialog> <ButtonContainer> <button @onclick=@(() => deleteDialog.Hide()) class="secondary">Cancel</button> @@ -177,11 +180,13 @@ </Dialog> <Dialog Title="OCR Data" @ref=ocrDialog> - @if(media.OcrData is null) { + @*@if(media.OcrData is null) {*@ <p><center>This media item hasn't been scanned yet!</center></p> +@* } else { <code style="max-height:400px;">@media.OcrData?.Text</code> } +*@ <ButtonContainer> <button @onclick=@(() => ocrDialog.Hide())>Close</button> </ButtonContainer> @@ -191,14 +196,15 @@ Title="Select one or more tag(s) to add" OnSubmit=AddTags @ref=tagDialog/> -*@ @code { [Parameter] [SupplyParameterFromQuery(Name = "m")] public Guid MediaId { get; set; } - private ApiModels.Media media; + private ApiModels.Media? media; + + private LoadableContent<List<UploadedFile>> uploadedFilesContent; private string title; @@ -207,9 +213,9 @@ private string? longDescription; private MediaTagTable mediaTagTable; - // private Dialog deleteDialog; - // private Dialog ocrDialog; - // private TagSelectDialog tagDialog; + private Dialog deleteDialog; + private Dialog ocrDialog; + private TagSelectDialog tagDialog; private ElementReference shortDescriptionInput; @@ -223,56 +229,81 @@ private async void LoadMedia() { media = await hb.Media.GetAsync(MediaId); - // using var db = dbFactory.CreateDbContext(); - // media = db.Media - // .Include(m => m.Tags) - // .ThenInclude(t => t.TagDefinition) - // .Include(m => m.CurrentUploadedFile) - // .Include(m => m.UploadedFiles) - // .Include(m => m.OcrData) - // .First(m => m.Guid == MediaId); + await InvokeAsync(() => StateHasChanged()); + } + + private async Task<List<UploadedFile>> LoadUploadedFiles() { + return (await hb.Media.GetUploadedFilesAsync(MediaId)) + .OrderByDescending(uf => uf.UploadTime) + .ToList(); + } - // title = media.DisplayName ?? "Media View"; - // InvokeAsync(() => StateHasChanged()); + private void AddTags(TagDefinition[] tagDefs) { + // foreach(var tagDef in tagDefs) + // tagService.AddTag(media, tagDef); + // mediaTagTable.Refresh(); } - // private void AddTags(TagDefinition[] tagDefs) { - // foreach(var tagDef in tagDefs) - // tagService.AddTag(media, tagDef); - // mediaTagTable.Refresh(); - // } + private async void SetIngest(bool ingest) { + // mediaService.SetIngest(media, ingest); + LoadMedia(); - private async void SetIngest(bool ingest) { - // mediaService.SetIngest(media, ingest); - LoadMedia(); + if(ingest) + StateHasChanged(); + else + await jsRuntime.InvokeVoidAsync("history.back"); + } - if(ingest) - StateHasChanged(); - else - await jsRuntime.InvokeVoidAsync("history.back"); - } + private string Title { + get { + if(media is null) + return "View Media"; - private bool InfoEditMode { - get => infoEditMode; - set { - shortDescription = media.ShortDescription; - longDescription = media.LongDescription; - infoEditMode = value; - StateHasChanged(); - } - } + if(media.ShortDescription is not null) + return media.ShortDescription; - // private void ApplyInfoEdit(bool apply) { - // if(apply) { - // mediaService.SetDescription(media, shortDescription, longDescription); - // LoadMedia(); - // } + if(uploadedFilesContent.Data is null) + return $"Media ({media.MediaId.ToString().ToUpper().Substring(0, 8)})"; + + return uploadedFilesContent.Data + .OrderBy(f => f.UploadTime) + .FirstOrDefault()?.Filename ?? media.MediaId.ToString().ToUpper(); + } + } + + private bool InfoEditMode { + get => infoEditMode; + set { + shortDescription = media.ShortDescription; + longDescription = media.LongDescription; + infoEditMode = value; + InvokeAsync(() => StateHasChanged()); + } + } - // infoEditMode = false; - // } + private async Task ApplyInfoEdit(bool apply) { + if(apply) { + var updatedMedia = new ApiModels.Media() { + MediaId = MediaId, + ShortDescription = shortDescription?.NullIfEmpty(), + LongDescription = longDescription?.NullIfEmpty() + }; - // private async void DeleteMedia() { - // mediaService.Delete(media); - // await jsRuntime.InvokeVoidAsync("history.back"); - // } + await hb.Media.UpdateAsync(updatedMedia); + + media = updatedMedia; + } + + infoEditMode = false; + await InvokeAsync(() => StateHasChanged()); + } + + private async Task DeleteMedia() { + await hb.Media.DeleteAsync(MediaId); + + deleteDialog.Hide(); + + // TODO: Use the NavigationManager properly + await jsRuntime.InvokeVoidAsync("history.back"); + } } |
