summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJake Mannens <jake@asger.xyz>2023-08-22 10:00:21 +1000
committerJake Mannens <jake@asger.xyz>2025-08-18 17:03:21 +1000
commitff715df3ccda51c1bb3d4b84f4391b4fdd1962dd (patch)
tree00cca236a9036896dc7a1d3688311c65f96159d8
parent4558ed9d6cbc59229cd56cc7a6c36b1df0b8199a (diff)
Finalised initial implementation of NSFW tag filtering
-rw-r--r--HBContext.cs4
-rw-r--r--MainLayout.razor2
-rw-r--r--Pages/Component/NsfwSwitch.razor8
-rw-r--r--Pages/Component/Switch.razor5
-rw-r--r--Pages/Component/TagSelectDialog.razor18
-rw-r--r--Pages/Gallery.razor44
-rw-r--r--Pages/TagDefinitions.razor29
-rw-r--r--Pages/Upload.razor14
-rw-r--r--Pages/Upload.razor.css9
-rw-r--r--Program.cs1
-rw-r--r--Services/TagService.cs79
-rw-r--r--Services/UserStateService.cs21
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
diff --git a/Program.cs b/Program.cs
index 4796825..b5d29af 100644
--- a/Program.cs
+++ b/Program.cs
@@ -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;
+}