diff options
Diffstat (limited to 'Pages/Component')
| -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 |
5 files changed, 240 insertions, 209 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; } = ""; |
