diff options
| author | Jake Mannens <jake@asger.xyz> | 2023-08-22 10:00:21 +1000 |
|---|---|---|
| committer | Jake Mannens <jake@asger.xyz> | 2025-08-18 17:03:21 +1000 |
| commit | ff715df3ccda51c1bb3d4b84f4391b4fdd1962dd (patch) | |
| tree | 00cca236a9036896dc7a1d3688311c65f96159d8 | |
| parent | 4558ed9d6cbc59229cd56cc7a6c36b1df0b8199a (diff) | |
Finalised initial implementation of NSFW tag filtering
| -rw-r--r-- | HBContext.cs | 4 | ||||
| -rw-r--r-- | MainLayout.razor | 2 | ||||
| -rw-r--r-- | Pages/Component/NsfwSwitch.razor | 8 | ||||
| -rw-r--r-- | Pages/Component/Switch.razor | 5 | ||||
| -rw-r--r-- | Pages/Component/TagSelectDialog.razor | 18 | ||||
| -rw-r--r-- | Pages/Gallery.razor | 44 | ||||
| -rw-r--r-- | Pages/TagDefinitions.razor | 29 | ||||
| -rw-r--r-- | Pages/Upload.razor | 14 | ||||
| -rw-r--r-- | Pages/Upload.razor.css | 9 | ||||
| -rw-r--r-- | Program.cs | 1 | ||||
| -rw-r--r-- | Services/TagService.cs | 79 | ||||
| -rw-r--r-- | Services/UserStateService.cs | 21 |
12 files changed, 188 insertions, 46 deletions
diff --git a/HBContext.cs b/HBContext.cs index 73ccdcb..74c040c 100644 --- a/HBContext.cs +++ b/HBContext.cs @@ -4,7 +4,7 @@ using HyperBooru.Services; namespace HyperBooru; public class HBContext : DbContext { - public static readonly Guid NSFWTag = new("EBDAD4F8-455A-4351-8017-1D4854D6FA38"); + public static readonly Guid NsfwTag = new("EBDAD4F8-455A-4351-8017-1D4854D6FA38"); public static readonly Guid IngestTag = new("EA212801-5BCC-4C0E-814F-FB9D30DB58BC"); public DbSet<HBObject> Objects { get; set; } @@ -40,7 +40,7 @@ public class HBContext : DbContext { modelBuilder.Entity<TagDefinition>().HasData(new TagDefinition[] { new() { ObjectId = -1, - Guid = NSFWTag, + Guid = NsfwTag, Source = TagSource.Internal, Name = "nsfw" }, diff --git a/MainLayout.razor b/MainLayout.razor index 7720fe2..714c925 100644 --- a/MainLayout.razor +++ b/MainLayout.razor @@ -10,7 +10,7 @@ <p id="nsfw-label">NSFW</p> <div id="nsfw-switch"> - <Switch/> + <NsfwSwitch/> </div> <form action="/Gallery" method="get"> <input type="text" name="q" placeholder="Search"/> diff --git a/Pages/Component/NsfwSwitch.razor b/Pages/Component/NsfwSwitch.razor new file mode 100644 index 0000000..d4e809b --- /dev/null +++ b/Pages/Component/NsfwSwitch.razor @@ -0,0 +1,8 @@ +@inject IUserStateService userState + +<Switch InitialValue=userState.ShowNsfw OnToggle=ToggleNsfw/> + +@code { + private void ToggleNsfw(bool showNsfw) => + userState.ShowNsfw = showNsfw; +}
\ No newline at end of file diff --git a/Pages/Component/Switch.razor b/Pages/Component/Switch.razor index ffb3543..d11ac81 100644 --- a/Pages/Component/Switch.razor +++ b/Pages/Component/Switch.razor @@ -3,6 +3,7 @@ <label> <input type="checkbox" + checked=@InitialValue @onchange=@(e => OnToggle.InvokeAsync((e.Value as bool?) ?? false)) hidden/> <div class="switch-outer"> @@ -11,5 +12,9 @@ </label> @code { + [Parameter] + public bool InitialValue { get; set; } = false; + + [Parameter] public EventCallback<bool> OnToggle { get; set; } } diff --git a/Pages/Component/TagSelectDialog.razor b/Pages/Component/TagSelectDialog.razor index 699ca3a..c7a7a49 100644 --- a/Pages/Component/TagSelectDialog.razor +++ b/Pages/Component/TagSelectDialog.razor @@ -1,4 +1,6 @@ @inject IDbContextFactory<HBContext> dbFactory +@inject ITagService tagService +@inject IUserStateService userState @implements IDisposable <link rel="stylesheet" href="@(nameof(HyperBooru)).styles.css"/> @@ -61,7 +63,10 @@ public void Show() => Visible = true; public void Hide() => Visible = false; - protected override void OnInitialized() => LoadTags(); + protected override void OnInitialized() { + userState.ShowNsfwChanged += ShowNsfwChanged; + LoadTags(); + } private void LoadTags() { db = dbFactory.CreateDbContext(); @@ -71,6 +76,11 @@ tagDefinitions = db.TagDefinitions .Where(td => td.Source == TagSource.UserTag) .OrderBy(td => td.Name) + .AsEnumerable() + .Where(td => userState.ShowNsfw || !tagService + .GetAllTags(td) + .Select(e => e.tagDefinition.Guid) + .Contains(HBContext.NsfwTag)) .Select(td => new Tuple<TagDefinition, bool>( td, selected.Contains(td.Guid)).ToValueTuple()) @@ -89,5 +99,11 @@ StateHasChanged(); } + public async void ShowNsfwChanged(object? sender, bool showNsfw) => + await InvokeAsync(() => { + LoadTags(); + StateHasChanged(); + }); + public void Dispose() => db.Dispose(); } diff --git a/Pages/Gallery.razor b/Pages/Gallery.razor index 8b1954f..36b2788 100644 --- a/Pages/Gallery.razor +++ b/Pages/Gallery.razor @@ -1,7 +1,9 @@ @page "/" @page "/Gallery" @inject IDbContextFactory<HBContext> dbFactory +@inject ITagService tagService @inject ISearchService searchService +@inject IUserStateService userState <PageTitle>@Title</PageTitle> @@ -38,24 +40,42 @@ private Media[] Media; + protected override void OnInitialized() => + userState.ShowNsfwChanged += ShowNsfwChanged; + protected override void OnParametersSet() => LoadMedia(); private void LoadMedia() { using var db = dbFactory.CreateDbContext(); + IEnumerable<Media> media = db.Media + .OrderByDescending(m => m.ObjectId) + .ToArray(); + + if(Query is not null) + media = searchService.Search(Query) + .OrderByDescending(m => m.ObjectId); + if(Ingest is not null && Ingest == true) { - Media = db.Media - .OrderByDescending(m => m.ObjectId) - .ToArray() - .Where(m => m.IsIngest) - .ToArray(); - } else { - if(Query is null) - Media = db.Media - .OrderByDescending(m => m.ObjectId) - .ToArray(); - else - Media = searchService.Search(Query); + media = media + .AsEnumerable() + .Where(m => m.IsIngest); } + + if(!userState.ShowNsfw) + media = media + .AsEnumerable() + .Where(m => !tagService.GetAllTags(m) + .Select(e => e.tagDefinition.Guid) + .Contains(HBContext.NsfwTag)); + + Media = media.ToArray(); + } + + private async void ShowNsfwChanged(object? sender, bool showNsfw) { + await InvokeAsync(() => { + LoadMedia(); + StateHasChanged(); + }); } } diff --git a/Pages/TagDefinitions.razor b/Pages/TagDefinitions.razor index a9e1ebe..7d9c3a1 100644 --- a/Pages/TagDefinitions.razor +++ b/Pages/TagDefinitions.razor @@ -1,6 +1,7 @@ @page "/TagDefinitions" @inject IDbContextFactory<HBContext> dbFactory @inject ITagService tagService +@inject IUserStateService userState <PageTitle>Tag Definitions</PageTitle> @@ -23,7 +24,10 @@ <td>@tagDef.Name</td> <td> <i> - @(string.Join(", ", tagDef.ImplicitTags.Select(it => it.Name).Order())) + @(string.Join(", ", tagDef.ImplicitTags + .Where(it => it.Source == TagSource.UserTag) + .Select(it => it.Name) + .Order())) </i> </td> <td> @@ -35,7 +39,7 @@ <a href="javascript:;" @onclick=@(() => PromptImplicitTags(tagDef))> Implicit Tags </a> - @if(tagDef.ImplicitTags.Select(td => td.Guid).Contains(HBContext.NSFWTag)) { + @if(tagDef.ImplicitTags.Select(td => td.Guid).Contains(HBContext.NsfwTag)) { <a href="javascript:;" @onclick=@(() => SetNsfw(tagDef, false))>Make SFW</a> } else { <a href="javascript:;" @onclick=@(() => SetNsfw(tagDef, true))>Make NSFW</a> @@ -100,8 +104,17 @@ .Where(td => td.Source == TagSource.UserTag) .OrderBy(td => td.Namespace) .ThenBy(td => td.Name) + .AsEnumerable() + .Where(td => userState.ShowNsfw || !tagService + .GetAllTags(td) + .Select(td => td.tagDefinition.Guid) + .Contains(HBContext.NsfwTag)) .ToArray(); + protected override void OnInitialized() => + userState.ShowNsfwChanged += ShowNsfwChanged; + + private void CreateTagDefinition() { if(string.IsNullOrEmpty(tagNamespace)) tagNamespace = null; @@ -155,11 +168,13 @@ } private void SetNsfw(TagDefinition tagDef, bool nsfw) { - if(nsfw) { - tagService.AddTag(tagDef.Guid, HBContext.NSFWTag); - } else { - tagService.RemoveTag(tagDef.Guid, HBContext.NSFWTag); - } + if(nsfw) + tagService.AddImplicitTag(tagDef.Guid, HBContext.NsfwTag); + else + tagService.RemoveImplicitTag(tagDef.Guid, HBContext.NsfwTag); StateHasChanged(); } + + private async void ShowNsfwChanged(object? sender, bool showNsfw) => + await InvokeAsync(() => StateHasChanged()); } diff --git a/Pages/Upload.razor b/Pages/Upload.razor index 33153d2..c139aef 100644 --- a/Pages/Upload.razor +++ b/Pages/Upload.razor @@ -2,20 +2,10 @@ <link rel="stylesheet" href="@(nameof(HyperBooru)).styles.css"/> -<div id="dropzone" class="@((dropHover ? "drop-hover" : ""))"> - <InputFile - multiple - title="" - accept="image/*" - OnChange=FileInputChange - @ondragenter=@(() => dropHover = true) - @ondragleave=@(() => dropHover = false)/> +<div id="dropzone"> <p>Drag a file to upload it</p> + <input type="file" accept="image/*"/> </div> @code { - private bool dropHover = false; - - private void FileInputChange(IFileListEntry[] files) { - } }
\ No newline at end of file diff --git a/Pages/Upload.razor.css b/Pages/Upload.razor.css index 4064467..76891de 100644 --- a/Pages/Upload.razor.css +++ b/Pages/Upload.razor.css @@ -6,6 +6,7 @@ position: relative; top: 50%; transform: translate(-50%, -50%); + transition: border-color 0.1s linear; width: 700px; } @@ -19,10 +20,14 @@ div#dropzone p { transform: translate(-50%, -50%); } -div#dropzone.drop-hover { +div#dropzone input { + display: none; +} + +div#dropzone:hover { border: 3px dashed white; } -div#dropzone.drop-hover p { +div#dropzone:hover p { color: white; }
\ No newline at end of file @@ -19,6 +19,7 @@ public class Program { builder.Services.AddDbContextFactory<HBContext>(); builder.Services.AddScoped<ISearchService, SearchService>(); builder.Services.AddScoped<ITagService, TagService>(); + builder.Services.AddSingleton<IUserStateService, UserStateService>(); var app = builder.Build(); diff --git a/Services/TagService.cs b/Services/TagService.cs index 8de52ff..860103b 100644 --- a/Services/TagService.cs +++ b/Services/TagService.cs @@ -14,12 +14,18 @@ public interface ITagService { public void RemoveTag(HBObject obj, TagDefinition tagDef); public void SetImplicitTags(TagDefinition tagDef, TagDefinition[] implicitTagDefs); public void SetImplicitTags(Guid tagDef, Guid[] implicitTagDefs); + public void AddImplicitTag(Guid tagDef, Guid implicitTagDef); + public void AddImplicitTag(TagDefinition tagDef, TagDefinition implicitTagDef); + public void RemoveImplicitTag(Guid tagDef, Guid implicitTagDef); + public void RemoveImplicitTag(TagDefinition tagDef, TagDefinition implicitTagDef); public void CreateTagDefinition(string name, string? @namespace); + public void DeleteTagDefinition(Guid tagDef); public void DeleteTagDefinition(TagDefinition tagDef); public void UpdateTagDefinition(Guid tagDef, string name, string? @namespace); public void UpdateTagDefinition(TagDefinition tagDef, string name, string? @namespace); public (TagDefinition tagDefinition, bool isImplicit)[] GetAllTags(Guid obj); public (TagDefinition tagDefinition, bool isImplicit)[] GetAllTags(HBObject obj); + public (TagDefinition tagDefinition, bool isImplicit)[] GetAllTags(TagDefinition tagDef); } public class TagService : ITagService { @@ -78,6 +84,31 @@ public class TagService : ITagService { public void SetImplicitTags(TagDefinition tagDef, TagDefinition[] implicitTagDefs) => SetImplicitTags(tagDef.Guid, implicitTagDefs.Select(td => td.Guid).ToArray()); + public void AddImplicitTag(Guid tagDef, Guid implicitTagDef) { + using var db = dbFactory.CreateDbContext(); + + var tag = db.TagDefinitions.First(td => td.Guid == tagDef); + var implicitTag = db.TagDefinitions.First(td => td.Guid == implicitTagDef); + + tag.ImplicitTags.Add(implicitTag); + db.SaveChanges(); + } + + public void AddImplicitTag(TagDefinition tagDef, TagDefinition implicitTagDef) => + AddImplicitTag(tagDef, implicitTagDef); + + public void RemoveImplicitTag(Guid tagDef, Guid implicitTagDef) { + using var db = dbFactory.CreateDbContext(); + + var tag = db.TagDefinitions.First(td => td.Guid == tagDef); + + tag.ImplicitTags.RemoveAll(td => td.Guid == implicitTagDef); + db.SaveChanges(); + } + + public void RemoveImplicitTag(TagDefinition tagDef, TagDefinition implicitTagDef) => + RemoveImplicitTag(tagDef, implicitTagDef); + public void CreateTagDefinition(string name, string? @namespace) { using var db = dbFactory.CreateDbContext(); @@ -91,20 +122,24 @@ public class TagService : ITagService { db.SaveChanges(); } - public void DeleteTagDefinition(TagDefinition tagDef) { + public void DeleteTagDefinition(Guid tagDef) { using var db = dbFactory.CreateDbContext(); - db.Entry(tagDef).State = EntityState.Unchanged; + + var tag = db.TagDefinitions.First(td => td.Guid == tagDef); using var transaction = db.Database.BeginTransaction(); db.Tags.RemoveRange( - db.Tags.Where(t => t.TagDefinition == tagDef)); - db.TagDefinitions.Remove(tagDef); + db.Tags.Where(t => t.TagDefinition.Guid == tagDef)); + db.TagDefinitions.Remove(tag); db.SaveChanges(); transaction.Commit(); } + public void DeleteTagDefinition(TagDefinition tagDef) => + DeleteTagDefinition(tagDef.Guid); + public void UpdateTagDefinition(Guid tagDef, string name, string? @namespace) { using var db = dbFactory.CreateDbContext(); @@ -122,11 +157,6 @@ public class TagService : ITagService { UpdateTagDefinition(tagDef.Guid, name, @namespace); public (TagDefinition tagDefinition, bool isImplicit)[] GetAllTags(Guid obj) { - IEnumerable<TagDefinition> GetTagRecursive(IEnumerable<TagDefinition> tagDefs) => - tagDefs - .Concat(tagDefs.SelectMany(td => GetTagRecursive(td.ImplicitTags))) - .DistinctBy(td => td.Guid); - using var db = dbFactory.CreateDbContext(); var @object = db.Objects.First(o => o.Guid == obj); @@ -139,4 +169,35 @@ public class TagService : ITagService { public (TagDefinition tagDefinition, bool isImplicit)[] GetAllTags(HBObject obj) => GetAllTags(obj.Guid); + + public (TagDefinition tagDefinition, bool isImplicit)[] GetAllTags(TagDefinition tagDef) { + using var db = dbFactory.CreateDbContext(); + + var tag = db.TagDefinitions.First(td => td.Guid == tagDef.Guid); + + return GetTagRecursive(tag.ImplicitTags) + .Select(td => new ValueTuple<TagDefinition, bool>(td, !tag.ImplicitTags.Select(it => it.Guid).Contains(td.Guid))) + .ToArray(); + } + + private List<TagDefinition> GetTagRecursive(IEnumerable<TagDefinition> tagDefs) { + var tags = new List<TagDefinition>(tagDefs); + + List<TagDefinition>? toProcessNext = null; + + while(true) { + toProcessNext = (toProcessNext ?? tags) + .SelectMany(td => td.ImplicitTags) + .Where(td => !tags.Select(td => td.Guid).Contains(td.Guid)) + .DistinctBy(td => td.Guid) + .ToList(); + + tags.AddRange(toProcessNext); + + if(toProcessNext.Count() == 0) + break; + } + + return tags; + } } diff --git a/Services/UserStateService.cs b/Services/UserStateService.cs new file mode 100644 index 0000000..dc742e1 --- /dev/null +++ b/Services/UserStateService.cs @@ -0,0 +1,21 @@ +namespace HyperBooru.Services; + +public interface IUserStateService { + public bool ShowNsfw { get; set; } + + public event EventHandler<bool> ShowNsfwChanged; +} + +public class UserStateService : IUserStateService { + public bool ShowNsfw { + get => showNsfw; + set { + showNsfw = value; + ShowNsfwChanged?.Invoke(this, value); + } + } + + public event EventHandler<bool> ShowNsfwChanged; + + private bool showNsfw = false; +} |
