summaryrefslogtreecommitdiff
path: root/Pages/Component
diff options
context:
space:
mode:
Diffstat (limited to 'Pages/Component')
-rw-r--r--Pages/Component/Dialog.razor38
-rw-r--r--Pages/Component/Dialog.razor.css21
-rw-r--r--Pages/Component/MediaTagTable.razor69
-rw-r--r--Pages/Component/MediaTagTable.razor.css3
-rw-r--r--Pages/Component/TabContainer.razor35
-rw-r--r--Pages/Component/TabContainer.razor.css19
-rw-r--r--Pages/Component/TabPane.razor29
-rw-r--r--Pages/Component/TabPane.razor.css1
-rw-r--r--Pages/Component/TagSelectDialog.razor59
-rw-r--r--Pages/Component/TagSelectDialog.razor.css31
10 files changed, 305 insertions, 0 deletions
diff --git a/Pages/Component/Dialog.razor b/Pages/Component/Dialog.razor
new file mode 100644
index 0000000..1e2929a
--- /dev/null
+++ b/Pages/Component/Dialog.razor
@@ -0,0 +1,38 @@
+<div style="@style" class="@(visible ? "visible" : "")">
+ @if(Title is not null) {
+ <p>@Title</p>
+ <hr/>
+ }
+ @ChildContent
+</div>
+
+@code {
+ [Parameter]
+ public string? Title { get; set; }
+
+ [Parameter]
+ public RenderFragment ChildContent { get; set; }
+
+ public bool Visible {
+ get => visible;
+ set {
+ visible = value;
+ StateHasChanged();
+ }
+ }
+
+ [Parameter]
+ public int HeightPixels { set => height = $"{value}px"; }
+ [Parameter]
+ public int HeightPercent { set => height = $"{value}%"; }
+
+ public void Show() => Visible = true;
+ public void Hide() => Visible = false;
+
+ private bool visible = false;
+
+ private string? height;
+
+ private string style =>
+ $"{(height is null ? "" : $"max-height:{height};")}";
+}
diff --git a/Pages/Component/Dialog.razor.css b/Pages/Component/Dialog.razor.css
new file mode 100644
index 0000000..ff34843
--- /dev/null
+++ b/Pages/Component/Dialog.razor.css
@@ -0,0 +1,21 @@
+div {
+ background: var(--col-dialog-bg);
+ border-radius: 20px;
+ box-shadow: 0px 5px 10px 10px rgb(0 0 0 / 25%);
+ display: flex;
+ flex-direction: column;
+ left: 50%;
+ opacity: 0;
+ padding: 20px;
+ position: absolute;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ transition: visibility 0.1s, opacity 0.1s linear;
+ visibility: hidden;
+ width: 450px;
+}
+
+div.visible {
+ opacity: 1;
+ visibility: visible;
+}
diff --git a/Pages/Component/MediaTagTable.razor b/Pages/Component/MediaTagTable.razor
new file mode 100644
index 0000000..1bd4de4
--- /dev/null
+++ b/Pages/Component/MediaTagTable.razor
@@ -0,0 +1,69 @@
+@inject IDbContextFactory<HBContext> dbFactory
+@inject ITagService tagService
+
+<link rel="stylesheet" href="@(nameof(HyperBooru)).styles.css"/>
+
+<table class="data-table">
+ <tr>
+ <th>Namespace</th>
+ <th>Tag Name</th>
+ <th></th>
+ </tr>
+ @foreach(var tag in userTags) {
+ bool isImplicit = IsImplicit(tag);
+ <tr>
+ <td>
+ @if(isImplicit) {
+ <i>@tag.Namespace</i>
+ } else {
+ @tag.Namespace
+ }
+ </td>
+ <td>
+ @if(isImplicit) {
+ <i>@tag.Name</i>
+ } else {
+ @tag.Name
+ }
+ </td>
+ <td><a href="javascript:;" @onclick=@(() => Delete(tag))>Delete</a></td>
+ </tr>
+ }
+</table>
+
+@code {
+ [Parameter]
+ public Media Media { get; set; }
+
+ private IEnumerable<TagDefinition> userTags {
+ get {
+ using var db = dbFactory.CreateDbContext();
+ if(db.Entry(Media).State == EntityState.Detached)
+ db.Attach(Media);
+ return GetTagRecursive(
+ Media.Tags
+ .Select(t => t.TagDefinition))
+ .Where(td => td.Source == TagSource.UserTag)
+ .OrderBy(td => td.Namespace)
+ .ThenBy(td => td.Name)
+ .ToArray();
+ }
+ }
+
+ public void Refresh() => StateHasChanged();
+
+ private void Delete(TagDefinition tagDef) {
+ tagService.RemoveTag(Media, tagDef);
+ StateHasChanged();
+ }
+
+ private bool IsImplicit(TagDefinition tagDef) =>
+ !Media.Tags
+ .Select(t => t.TagDefinition.Guid)
+ .Contains(tagDef.Guid);
+
+ private IEnumerable<TagDefinition> GetTagRecursive(IEnumerable<TagDefinition> tagDefs) =>
+ tagDefs
+ .Concat(tagDefs.SelectMany(td => GetTagRecursive(td.ImplicitTags)))
+ .DistinctBy(td => td.Guid);
+}
diff --git a/Pages/Component/MediaTagTable.razor.css b/Pages/Component/MediaTagTable.razor.css
new file mode 100644
index 0000000..dcf5e09
--- /dev/null
+++ b/Pages/Component/MediaTagTable.razor.css
@@ -0,0 +1,3 @@
+td {
+ font-size: 8pt;
+}
diff --git a/Pages/Component/TabContainer.razor b/Pages/Component/TabContainer.razor
new file mode 100644
index 0000000..3caab0b
--- /dev/null
+++ b/Pages/Component/TabContainer.razor
@@ -0,0 +1,35 @@
+<link rel="stylesheet" href="@(nameof(HyperBooru)).styles.css"/>
+
+<div class="tabs">
+ @foreach(var pane in Panes) {
+ <a href="javascript:;" @onclick=@(() => ActivePane = pane) class="@(pane == ActivePane ? "selected" : "")">
+ @pane.Title
+ </a>
+ }
+</div>
+
+<CascadingValue Value="this">
+ @ChildContent
+</CascadingValue>
+
+@code {
+ [Parameter]
+ public RenderFragment ChildContent { get; set; }
+
+ public TabPane? ActivePane { get; set; }
+ List<TabPane> Panes = new();
+
+ public void AddPane(TabPane tabPane) {
+ Panes.Add(tabPane);
+ if(Panes.Count == 1)
+ ActivePane = tabPane;
+ StateHasChanged();
+ }
+
+ public void RemovePane(TabPane tabPane) {
+ if(ActivePane == tabPane)
+ ActivePane = Panes.ElementAtOrDefault(0);
+ Panes.Remove(tabPane);
+ StateHasChanged();
+ }
+} \ No newline at end of file
diff --git a/Pages/Component/TabContainer.razor.css b/Pages/Component/TabContainer.razor.css
new file mode 100644
index 0000000..6a56021
--- /dev/null
+++ b/Pages/Component/TabContainer.razor.css
@@ -0,0 +1,19 @@
+div.tabs {
+ display: inherit !important;
+ border-bottom: 1px solid white;
+}
+
+div.tabs > a {
+ display: inline-block;
+ padding: 10px 10px 9px 10px;
+}
+
+div.tabs > a.selected {
+ border-bottom: 4px solid white;
+ padding-bottom: 5px;
+}
+
+div.tabs > a:hover {
+ background: rgba(255, 255, 255, 0.4);
+ filter: none;
+}
diff --git a/Pages/Component/TabPane.razor b/Pages/Component/TabPane.razor
new file mode 100644
index 0000000..ba4a13a
--- /dev/null
+++ b/Pages/Component/TabPane.razor
@@ -0,0 +1,29 @@
+@implements IDisposable
+
+<link rel="stylesheet" href="@(nameof(HyperBooru)).styles.css"/>
+
+@if(Parent.ActivePane == this) {
+ @ChildContent
+}
+
+@code {
+ [CascadingParameter]
+ private TabContainer Parent { get; set; }
+
+ [Parameter]
+ public string Title { get; set; }
+
+ [Parameter]
+ public RenderFragment ChildContent { get; set; }
+
+ protected override void OnInitialized() {
+ if (Parent is null)
+ throw new ArgumentNullException(nameof(Parent), "TabPane must exist within a TabContainer");
+
+ Parent.AddPane(this);
+ }
+
+ public void Dispose() {
+ Parent.RemovePane(this);
+ }
+} \ No newline at end of file
diff --git a/Pages/Component/TabPane.razor.css b/Pages/Component/TabPane.razor.css
new file mode 100644
index 0000000..5f28270
--- /dev/null
+++ b/Pages/Component/TabPane.razor.css
@@ -0,0 +1 @@
+ \ No newline at end of file
diff --git a/Pages/Component/TagSelectDialog.razor b/Pages/Component/TagSelectDialog.razor
new file mode 100644
index 0000000..590c8f2
--- /dev/null
+++ b/Pages/Component/TagSelectDialog.razor
@@ -0,0 +1,59 @@
+@inject HBContext db
+
+<link rel="stylesheet" href="@(nameof(HyperBooru)).styles.css"/>
+
+<Dialog Title=@(Title ?? "Select one or more tag(s)") @ref=dialog>
+ <input type="text" placeholder="Search"/>
+ <div class="tag-definitions">
+ @foreach(var tagDef in tagDefinitions) {
+ <input type="checkbox" id="tagDef-@tagDef.Guid" @onchange=@(e => Checked(tagDef, e.Value))/>
+ <label for="tagDef-@tagDef.Guid">@tagDef.Name</label>
+ }
+ </div>
+ <div class="button-container">
+ <button @onclick=@(() => dialog.Hide()) class="secondary">Cancel</button>
+ <button @onclick=@(() => Submit())>Accept</button>
+ </div>
+</Dialog>
+
+@code {
+ [Parameter]
+ public string? Title { get; set; }
+
+ [Parameter]
+ public EventCallback<TagDefinition[]> OnSubmit { get; set; }
+
+ public bool Visible {
+ get => visible;
+ set => visible = dialog.Visible = value;
+ }
+
+ private bool visible;
+
+ private Dialog dialog;
+
+ private IEnumerable<TagDefinition> tagDefinitions => db.TagDefinitions
+ .Where(td => td.Source == TagSource.UserTag)
+ .OrderBy(td => td.Name);
+
+ private List<TagDefinition> selected = new();
+
+ public void Show() => Visible = true;
+ public void Hide() => Visible = false;
+
+ private async void Submit() {
+ await OnSubmit.InvokeAsync(selected.ToArray());
+ selected.Clear();
+ Hide();
+ StateHasChanged();
+ }
+
+ private void Checked(TagDefinition tagDef, object? isChecked) {
+ if(isChecked is bool && (bool) isChecked == true)
+ if (!selected.Contains(tagDef))
+ selected.Add(tagDef);
+ else
+ if (selected.Contains(tagDef))
+ selected.Remove(tagDef);
+ }
+}
diff --git a/Pages/Component/TagSelectDialog.razor.css b/Pages/Component/TagSelectDialog.razor.css
new file mode 100644
index 0000000..f6f704e
--- /dev/null
+++ b/Pages/Component/TagSelectDialog.razor.css
@@ -0,0 +1,31 @@
+div.button-container {
+ display: flex;
+ justify-content: flex-end;
+}
+
+div.tag-definitions {
+ overflow-y: auto;
+ user-select: none;
+}
+
+div.tag-definitions label {
+ background: #555;
+ border-radius: 10px;
+ display: inline-block;
+ font-size: 10pt;
+ margin: 0 5px 5px 0;
+ padding: 5px 7px 5px 7px;
+ transition: background 0.1s linear;
+}
+
+div.tag-definitions label:hover {
+ background: #777;
+}
+
+div.tag-definitions input:checked + label {
+ background: #aaa;
+}
+
+div.tag-definitions input {
+ display: none;
+}