diff options
Diffstat (limited to 'Pages/Component')
| -rw-r--r-- | Pages/Component/Dialog.razor | 38 | ||||
| -rw-r--r-- | Pages/Component/Dialog.razor.css | 21 | ||||
| -rw-r--r-- | Pages/Component/MediaTagTable.razor | 69 | ||||
| -rw-r--r-- | Pages/Component/MediaTagTable.razor.css | 3 | ||||
| -rw-r--r-- | Pages/Component/TabContainer.razor | 35 | ||||
| -rw-r--r-- | Pages/Component/TabContainer.razor.css | 19 | ||||
| -rw-r--r-- | Pages/Component/TabPane.razor | 29 | ||||
| -rw-r--r-- | Pages/Component/TabPane.razor.css | 1 | ||||
| -rw-r--r-- | Pages/Component/TagSelectDialog.razor | 59 | ||||
| -rw-r--r-- | Pages/Component/TagSelectDialog.razor.css | 31 |
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; +} |
