summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig7
-rw-r--r--.gitattributes63
-rw-r--r--App.razor19
-rw-r--r--Controllers/LoginController.cs49
-rw-r--r--MainLayout.razor11
-rw-r--r--MainLayout.razor.css9
-rw-r--r--Pages/Component/NsfwSwitch.razor8
-rw-r--r--Pages/Component/Titlebar.razor87
-rw-r--r--Pages/Login.razor17
-rw-r--r--Pages/_Host.cshtml37
-rw-r--r--Properties/launchSettings.json31
-rw-r--r--Server.Client/Client.csproj22
-rw-r--r--Server.Client/IDialog.cs (renamed from IDialog.cs)2
-rw-r--r--Server.Client/LICENSE.txt (renamed from LICENSE.txt)0
-rw-r--r--Server.Client/Layout/MainLayout.razor15
-rw-r--r--Server.Client/Layout/MainLayout.razor.css30
-rw-r--r--Server.Client/Pages/Component/AboutDialog.razor (renamed from Pages/Component/AboutDialog.razor)24
-rw-r--r--Server.Client/Pages/Component/AboutDialog.razor.css (renamed from Pages/Component/AboutDialog.razor.css)0
-rw-r--r--Server.Client/Pages/Component/ButtonContainer.razor (renamed from Pages/Component/ButtonContainer.razor)0
-rw-r--r--Server.Client/Pages/Component/ButtonContainer.razor.css (renamed from Pages/Component/ButtonContainer.razor.css)0
-rw-r--r--Server.Client/Pages/Component/Dialog.razor (renamed from Pages/Component/Dialog.razor)0
-rw-r--r--Server.Client/Pages/Component/Dialog.razor.css (renamed from Pages/Component/Dialog.razor.css)0
-rw-r--r--Server.Client/Pages/Component/MediaTagTable.razor (renamed from Pages/Component/MediaTagTable.razor)39
-rw-r--r--Server.Client/Pages/Component/MediaTagTable.razor.css (renamed from Pages/Component/MediaTagTable.razor.css)0
-rw-r--r--Server.Client/Pages/Component/MobileMenu.razor (renamed from Pages/Component/MobileMenu.razor)0
-rw-r--r--Server.Client/Pages/Component/MobileMenu.razor.css (renamed from Pages/Component/MobileMenu.razor.css)0
-rw-r--r--Server.Client/Pages/Component/NsfwSwitch.razor6
-rw-r--r--Server.Client/Pages/Component/ProgressBar.razor (renamed from Pages/Component/ProgressBar.razor)0
-rw-r--r--Server.Client/Pages/Component/ProgressBar.razor.css (renamed from Pages/Component/ProgressBar.razor.css)0
-rw-r--r--Server.Client/Pages/Component/RedirectLogin.razor (renamed from Pages/Component/RedirectLogin.razor)0
-rw-r--r--Server.Client/Pages/Component/Switch.razor (renamed from Pages/Component/Switch.razor)4
-rw-r--r--Server.Client/Pages/Component/Switch.razor.css (renamed from Pages/Component/Switch.razor.css)0
-rw-r--r--Server.Client/Pages/Component/TabContainer.razor (renamed from Pages/Component/TabContainer.razor)0
-rw-r--r--Server.Client/Pages/Component/TabContainer.razor.css (renamed from Pages/Component/TabContainer.razor.css)0
-rw-r--r--Server.Client/Pages/Component/TabPane.razor (renamed from Pages/Component/TabPane.razor)0
-rw-r--r--Server.Client/Pages/Component/TabPane.razor.css (renamed from Pages/Component/TabPane.razor.css)0
-rw-r--r--Server.Client/Pages/Component/TagEditDialog.razor (renamed from Pages/Component/TagEditDialog.razor)8
-rw-r--r--Server.Client/Pages/Component/TagEditDialog.razor.css (renamed from Pages/Component/TagEditDialog.razor.css)0
-rw-r--r--Server.Client/Pages/Component/TagSelectDialog.razor (renamed from Pages/Component/TagSelectDialog.razor)101
-rw-r--r--Server.Client/Pages/Component/TagSelectDialog.razor.css (renamed from Pages/Component/TagSelectDialog.razor.css)0
-rw-r--r--Server.Client/Pages/Component/Titlebar.razor98
-rw-r--r--Server.Client/Pages/Component/Titlebar.razor.css (renamed from Pages/Component/Titlebar.razor.css)0
-rw-r--r--Server.Client/Pages/Gallery.razor (renamed from Pages/Gallery.razor)94
-rw-r--r--Server.Client/Pages/Gallery.razor.css (renamed from Pages/Gallery.razor.css)0
-rw-r--r--Server.Client/Pages/Login.razor17
-rw-r--r--Server.Client/Pages/Login.razor.css (renamed from Pages/Login.razor.css)0
-rw-r--r--Server.Client/Pages/NotFound.razor5
-rw-r--r--Server.Client/Pages/TagDefinitions.razor (renamed from Pages/TagDefinitions.razor)126
-rw-r--r--Server.Client/Pages/TagDefinitions.razor.css (renamed from Pages/TagDefinitions.razor.css)0
-rw-r--r--Server.Client/Pages/Upload.razor (renamed from Pages/Upload.razor)1
-rw-r--r--Server.Client/Pages/Upload.razor.css (renamed from Pages/Upload.razor.css)0
-rw-r--r--Server.Client/Pages/ViewMedia.razor (renamed from Pages/ViewMedia.razor)139
-rw-r--r--Server.Client/Pages/ViewMedia.razor.css (renamed from Pages/ViewMedia.razor.css)0
-rw-r--r--Server.Client/Program.cs16
-rw-r--r--Server.Client/Routes.razor6
-rw-r--r--Server.Client/_Imports.razor13
-rw-r--r--Server.Client/wwwroot/appsettings.Development.json8
-rw-r--r--Server.Client/wwwroot/appsettings.json8
-rw-r--r--Server/Components/App.razor24
-rw-r--r--Server/Components/Pages/Error.razor36
-rw-r--r--Server/Components/_Imports.razor11
-rw-r--r--Server/Controllers/ApiFeedController.cs (renamed from Controllers/ApiFeedController.cs)2
-rw-r--r--Server/Controllers/ApiMediaController.cs (renamed from Controllers/ApiMediaController.cs)2
-rw-r--r--Server/Controllers/ApiTagController.cs (renamed from Controllers/ApiTagController.cs)1
-rw-r--r--Server/Controllers/ApiUserController.cs (renamed from Controllers/ApiUserController.cs)0
-rw-r--r--Server/Controllers/MediaController.cs (renamed from Controllers/MediaController.cs)1
-rw-r--r--Server/Dockerfile (renamed from Dockerfile)0
-rw-r--r--Server/ExceptionMiddleware.cs (renamed from ExceptionMiddleware.cs)0
-rw-r--r--Server/HBContext.cs (renamed from HBContext.cs)0
-rw-r--r--Server/HBObject.cs (renamed from HBObject.cs)0
-rw-r--r--Server/LICENSE.txt661
-rw-r--r--Server/Media.cs (renamed from Media.cs)0
-rw-r--r--Server/Migrations/20260131125650_InitialMigration.Designer.cs (renamed from Migrations/20260131125650_InitialMigration.Designer.cs)0
-rw-r--r--Server/Migrations/20260131125650_InitialMigration.cs (renamed from Migrations/20260131125650_InitialMigration.cs)0
-rw-r--r--Server/Migrations/HBContextModelSnapshot.cs (renamed from Migrations/HBContextModelSnapshot.cs)0
-rw-r--r--Server/Program.cs (renamed from Program.cs)52
-rw-r--r--Server/Properties/launchSettings.json25
-rw-r--r--Server/Server.csproj (renamed from Server.csproj)35
-rw-r--r--Server/Services/ConfigService.cs (renamed from Services/ConfigService.cs)0
-rw-r--r--Server/Services/FeedService.cs (renamed from Services/FeedService.cs)0
-rw-r--r--Server/Services/MediaService.cs (renamed from Services/MediaService.cs)0
-rw-r--r--Server/Services/OcrService.cs (renamed from Services/OcrService.cs)0
-rw-r--r--Server/Services/SourceService.cs (renamed from Services/SourceService.cs)0
-rw-r--r--Server/Services/TagService.cs (renamed from Services/TagService.cs)0
-rw-r--r--Server/Services/UserService.cs (renamed from Services/UserService.cs)0
-rw-r--r--Server/Tag.cs (renamed from Tag.cs)0
-rw-r--r--Server/Todo.md (renamed from Todo.md)0
-rw-r--r--Server/User.cs (renamed from User.cs)0
-rw-r--r--Server/Util.cs (renamed from Util.cs)0
-rw-r--r--Server/appsettings.Development.json8
-rw-r--r--Server/appsettings.json (renamed from appsettings.json)0
-rw-r--r--Server/dotnet-tools.json (renamed from .config/dotnet-tools.json)2
-rw-r--r--Server/tessdata/eng.traineddata (renamed from tessdata/eng.traineddata)bin15400601 -> 15400601 bytes
-rw-r--r--Server/wwwroot/app.css60
-rw-r--r--Server/wwwroot/css/site.css (renamed from wwwroot/css/site.css)0
-rw-r--r--Server/wwwroot/favicon.ico (renamed from wwwroot/favicon.ico)bin3262 -> 3262 bytes
-rw-r--r--Server/wwwroot/icon-192.png (renamed from wwwroot/icon-192.png)bin31523 -> 31523 bytes
-rw-r--r--Server/wwwroot/icon-512.png (renamed from wwwroot/icon-512.png)bin136487 -> 136487 bytes
-rw-r--r--Server/wwwroot/images/book.svg (renamed from wwwroot/images/book.svg)0
-rw-r--r--Server/wwwroot/images/checkmark.svg (renamed from wwwroot/images/checkmark.svg)0
-rw-r--r--Server/wwwroot/images/cross.svg (renamed from wwwroot/images/cross.svg)0
-rw-r--r--Server/wwwroot/images/edit.svg (renamed from wwwroot/images/edit.svg)0
-rw-r--r--Server/wwwroot/images/info.svg (renamed from wwwroot/images/info.svg)0
-rw-r--r--Server/wwwroot/images/loginbg.webp (renamed from wwwroot/images/loginbg.webp)bin2247672 -> 2247672 bytes
-rw-r--r--Server/wwwroot/images/photo.svg (renamed from wwwroot/images/photo.svg)0
-rw-r--r--Server/wwwroot/images/tag.svg (renamed from wwwroot/images/tag.svg)0
-rw-r--r--Server/wwwroot/images/trash.svg (renamed from wwwroot/images/trash.svg)0
-rw-r--r--Server/wwwroot/js/dialog.js (renamed from wwwroot/js/dialog.js)0
-rw-r--r--Server/wwwroot/js/keyboard.js (renamed from wwwroot/js/keyboard.js)0
-rw-r--r--Server/wwwroot/js/mobile.js (renamed from wwwroot/js/mobile.js)0
-rw-r--r--Server/wwwroot/loginbg.webm (renamed from wwwroot/loginbg.webm)bin390877 -> 390877 bytes
-rw-r--r--Server/wwwroot/manifest.webmanifest (renamed from wwwroot/manifest.webmanifest)0
-rw-r--r--Server/wwwroot/styles/data-table.css (renamed from wwwroot/styles/data-table.css)0
-rw-r--r--Server/wwwroot/styles/global.css (renamed from wwwroot/styles/global.css)0
-rw-r--r--_Imports.razor9
-rw-r--r--appsettings.Development.json19
116 files changed, 1382 insertions, 686 deletions
diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index 913b3e5..0000000
--- a/.editorconfig
+++ /dev/null
@@ -1,7 +0,0 @@
-[*.cs]
-
-# CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
-dotnet_diagnostic.CS8618.severity = silent
-
-# ASP0000: Do not call 'IServiceCollection.BuildServiceProvider' in 'ConfigureServices'
-dotnet_diagnostic.ASP0000.severity = silent
diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index 1ff0c42..0000000
--- a/.gitattributes
+++ /dev/null
@@ -1,63 +0,0 @@
-###############################################################################
-# Set default behavior to automatically normalize line endings.
-###############################################################################
-* text=auto
-
-###############################################################################
-# Set default behavior for command prompt diff.
-#
-# This is need for earlier builds of msysgit that does not have it on by
-# default for csharp files.
-# Note: This is only used by command line
-###############################################################################
-#*.cs diff=csharp
-
-###############################################################################
-# Set the merge driver for project and solution files
-#
-# Merging from the command prompt will add diff markers to the files if there
-# are conflicts (Merging from VS is not affected by the settings below, in VS
-# the diff markers are never inserted). Diff markers may cause the following
-# file extensions to fail to load in VS. An alternative would be to treat
-# these files as binary and thus will always conflict and require user
-# intervention with every merge. To do so, just uncomment the entries below
-###############################################################################
-#*.sln merge=binary
-#*.csproj merge=binary
-#*.vbproj merge=binary
-#*.vcxproj merge=binary
-#*.vcproj merge=binary
-#*.dbproj merge=binary
-#*.fsproj merge=binary
-#*.lsproj merge=binary
-#*.wixproj merge=binary
-#*.modelproj merge=binary
-#*.sqlproj merge=binary
-#*.wwaproj merge=binary
-
-###############################################################################
-# behavior for image files
-#
-# image files are treated as binary by default.
-###############################################################################
-#*.jpg binary
-#*.png binary
-#*.gif binary
-
-###############################################################################
-# diff behavior for common document formats
-#
-# Convert binary document formats to text before diffing them. This feature
-# is only available from the command line. Turn it on by uncommenting the
-# entries below.
-###############################################################################
-#*.doc diff=astextplain
-#*.DOC diff=astextplain
-#*.docx diff=astextplain
-#*.DOCX diff=astextplain
-#*.dot diff=astextplain
-#*.DOT diff=astextplain
-#*.pdf diff=astextplain
-#*.PDF diff=astextplain
-#*.rtf diff=astextplain
-#*.RTF diff=astextplain
diff --git a/App.razor b/App.razor
deleted file mode 100644
index b4e47c9..0000000
--- a/App.razor
+++ /dev/null
@@ -1,19 +0,0 @@
-<CascadingAuthenticationState>
- <Router AppAssembly="@typeof(App).Assembly">
- <Found Context="routeData">
- <AuthorizeRouteView
- RouteData="@routeData"
- DefaultLayout="@typeof(MainLayout)">
- <NotAuthorized>
- <RedirectLogin/>
- </NotAuthorized>
- </AuthorizeRouteView>
- </Found>
- <NotFound>
- <PageTitle>Not found</PageTitle>
- <LayoutView Layout="@typeof(MainLayout)">
- <p role="alert">Sorry, there's nothing at this address.</p>
- </LayoutView>
- </NotFound>
- </Router>
-</CascadingAuthenticationState>
diff --git a/Controllers/LoginController.cs b/Controllers/LoginController.cs
deleted file mode 100644
index c93f0d5..0000000
--- a/Controllers/LoginController.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using HyperBooru.Services;
-using Microsoft.AspNetCore.Authentication;
-using Microsoft.AspNetCore.Authentication.Cookies;
-using Microsoft.AspNetCore.Mvc;
-using System.Security.Claims;
-
-namespace HyperBooru.Controllers;
-
-[ApiController]
-[Route("/")]
-public class LoginController : Controller {
- private IHttpContextAccessor httpContextAccessor;
-
- public LoginController(IHttpContextAccessor httpContextAccessor) =>
- this.httpContextAccessor = httpContextAccessor;
-
- [HttpPost("Login")]
- public async Task<IActionResult> Login(
- [FromForm] string username,
- [FromForm] string password,
- HBContext db) {
-
- var user = db.Users.FirstOrDefault(u => u.Username == username);
- if(user is null)
- return StatusCode(403);
-
- var hash = UserService.HashPassword(password);
- if(hash != user.PasswordHash)
- return StatusCode(403);
-
- var claims = new Claim[] {
- new Claim(ClaimTypes.Name, user.Username),
- new Claim("ObjectId", user.ObjectId.ToString())
- };
-
- var claimsIdentity = new ClaimsIdentity(
- claims,
- CookieAuthenticationDefaults.AuthenticationScheme);
-
- var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
-
- await httpContextAccessor.HttpContext!.SignInAsync(claimsPrincipal);
- return Ok();
- }
-
- [HttpPost("Logout")]
- public async Task Logout() =>
- await httpContextAccessor.HttpContext!.SignOutAsync();
-}
diff --git a/MainLayout.razor b/MainLayout.razor
deleted file mode 100644
index 8e9f6bd..0000000
--- a/MainLayout.razor
+++ /dev/null
@@ -1,11 +0,0 @@
-@inherits LayoutComponentBase
-
-<link href="@(nameof(HyperBooru)).styles.css" rel="stylesheet" />
-
-<Titlebar/>
-
-<MobileMenu/>
-
-<div id="content">
- @Body
-</div>
diff --git a/MainLayout.razor.css b/MainLayout.razor.css
deleted file mode 100644
index c2b5603..0000000
--- a/MainLayout.razor.css
+++ /dev/null
@@ -1,9 +0,0 @@
-div#content {
- flex: 1 1 calc(100vh - 59px);
- overflow-x: hidden;
- overflow-y: auto;
-}
-
-body.mobile-menu-visible div#content {
- display: none;
-}
diff --git a/Pages/Component/NsfwSwitch.razor b/Pages/Component/NsfwSwitch.razor
deleted file mode 100644
index b96606d..0000000
--- a/Pages/Component/NsfwSwitch.razor
+++ /dev/null
@@ -1,8 +0,0 @@
-@inject IUserService userService
-
-<Switch InitialValue=userService.UserSessionState.ShowNsfw OnToggle=ToggleNsfw/>
-
-@code {
- private void ToggleNsfw(bool showNsfw) =>
- userService.UserSessionState.ShowNsfw = showNsfw;
-} \ No newline at end of file
diff --git a/Pages/Component/Titlebar.razor b/Pages/Component/Titlebar.razor
deleted file mode 100644
index 48257b2..0000000
--- a/Pages/Component/Titlebar.razor
+++ /dev/null
@@ -1,87 +0,0 @@
-@inject IJSRuntime jsRuntime
-@inject IUserService userService
-
-<script suppress-error="BL9992">
- async function login() {
- var username = document.querySelector('input#username');
- var password = document.querySelector('input#password');
-
- var formData = new FormData();
- formData.append('username', username.value);
- formData.append('password', password.value);
-
- var resp = await fetch('/Login', {
- method: 'POST',
- body: formData
- });
-
- if(resp.ok) {
- window.location.href = '/';
- } else if(resp.status == 403) {
- var form = document.querySelector('form.login');
- form.classList.remove('bad-login');
- @* TODO: improve this hacky method of triggering reflow *@
- form.offsetWidth;
- form.classList.add('bad-login');
- username.value = password.value = null;
- username.focus();
- } else {
- alert('Unknown error while attempting to login!');
- }
- }
-
- async function logout() {
- var resp = await fetch('/Logout', { method: 'POST' });
- if(resp.ok) {
- window.location.href = '/Login';
- } else {
- alert('Error logging out!');
- }
- }
-</script>
-
-<AuthorizeView>
- <Authorized>
- <div id="navbar">
- <p class="mobile">HyperBooru</p>
- <a class="mobile menu-button" href="javascript:toggleMobileMenu();">&#x2630</a>
-
- <a class="desktop" href="/">Home</a>
- <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>
-
- <p class="desktop" id="nsfw-label">NSFW</p>
- <div id="nsfw-switch" class="desktop">
- <NsfwSwitch/>
- </div>
- <form action="/Gallery" method="get" class="desktop">
- <input type="text" name="q" placeholder="Search"/>
- </form>
- <a class="desktop" href="javascript:logout();">Logout</a>
- </div>
- <AboutDialog @ref=aboutDialog/>
- </Authorized>
- <NotAuthorized>
- <div id="navbar">
- <h2>Login</h2>
- <form class="login" action="javascript:login();">
- <input
- id="username"
- placeholder="Username"
- type="text"
- autocorrect="off"
- autocapitalize="off"
- autocomplete="off"
- autofocus/>
- <input id="password" placeholder="Password" type="password"/>
- </form>
- <a href="javascript:login();">Login</a>
- </div>
- </NotAuthorized>
-</AuthorizeView>
-
-@code {
- private AboutDialog aboutDialog;
-}
diff --git a/Pages/Login.razor b/Pages/Login.razor
deleted file mode 100644
index 723a78a..0000000
--- a/Pages/Login.razor
+++ /dev/null
@@ -1,17 +0,0 @@
-@page "/Login"
-@inject NavigationManager navigationManager
-
-<PageTitle>HyperBooru Login</PageTitle>
-
-<div/>
-
-@code {
- [CascadingParameter]
- public Task<AuthenticationState> AuthenticationState{ get; set; }
-
- protected override void OnInitialized() {
- var authState = AuthenticationState.GetAwaiter().GetResult();
- if(authState!.User.Identity?.IsAuthenticated ?? false)
- navigationManager.NavigateTo("/");
- }
-} \ No newline at end of file
diff --git a/Pages/_Host.cshtml b/Pages/_Host.cshtml
deleted file mode 100644
index 28ff24c..0000000
--- a/Pages/_Host.cshtml
+++ /dev/null
@@ -1,37 +0,0 @@
-@page "/"
-@using Microsoft.AspNetCore.Components.Web
-@namespace HyperBooru.Pages
-@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
-
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
- <base href="~/" />
- <link href="css/site.css" rel="stylesheet" />
- <link href="/styles/global.css" rel="stylesheet" />
- <link href="/favicon.ico" rel="icon" />
- <link href="/manifest.webmanifest" rel="manifest" />
- <script type="text/javascript" src="/js/dialog.js"></script>
- <script type="text/javascript" src="/js/keyboard.js"></script>
- <script type="text/javascript" src="/js/mobile.js"></script>
- <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
-</head>
-<body>
- <component type="typeof(App)" render-mode="ServerPrerendered" />
-
- <div id="blazor-error-ui">
- <environment include="Staging,Production">
- An error has occurred. This application may no longer respond until reloaded.
- </environment>
- <environment include="Development">
- An unhandled exception has occurred. See browser dev tools for details.
- </environment>
- <a href="" class="reload">Reload</a>
- <a class="dismiss">🗙</a>
- </div>
-
- <script src="_framework/blazor.server.js"></script>
-</body>
-</html>
diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json
deleted file mode 100644
index 9f4966c..0000000
--- a/Properties/launchSettings.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "profiles": {
- "WSL": {
- "commandName": "WSL2",
- "launchBrowser": true,
- "launchUrl": "https://localhost:7132",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "ASPNETCORE_URLS": "https://localhost:7132;http://localhost:5186"
- },
- "distributionName": ""
- },
- "HyperBooru": {
- "commandName": "Project",
- "launchBrowser": true,
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- },
- "dotnetRunMessages": true,
- "applicationUrl": "https://localhost:7132;http://localhost:5186"
- }
- },
- "iisSettings": {
- "windowsAuthentication": false,
- "anonymousAuthentication": true,
- "iisExpress": {
- "applicationUrl": "http://localhost:1922",
- "sslPort": 44354
- }
- }
-} \ No newline at end of file
diff --git a/Server.Client/Client.csproj b/Server.Client/Client.csproj
new file mode 100644
index 0000000..19c4f39
--- /dev/null
+++ b/Server.Client/Client.csproj
@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
+
+ <PropertyGroup>
+ <TargetFramework>net10.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ <NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
+ <StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
+ <BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
+ <AssemblyName>HyperBooru.Client</AssemblyName>
+ <RootNamespace>HyperBooru.Client</RootNamespace>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.8" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\ApiClient\ApiClient.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/IDialog.cs b/Server.Client/IDialog.cs
index 41e86a8..6fc4646 100644
--- a/IDialog.cs
+++ b/Server.Client/IDialog.cs
@@ -1,4 +1,4 @@
-namespace HyperBooru;
+namespace HyperBooru.Client;
public interface IDialog {
public bool Visible { get; set; }
diff --git a/LICENSE.txt b/Server.Client/LICENSE.txt
index 0ad25db..0ad25db 100644
--- a/LICENSE.txt
+++ b/Server.Client/LICENSE.txt
diff --git a/Server.Client/Layout/MainLayout.razor b/Server.Client/Layout/MainLayout.razor
new file mode 100644
index 0000000..dc8b923
--- /dev/null
+++ b/Server.Client/Layout/MainLayout.razor
@@ -0,0 +1,15 @@
+@inherits LayoutComponentBase
+
+<Titlebar/>
+
+<MobileMenu/>
+
+<div id="content">
+ @Body
+</div>
+
+<div id="blazor-error-ui" data-nosnippet>
+ An unhandled error has occurred.
+ <a href="." class="reload">Reload</a>
+ <span class="dismiss">🗙</span>
+</div>
diff --git a/Server.Client/Layout/MainLayout.razor.css b/Server.Client/Layout/MainLayout.razor.css
new file mode 100644
index 0000000..7e84358
--- /dev/null
+++ b/Server.Client/Layout/MainLayout.razor.css
@@ -0,0 +1,30 @@
+div#content {
+ flex: 1 1 calc(100vh - 59px);
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+body.mobile-menu-visible div#content {
+ display: none;
+}
+
+#blazor-error-ui {
+ color-scheme: light only;
+ background: lightyellow;
+ bottom: 0;
+ box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
+ box-sizing: border-box;
+ display: none;
+ left: 0;
+ padding: 0.6rem 1.25rem 0.7rem 1.25rem;
+ position: fixed;
+ width: 100%;
+ z-index: 1000;
+}
+
+#blazor-error-ui .dismiss {
+ cursor: pointer;
+ position: absolute;
+ right: 0.75rem;
+ top: 0.5rem;
+}
diff --git a/Pages/Component/AboutDialog.razor b/Server.Client/Pages/Component/AboutDialog.razor
index 1229dc7..0ef8473 100644
--- a/Pages/Component/AboutDialog.razor
+++ b/Server.Client/Pages/Component/AboutDialog.razor
@@ -1,8 +1,8 @@
@using System.Reflection
-@using Microsoft.AspNetCore.Hosting
+@* @using Microsoft.AspNetCore.Hosting
@inject IDbContextFactory<HBContext> dbFactory
-@inject IHostingEnvironment hostingEnvironment
-@implements IDialog
+@inject IHostingEnvironment hostingEnvironment*@
+ @implements IDialog
<Dialog @ref=dialog>
<p id="title">@Title</p>
@@ -699,15 +699,15 @@
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());
+ // 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());
}
}
}
diff --git a/Pages/Component/AboutDialog.razor.css b/Server.Client/Pages/Component/AboutDialog.razor.css
index cbc4b59..cbc4b59 100644
--- a/Pages/Component/AboutDialog.razor.css
+++ b/Server.Client/Pages/Component/AboutDialog.razor.css
diff --git a/Pages/Component/ButtonContainer.razor b/Server.Client/Pages/Component/ButtonContainer.razor
index a631dcc..a631dcc 100644
--- a/Pages/Component/ButtonContainer.razor
+++ b/Server.Client/Pages/Component/ButtonContainer.razor
diff --git a/Pages/Component/ButtonContainer.razor.css b/Server.Client/Pages/Component/ButtonContainer.razor.css
index c2d5d25..c2d5d25 100644
--- a/Pages/Component/ButtonContainer.razor.css
+++ b/Server.Client/Pages/Component/ButtonContainer.razor.css
diff --git a/Pages/Component/Dialog.razor b/Server.Client/Pages/Component/Dialog.razor
index 673ec2f..673ec2f 100644
--- a/Pages/Component/Dialog.razor
+++ b/Server.Client/Pages/Component/Dialog.razor
diff --git a/Pages/Component/Dialog.razor.css b/Server.Client/Pages/Component/Dialog.razor.css
index 93680c5..93680c5 100644
--- a/Pages/Component/Dialog.razor.css
+++ b/Server.Client/Pages/Component/Dialog.razor.css
diff --git a/Pages/Component/MediaTagTable.razor b/Server.Client/Pages/Component/MediaTagTable.razor
index e687529..0524739 100644
--- a/Pages/Component/MediaTagTable.razor
+++ b/Server.Client/Pages/Component/MediaTagTable.razor
@@ -1,7 +1,4 @@
-@inject IDbContextFactory<HBContext> dbFactory
-@inject ITagService tagService
-
-<link rel="stylesheet" href="@(nameof(HyperBooru)).styles.css"/>
+@inject HBSession hb
<table class="data-table">
<tr>
@@ -19,7 +16,7 @@
}
</td>
<td>
- <a href="/Gallery?t=@(e.tagDef.Guid)" class="nondecorated">
+ <a href="/Gallery?t=@(e.tagDef.TagDefinitionId)" class="nondecorated">
@if(e.isImplicit) {
<i>@e.tagDef.Name</i>
} else {
@@ -40,33 +37,39 @@
@code {
[Parameter]
- public Media Media { get; set; }
+ public Guid MediaId { get; set; }
- private (TagDefinition tagDef, bool isImplicit)[] tagDefs;
+ private (TagDefinition tagDef, bool isImplicit)[] tagDefs = [];
protected override void OnInitialized() => LoadTagDefs();
public void Refresh() {
LoadTagDefs();
- StateHasChanged();
+ // StateHasChanged();
}
private void Delete(TagDefinition tagDef) {
- tagService.RemoveTag(Media.Guid, tagDef.Guid);
- Refresh();
+ // tagService.RemoveTag(Media.Guid, tagDef.Guid);
+ // Refresh();
}
- private void LoadTagDefs() {
- using var db = dbFactory.CreateDbContext();
- var media = db.Media.First(m => m.ObjectId == Media.ObjectId);
+ private async void LoadTagDefs() {
+ var tags = await hb.Media.GetTagsAsync(MediaId);
+
+ tagDefs = tags.Select(td => (tagDef: td, isImplicit: false)).ToArray();
+
+ await InvokeAsync(() => StateHasChanged());
+
+ // using var db = dbFactory.CreateDbContext();
+ // var media = db.Media.First(m => m.ObjectId == Media.ObjectId);
- tagDefs = tagService.GetAllTags(Media)
- .Where(e => e.tagDefinition.Source == TagSource.UserTag)
- .ToArray();
+ // tagDefs = tagService.GetAllTags(Media)
+ // .Where(e => e.tagDefinition.Source == TagSource.UserTag)
+ // .ToArray();
}
private void MakeExplicit(TagDefinition tagDef) {
- tagService.AddTag(Media, tagDef);
- Refresh();
+ // tagService.AddTag(Media, tagDef);
+ // Refresh();
}
}
diff --git a/Pages/Component/MediaTagTable.razor.css b/Server.Client/Pages/Component/MediaTagTable.razor.css
index 4dedb3f..4dedb3f 100644
--- a/Pages/Component/MediaTagTable.razor.css
+++ b/Server.Client/Pages/Component/MediaTagTable.razor.css
diff --git a/Pages/Component/MobileMenu.razor b/Server.Client/Pages/Component/MobileMenu.razor
index 49c45d5..49c45d5 100644
--- a/Pages/Component/MobileMenu.razor
+++ b/Server.Client/Pages/Component/MobileMenu.razor
diff --git a/Pages/Component/MobileMenu.razor.css b/Server.Client/Pages/Component/MobileMenu.razor.css
index 0237cf1..0237cf1 100644
--- a/Pages/Component/MobileMenu.razor.css
+++ b/Server.Client/Pages/Component/MobileMenu.razor.css
diff --git a/Server.Client/Pages/Component/NsfwSwitch.razor b/Server.Client/Pages/Component/NsfwSwitch.razor
new file mode 100644
index 0000000..f4f9c1f
--- /dev/null
+++ b/Server.Client/Pages/Component/NsfwSwitch.razor
@@ -0,0 +1,6 @@
+<Switch InitialValue=false OnToggle=ToggleNsfw/>
+
+@code {
+ private void ToggleNsfw(bool showNsfw) => _ = 0;
+ // userService.UserSessionState.ShowNsfw = showNsfw;
+} \ No newline at end of file
diff --git a/Pages/Component/ProgressBar.razor b/Server.Client/Pages/Component/ProgressBar.razor
index aa22194..aa22194 100644
--- a/Pages/Component/ProgressBar.razor
+++ b/Server.Client/Pages/Component/ProgressBar.razor
diff --git a/Pages/Component/ProgressBar.razor.css b/Server.Client/Pages/Component/ProgressBar.razor.css
index f49c982..f49c982 100644
--- a/Pages/Component/ProgressBar.razor.css
+++ b/Server.Client/Pages/Component/ProgressBar.razor.css
diff --git a/Pages/Component/RedirectLogin.razor b/Server.Client/Pages/Component/RedirectLogin.razor
index 290a7ac..290a7ac 100644
--- a/Pages/Component/RedirectLogin.razor
+++ b/Server.Client/Pages/Component/RedirectLogin.razor
diff --git a/Pages/Component/Switch.razor b/Server.Client/Pages/Component/Switch.razor
index d11ac81..5264094 100644
--- a/Pages/Component/Switch.razor
+++ b/Server.Client/Pages/Component/Switch.razor
@@ -1,6 +1,4 @@
-<link rel="stylesheet" href="@(nameof(HyperBooru)).styles.css"/>
-
-<label>
+<label>
<input
type="checkbox"
checked=@InitialValue
diff --git a/Pages/Component/Switch.razor.css b/Server.Client/Pages/Component/Switch.razor.css
index 6b1f5d5..6b1f5d5 100644
--- a/Pages/Component/Switch.razor.css
+++ b/Server.Client/Pages/Component/Switch.razor.css
diff --git a/Pages/Component/TabContainer.razor b/Server.Client/Pages/Component/TabContainer.razor
index 3caab0b..3caab0b 100644
--- a/Pages/Component/TabContainer.razor
+++ b/Server.Client/Pages/Component/TabContainer.razor
diff --git a/Pages/Component/TabContainer.razor.css b/Server.Client/Pages/Component/TabContainer.razor.css
index bfb5694..bfb5694 100644
--- a/Pages/Component/TabContainer.razor.css
+++ b/Server.Client/Pages/Component/TabContainer.razor.css
diff --git a/Pages/Component/TabPane.razor b/Server.Client/Pages/Component/TabPane.razor
index ba4a13a..ba4a13a 100644
--- a/Pages/Component/TabPane.razor
+++ b/Server.Client/Pages/Component/TabPane.razor
diff --git a/Pages/Component/TabPane.razor.css b/Server.Client/Pages/Component/TabPane.razor.css
index 5f28270..5f28270 100644
--- a/Pages/Component/TabPane.razor.css
+++ b/Server.Client/Pages/Component/TabPane.razor.css
diff --git a/Pages/Component/TagEditDialog.razor b/Server.Client/Pages/Component/TagEditDialog.razor
index afa312e..6192d55 100644
--- a/Pages/Component/TagEditDialog.razor
+++ b/Server.Client/Pages/Component/TagEditDialog.razor
@@ -1,5 +1,5 @@
-@inject IDbContextFactory<HBContext> dbFactory;
-@inject ITagService tagService
+@* @inject IDbContextFactory<HBContext> dbFactory;
+@inject ITagService tagService*@
@implements IDialog
<Dialog Title=@Title @ref=dialog>
@@ -80,9 +80,9 @@
private void Submit() {
try {
if(TagDefinition is null) {
- tagService.CreateTagDefinition(tagName, tagNamespace, tagAlias);
+ // tagService.CreateTagDefinition(tagName, tagNamespace, tagAlias);
} else {
- tagService.UpdateTagDefinition(TagDefinition, tagName, tagNamespace, tagAlias);
+ // tagService.UpdateTagDefinition(TagDefinition, tagName, tagNamespace, tagAlias);
}
} catch(ApiModels.TagDuplicateException e) {
nameExists = e.NameExists;
diff --git a/Pages/Component/TagEditDialog.razor.css b/Server.Client/Pages/Component/TagEditDialog.razor.css
index 02781c0..02781c0 100644
--- a/Pages/Component/TagEditDialog.razor.css
+++ b/Server.Client/Pages/Component/TagEditDialog.razor.css
diff --git a/Pages/Component/TagSelectDialog.razor b/Server.Client/Pages/Component/TagSelectDialog.razor
index 87065d7..c2fddc2 100644
--- a/Pages/Component/TagSelectDialog.razor
+++ b/Server.Client/Pages/Component/TagSelectDialog.razor
@@ -1,6 +1,6 @@
-@inject IDbContextFactory<HBContext> dbFactory
+@* @inject IDbContextFactory<HBContext> dbFactory
@inject ITagService tagService
-@inject IUserService userService
+@inject IUserService userService*@
@implements IDisposable
@implements IDialog
@@ -28,7 +28,7 @@
ns,
alias is not null ? $"({alias})" : null
});
- <input
+@* <input
type="checkbox"
id="tagDef-@tagDefinitions[i].tagDefinition.Guid"
@bind=tagDefinitions[local].selected />
@@ -37,7 +37,7 @@
title=@title>
@tagDefinitions[i].tagDefinition.Name
</label>
- }
+ *@ }
</div>
<ButtonContainer>
<button @onclick=@(() => dialog.Hide()) class="secondary">Cancel</button>
@@ -67,7 +67,7 @@
private (TagDefinition tagDefinition, bool selected)[] tagDefinitions;
- private HBContext db;
+ // private HBContext db;
private Dialog dialog;
@@ -80,33 +80,33 @@
public void Hide() => Visible = false;
protected override void OnInitialized() {
- userService.UserSessionState.OnStateChange += ShowNsfwChanged;
+ // userService.UserSessionState.OnStateChange += ShowNsfwChanged;
LoadTags();
}
private void LoadTags() {
- db = dbFactory.CreateDbContext();
+ // db = dbFactory.CreateDbContext();
- var selected = SelectedTags.Select(td => td.Guid);
+ // var selected = SelectedTags.Select(td => td.Guid);
- int[] nsfwTags = Array.Empty<int>();
- if(!userService.UserSessionState.ShowNsfw)
- nsfwTags = tagService.TagsThatImply(HBContext.NsfwTag)
- .Select(td => td.ObjectId)
- .ToArray();
+ // int[] nsfwTags = Array.Empty<int>();
+ // 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();
+ // 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();
}
private void QueryInput(ChangeEventArgs e) {
@@ -141,30 +141,31 @@
}
private bool MatchesQuery(TagDefinition tagDef) {
- TagDefinition? singleTag = null;
+ // TagDefinition? singleTag = null;
- if(string.IsNullOrEmpty(query))
- return true;
+ // if(string.IsNullOrEmpty(query))
+ // return true;
- singleTag = tagDefinitions.FirstOrDefault(
- e => string.Equals(
- e.tagDefinition.Alias,
- query,
- StringComparison.OrdinalIgnoreCase)).tagDefinition;
+ // singleTag = tagDefinitions.FirstOrDefault(
+ // e => string.Equals(
+ // e.tagDefinition.Alias,
+ // query,
+ // StringComparison.OrdinalIgnoreCase)).tagDefinition;
- if(singleTag is not null)
- return tagDef.Guid == singleTag.Guid;
+ // if(singleTag is not null)
+ // return tagDef.Guid == singleTag.Guid;
- singleTag = tagDefinitions.FirstOrDefault(
- e => string.Equals(
- e.tagDefinition.Name,
- query,
- StringComparison.OrdinalIgnoreCase)).tagDefinition;
+ // singleTag = tagDefinitions.FirstOrDefault(
+ // e => string.Equals(
+ // e.tagDefinition.Name,
+ // query,
+ // StringComparison.OrdinalIgnoreCase)).tagDefinition;
- if(singleTag is not null)
- return tagDef.Guid == singleTag.Guid;
+ // if(singleTag is not null)
+ // return tagDef.Guid == singleTag.Guid;
- return tagDef.Name.ToLower().Contains(query.ToLower());
+ // return tagDef.Name.ToLower().Contains(query.ToLower());
+ return false;
}
private async void Submit() {
@@ -179,14 +180,14 @@
StateHasChanged();
}
- public async void ShowNsfwChanged(UserSessionState userSessionState) =>
- await InvokeAsync(() => {
- LoadTags();
- StateHasChanged();
- });
+ // public async void ShowNsfwChanged(UserSessionState userSessionState) =>
+ // await InvokeAsync(() => {
+ // LoadTags();
+ // StateHasChanged();
+ // });
public void Dispose() {
- db.Dispose();
- userService.UserSessionState.OnStateChange -= ShowNsfwChanged;
+ // db.Dispose();
+ // userService.UserSessionState.OnStateChange -= ShowNsfwChanged;
}
}
diff --git a/Pages/Component/TagSelectDialog.razor.css b/Server.Client/Pages/Component/TagSelectDialog.razor.css
index dadd0c4..dadd0c4 100644
--- a/Pages/Component/TagSelectDialog.razor.css
+++ b/Server.Client/Pages/Component/TagSelectDialog.razor.css
diff --git a/Server.Client/Pages/Component/Titlebar.razor b/Server.Client/Pages/Component/Titlebar.razor
new file mode 100644
index 0000000..521fb46
--- /dev/null
+++ b/Server.Client/Pages/Component/Titlebar.razor
@@ -0,0 +1,98 @@
+@inject IJSRuntime jsRuntime
+@inject NavigationManager nav
+@inject HBSession session;
+@* @inject IUserService userService *@
+
+<script suppress-error="BL9992">
+ async function login() {
+ var username = document.querySelector('input#username');
+ var password = document.querySelector('input#password');
+
+ var formData = new FormData();
+ formData.append('username', username.value);
+ formData.append('password', password.value);
+
+ var resp = await fetch('/Login', {
+ method: 'POST',
+ body: formData
+ });
+
+ if(resp.ok) {
+ window.location.href = '/';
+ } else if(resp.status == 403) {
+ var form = document.querySelector('form.login');
+ form.classList.remove('bad-login');
+ @* TODO: improve this hacky method of triggering reflow *@
+ form.offsetWidth;
+ form.classList.add('bad-login');
+ username.value = password.value = null;
+ username.focus();
+ } else {
+ alert('Unknown error while attempting to login!');
+ }
+ }
+
+ async function logout() {
+ var resp = await fetch('/Logout', { method: 'POST' });
+ if(resp.ok) {
+ window.location.href = '/Login';
+ } else {
+ alert('Error logging out!');
+ }
+ }
+</script>
+
+@if(!IsLoginPage) {
+ <div id="navbar">
+ <p class="mobile">HyperBooru</p>
+ <a class="mobile menu-button" href="javascript:toggleMobileMenu();">&#x2630</a>
+
+ <a class="desktop" href="/">Home</a>
+ <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>
+
+ <p class="desktop" id="nsfw-label">NSFW</p>
+ <div id="nsfw-switch" class="desktop">
+ <NsfwSwitch/>
+ </div>
+ <form action="/Gallery" method="get" class="desktop">
+ <input type="text" name="q" placeholder="Search"/>
+ </form>
+ <a class="desktop" href="javascript:logout();">Logout</a>
+ </div>
+ @* <AboutDialog @ref=aboutDialog/> *@
+} else {
+ <div id="navbar">
+ <h2>Login</h2>
+ <form class="login" action="javascript:login();">
+ <input
+ id="username"
+ placeholder="Username"
+ type="text"
+ autocorrect="off"
+ autocapitalize="off"
+ autocomplete="off"
+ autofocus
+ @bind=Username/>
+ <input id="password" placeholder="Password" type="password" @bind=Password/>
+ </form>
+ <a @onclick=Login>Login</a>
+ </div>
+}
+
+@code {
+ // private AboutDialog aboutDialog;
+
+ public string Username { get; set; } = "";
+ public string Password { get; set; } = "";
+
+ private bool IsLoginPage =>
+ new Uri(nav.Uri).AbsolutePath.Equals("/Login", StringComparison.OrdinalIgnoreCase);
+
+ private async void Login() {
+ await session.LoginAsync(Username, Password);
+ }
+}
diff --git a/Pages/Component/Titlebar.razor.css b/Server.Client/Pages/Component/Titlebar.razor.css
index 58a1c0c..58a1c0c 100644
--- a/Pages/Component/Titlebar.razor.css
+++ b/Server.Client/Pages/Component/Titlebar.razor.css
diff --git a/Pages/Gallery.razor b/Server.Client/Pages/Gallery.razor
index 743485e..b3391d1 100644
--- a/Pages/Gallery.razor
+++ b/Server.Client/Pages/Gallery.razor
@@ -1,15 +1,13 @@
@page "/"
@page "/Gallery"
-@inject ITagService tagService
-@inject IFeedService feedService
-@inject IUserService userService
@inject IJSRuntime jsRuntime
-@implements IDisposable
-@attribute [Authorize]
+@inject HBSession hb
+@* @implements IDisposable
+@attribute [Authorize]*@
<PageTitle>@Title</PageTitle>
-@if(Ingest && !userService.UserSessionState.ShowNsfw) {
+@if(Ingest && !hb.HasNsfwClaim) {
<div id="feed-error">
<p><center>Ingest feed is not available unless NSFW mode is enabled!</center></p>
<p><center><i>You must enable NSFW mode to continue...</i></center></p>
@@ -21,12 +19,12 @@
</div>
} else {
<div style="padding:var(--size-default-gap);">
- @foreach(var media in displayMedia) {
+ @foreach(var mediaId in displayMedia) {
// Precalculate thumbnail size to help the browser
// lay out the images during initial page load
- int width = (int) media.CurrentUploadedFile!.Width! * 200 / (int) media.CurrentUploadedFile.Height!;
- <a href="/ViewMedia?m=@(media.Guid)">
- <img src="/media/thumb/@(media.Guid)?h=200" width=@width height="200"/>
+ @* int width = (int) media.CurrentUploadedFile!.Width! * 200 / (int) media.CurrentUploadedFile.Height!; *@
+ <a href="/ViewMedia?m=@mediaId">
+ <img src="/media/thumb/@mediaId?h=200" height="200"/>
</a>
}
<div id="canary" style="height:1px;"></div>
@@ -77,10 +75,13 @@
}
}
- private List<Media> displayMedia;
+ private List<Guid> displayMedia = new();
+
+ // protected override void OnInitialized() =>
+ // userService.UserSessionState.OnStateChange += ShowNsfwChanged;
protected override void OnInitialized() =>
- userService.UserSessionState.OnStateChange += ShowNsfwChanged;
+ Console.WriteLine("PENUS");
protected override void OnParametersSet() => LoadMedia(true);
@@ -93,7 +94,8 @@
[JSInvokable("LoadMedia")]
public void LoadMedia(bool initial = false) {
- Media? key = displayMedia?.Any() ?? false && !initial ? displayMedia.Last() : null;
+ Guid? key =
+ displayMedia?.Any() ?? false && !initial ? displayMedia.Last() : null;
if(initial)
displayMedia = new();
@@ -102,40 +104,48 @@
if(Enum.TryParse<ApiModels.SortOrder>(SortOrder, true, out var so))
sortOrder = so;
+ FeedRequest feedRequest = new FeedRequest() {
+ SelectIngest = Ingest,
+ IncludeNsfw = hb.HasNsfwClaim,
+ ContinuationToken = key,
+ Count = PageSize,
+ SortOrder = sortOrder ?? default
+ };
+
if(TagId is not null && Query is null) {
- displayMedia!.AddRange(feedService.LoadChunk(
- selectIngest: Ingest,
- includeNsfw: userService.UserSessionState.ShowNsfw,
- tagId: (Guid) TagId!,
- key: key,
- count: PageSize,
- sortOrder: sortOrder ?? default));
+ feedRequest = new FeedTagRequest() {
+ SelectIngest = Ingest,
+ IncludeNsfw = hb.HasNsfwClaim,
+ TagId = (Guid) TagId!,
+ ContinuationToken = key,
+ Count = PageSize,
+ SortOrder = sortOrder ?? default
+ };
} else if(Query is not null && TagId is null) {
- displayMedia!.AddRange(feedService.LoadChunk(
- selectIngest: Ingest,
- includeNsfw: userService.UserSessionState.ShowNsfw,
- query: string.IsNullOrWhiteSpace(Query) ? null : Query,
- key: key,
- count: PageSize,
- sortOrder: sortOrder ?? default));
- } else {
- displayMedia!.AddRange(feedService.LoadChunk(
- selectIngest: Ingest,
- includeNsfw: userService.UserSessionState.ShowNsfw,
- key: key,
- count: PageSize,
- sortOrder: sortOrder ?? default));
+ feedRequest = new FeedSearchRequest() {
+ SelectIngest = Ingest,
+ IncludeNsfw = hb.HasNsfwClaim,
+ Query = string.IsNullOrWhiteSpace(Query) ? null : Query,
+ ContinuationToken = key,
+ Count = PageSize,
+ SortOrder = sortOrder ?? default
+ };
}
- StateHasChanged();
+ hb.Feed
+ .LoadChunkAsync(feedRequest)
+ .ContinueWith(async m => {
+ displayMedia!.AddRange(await m);
+ await InvokeAsync(() => StateHasChanged());
+ });
}
- private async void ShowNsfwChanged(UserSessionState userSessionState) {
- await InvokeAsync(() => {
- LoadMedia(true);
- });
- }
+ // private async void ShowNsfwChanged(UserSessionState userSessionState) {
+ // await InvokeAsync(() => {
+ // LoadMedia(true);
+ // });
+ // }
- public void Dispose() =>
- userService.UserSessionState.OnStateChange -= ShowNsfwChanged;
+ // public void Dispose() =>
+ // userService.UserSessionState.OnStateChange -= ShowNsfwChanged;
}
diff --git a/Pages/Gallery.razor.css b/Server.Client/Pages/Gallery.razor.css
index 6226d9b..6226d9b 100644
--- a/Pages/Gallery.razor.css
+++ b/Server.Client/Pages/Gallery.razor.css
diff --git a/Server.Client/Pages/Login.razor b/Server.Client/Pages/Login.razor
new file mode 100644
index 0000000..bdc6069
--- /dev/null
+++ b/Server.Client/Pages/Login.razor
@@ -0,0 +1,17 @@
+@page "/Login"
+@* @inject NavigationManager navigationManager *@
+
+<PageTitle>HyperBooru Login</PageTitle>
+
+<div/>
+
+@code {
+ // [CascadingParameter]
+ // public Task<AuthenticationState> AuthenticationState{ get; set; }
+
+ protected override void OnInitialized() {
+ // var authState = AuthenticationState.GetAwaiter().GetResult();
+ // if(authState!.User.Identity?.IsAuthenticated ?? false)
+ // navigationManager.NavigateTo("/");
+ }
+} \ No newline at end of file
diff --git a/Pages/Login.razor.css b/Server.Client/Pages/Login.razor.css
index fc8c8ca..fc8c8ca 100644
--- a/Pages/Login.razor.css
+++ b/Server.Client/Pages/Login.razor.css
diff --git a/Server.Client/Pages/NotFound.razor b/Server.Client/Pages/NotFound.razor
new file mode 100644
index 0000000..917ada1
--- /dev/null
+++ b/Server.Client/Pages/NotFound.razor
@@ -0,0 +1,5 @@
+@page "/not-found"
+@layout MainLayout
+
+<h3>Not Found</h3>
+<p>Sorry, the content you are looking for does not exist.</p> \ No newline at end of file
diff --git a/Pages/TagDefinitions.razor b/Server.Client/Pages/TagDefinitions.razor
index f3dca0f..032386e 100644
--- a/Pages/TagDefinitions.razor
+++ b/Server.Client/Pages/TagDefinitions.razor
@@ -1,9 +1,8 @@
@page "/TagDefinitions"
-@inject IDbContextFactory<HBContext> dbFactory
+@* @inject IDbContextFactory<HBContext> dbFactory
@inject ITagService tagService
@inject IUserService userService
-@implements IDisposable
-@attribute [Authorize]
+@implements IDisposable*@
<PageTitle>Tag Definitions</PageTitle>
@@ -23,25 +22,26 @@
<th></th>
</tr>
@foreach(var tagDef in tagDefinitions.Where(td => td.Namespace == ns)) {
- <tr data-guid="@tagDef.Guid">
+ @* <tr data-guid="@tagDef.Guid"> *@
+ <tr>
<td>@tagDef.Alias</td>
<td>
- <a href="/Gallery?t=@tagDef.Guid" class="nondecorated">
+@* <a href="/Gallery?t=@tagDef.Guid" class="nondecorated">
@tagDef.Name
</a>
- </td>
+ *@ </td>
<td>
<i>
@{
- var implicitTags = tagDef.ImplicitTags
- .Where(td => td.Source == TagSource.UserTag);
- foreach(var tag in implicitTags) {
- <a href="/Gallery?q=@tag.Name" class="nondecorated">
- @tag.Name
- </a>
- if(tag != implicitTags.Last())
- @(", ")
- }
+ // var implicitTags = tagDef.ImplicitTags
+ // .Where(td => td.Source == TagSource.UserTag);
+ // foreach(var tag in implicitTags) {
+ // <a href="/Gallery?q=@tag.Name" class="nondecorated">
+ // @tag.Name
+ // </a>
+ // if(tag != implicitTags.Last())
+ // @(", ")
+ // }
}
</i>
</td>
@@ -53,12 +53,12 @@
<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>
}
- </td>
+ *@ </td>
</tr>
}
</table>
@@ -93,35 +93,35 @@
private string?[] tagNamespaces;
- protected override void OnInitialized() =>
- userService.UserSessionState.OnStateChange += ShowNsfwChanged;
+ // protected override void OnInitialized() =>
+ // userService.UserSessionState.OnStateChange += ShowNsfwChanged;
protected override void OnParametersSet() =>
LoadTags();
private void LoadTags() {
- int[] nsfwTags = Array.Empty<int>();
- if(!userService.UserSessionState.ShowNsfw)
- nsfwTags = tagService.TagsThatImply(HBContext.NsfwTag)
- .Select(td => td.ObjectId)
- .ToArray();
+ // int[] nsfwTags = Array.Empty<int>();
+ // if(!userService.UserSessionState.ShowNsfw)
+ // nsfwTags = tagService.TagsThatImply(HBContext.NsfwTag)
+ // .Select(td => td.ObjectId)
+ // .ToArray();
- tagDefinitions = dbFactory.CreateDbContext().TagDefinitions
- .Include(td => td.ImplicitTags)
- .Where(td => td.Source == TagSource.UserTag)
- .OrderBy(td => td.Namespace)
- .ThenBy(td => td.Name)
- .AsEnumerable()
- .Where(td => userService.UserSessionState.ShowNsfw || !td.ImplicitTags
- .IntersectBy(nsfwTags, td => td.ObjectId)
- .Any())
- .ToArray();
+ // tagDefinitions = dbFactory.CreateDbContext().TagDefinitions
+ // .Include(td => td.ImplicitTags)
+ // .Where(td => td.Source == TagSource.UserTag)
+ // .OrderBy(td => td.Namespace)
+ // .ThenBy(td => td.Name)
+ // .AsEnumerable()
+ // .Where(td => userService.UserSessionState.ShowNsfw || !td.ImplicitTags
+ // .IntersectBy(nsfwTags, td => td.ObjectId)
+ // .Any())
+ // .ToArray();
- tagNamespaces = tagDefinitions
- .Select(td => td.Namespace)
- .Order()
- .Distinct()
- .ToArray();
+ // tagNamespaces = tagDefinitions
+ // .Select(td => td.Namespace)
+ // .Order()
+ // .Distinct()
+ // .ToArray();
}
private void PromptToDelete(TagDefinition toDelete) {
@@ -130,13 +130,13 @@
}
private void DeleteTagDefinition() {
- if(toDelete is null)
- return;
+ // if(toDelete is null)
+ // return;
- tagService.DeleteTagDefinition(toDelete);
- deleteTagDialog.Hide();
- LoadTags();
- StateHasChanged();
+ // tagService.DeleteTagDefinition(toDelete);
+ // deleteTagDialog.Hide();
+ // LoadTags();
+ // StateHasChanged();
}
private void PromptTagCreate() {
@@ -152,36 +152,36 @@
}
private void PromptImplicitTags(TagDefinition toEditImplicit) {
- this.toEditImplicit = toEditImplicit;
- implicitTagDialog.SelectedTags =
- toEditImplicit.ImplicitTags.ToArray();
- implicitTagDialog.Show();
+ // this.toEditImplicit = toEditImplicit;
+ // implicitTagDialog.SelectedTags =
+ // toEditImplicit.ImplicitTags.ToArray();
+ // implicitTagDialog.Show();
}
private void SetImplicitTags(TagDefinition[] tagDefs) {
if(toEditImplicit is null)
return;
- tagService.SetImplicitTags(toEditImplicit, tagDefs);
+ // tagService.SetImplicitTags(toEditImplicit, tagDefs);
LoadTags();
StateHasChanged();
}
private void SetNsfw(TagDefinition tagDef, bool nsfw) {
- if(nsfw)
- tagService.AddImplicitTag(tagDef.Guid, HBContext.NsfwTag);
- else
- tagService.RemoveImplicitTag(tagDef.Guid, HBContext.NsfwTag);
- LoadTags();
- StateHasChanged();
+ // if(nsfw)
+ // tagService.AddImplicitTag(tagDef.Guid, HBContext.NsfwTag);
+ // else
+ // tagService.RemoveImplicitTag(tagDef.Guid, HBContext.NsfwTag);
+ // LoadTags();
+ // StateHasChanged();
}
- private async void ShowNsfwChanged(UserSessionState userSessionState) =>
- await InvokeAsync(() => {
- LoadTags();
- StateHasChanged();
- });
+ // private async void ShowNsfwChanged(UserSessionState userSessionState) =>
+ // await InvokeAsync(() => {
+ // LoadTags();
+ // StateHasChanged();
+ // });
- public void Dispose() =>
- userService.UserSessionState.OnStateChange -= ShowNsfwChanged;
+ // public void Dispose() =>
+ // userService.UserSessionState.OnStateChange -= ShowNsfwChanged;
}
diff --git a/Pages/TagDefinitions.razor.css b/Server.Client/Pages/TagDefinitions.razor.css
index 409eacc..409eacc 100644
--- a/Pages/TagDefinitions.razor.css
+++ b/Server.Client/Pages/TagDefinitions.razor.css
diff --git a/Pages/Upload.razor b/Server.Client/Pages/Upload.razor
index 6d6e8bc..74cfd64 100644
--- a/Pages/Upload.razor
+++ b/Server.Client/Pages/Upload.razor
@@ -1,5 +1,4 @@
@page "/Upload"
-@attribute [Authorize]
<div id="dropzone">
<p></p>
diff --git a/Pages/Upload.razor.css b/Server.Client/Pages/Upload.razor.css
index d510bc6..d510bc6 100644
--- a/Pages/Upload.razor.css
+++ b/Server.Client/Pages/Upload.razor.css
diff --git a/Pages/ViewMedia.razor b/Server.Client/Pages/ViewMedia.razor
index 46cbc45..4c9151e 100644
--- a/Pages/ViewMedia.razor
+++ b/Server.Client/Pages/ViewMedia.razor
@@ -1,11 +1,12 @@
@page "/ViewMedia"
-@using HyperBooru.Util
+@* @using HyperBooru.Util*@
+@inject HBSession hb
@inject IJSRuntime jsRuntime
-@inject IDbContextFactory<HBContext> dbFactory
+@* @inject IDbContextFactory<HBContext> dbFactory
@inject ITagService tagService
@inject IMediaService mediaService
@inject ISourceService sourceService
-@attribute [Authorize]
+ *@
<PageTitle>@title</PageTitle>
@@ -34,10 +35,12 @@
<div id="hcontainer">
<div id="image-container" class="mobile-pane-image visible">
<img
+ src="/media/@(MediaId)"/>
+@* <img
src="/media/@(media.Guid)"
width=@media.CurrentUploadedFile.Width
height=@media.CurrentUploadedFile.Height/>
- </div>
+ *@ </div>
<div id="metadata-show-button">
<a href="javascript:toggleSidebar();" title="Toggle sidebar (S)"></a>
</div>
@@ -45,7 +48,7 @@
<div id="metadata-container">
<div id="metadata-fileinfo">
@if(infoEditMode) {
- <form action="javascript:;" @onsubmit=@(() => ApplyInfoEdit(true))>
+@* <form action="javascript:;" @onsubmit=@(() => ApplyInfoEdit(true))>
<table id="edit-metadata">
<tr>
<td>Title:</td>
@@ -57,11 +60,11 @@
</tr>
</table>
</form>
- } else {
- <p>Title: <i>@(media.ShortDescription ?? "None")</i></p>
- <p class="newlines">Description:<br/><i>@(media.LongDescription ?? "None")</i></p>
+ *@ } else {
+ <p>Title: <i>@(media?.ShortDescription ?? "None")</i></p>
+ <p class="newlines">Description:<br/><i>@(media?.LongDescription ?? "None")</i></p>
}
- <p>Resolution: @(media.CurrentUploadedFile.Width)x@(media.CurrentUploadedFile.Height)</p>
+@* <p>Resolution: @(media.CurrentUploadedFile.Width)x@(media.CurrentUploadedFile.Height)</p>
<p class="heading">Upload history</p>
<hr/>
<table id="uploaded-files" class="data-table">
@@ -102,16 +105,16 @@
</tr>
}
</table>
- </div>
+ *@ </div>
<div id="metadata-tags">
<p class="heading">Tags</p>
<hr/>
- <MediaTagTable Media=media @ref=mediaTagTable/>
+ <MediaTagTable MediaId=MediaId @ref=mediaTagTable/>
</div>
</div>
<div id="button-container">
<ButtonContainer>
- <button @onclick=@(() => deleteDialog.Show()) class="warning" data-keyboard-shortcut="d">
+@* <button @onclick=@(() => deleteDialog.Show()) class="warning" data-keyboard-shortcut="d">
<img src="/images/trash.svg"/>
<p><u>D</u>elete</p>
</button>
@@ -123,8 +126,8 @@
<img src="/images/book.svg"/>
<p>View <u>O</u>CR</p>
</button>
- @if(infoEditMode) {
- <button @onclick=@(() => ApplyInfoEdit(false)) class="secondary">
+ *@ @if(infoEditMode) {
+@* <button @onclick=@(() => ApplyInfoEdit(false)) class="secondary">
<img src="/images/cross.svg"/>
<p>Cancel</p>
</button>
@@ -132,23 +135,23 @@
<img src="/images/checkmark.svg"/>
<p>Apply</p>
</button>
- } else {
+ *@ } else {
<button @onclick=@(() => InfoEditMode = true) class="secondary" data-keyboard-shortcut="e">
<img src="/images/edit.svg"/>
<p><u>E</u>dit Info</p>
</button>
}
- @if(media.IsIngest) {
+@* @if(media.IsIngest) {
<button @onclick=@(() => SetIngest(false)) data-keyboard-shortcut="c">
<img src="/images/checkmark.svg"/>
<p>Mark Tagging <u>C</u>omplete</p>
</button>
} else {
- <button class="secondary" @onclick=@(() => SetIngest(true)) data-keyboard-shortcut="c">
+ *@ <button class="secondary" @onclick=@(() => SetIngest(true)) data-keyboard-shortcut="c">
<img src="/images/cross.svg"/>
<p>Mark Tagging In<u>c</u>omplete</p>
</button>
- }
+ @* } *@
</ButtonContainer>
</div>
</div>
@@ -159,7 +162,7 @@
</div>
</div>
-<Dialog Title="Delete this media?" @ref=deleteDialog>
+@* <Dialog Title="Delete this media?" @ref=deleteDialog>
<ButtonContainer>
<button @onclick=@(() => deleteDialog.Hide()) class="secondary">Cancel</button>
<button @onclick=DeleteMedia class="warning">Confirm</button>
@@ -181,56 +184,58 @@
Title="Select one or more tag(s) to add"
OnSubmit=AddTags
@ref=tagDialog/>
-
+ *@
@code {
- [Parameter]
- [SupplyParameterFromQuery(Name = "m")]
- public Guid MediaId { get; set; }
+ [Parameter]
+ [SupplyParameterFromQuery(Name = "m")]
+ public Guid MediaId { get; set; }
- private Media media;
+ private ApiModels.Media media;
- private string title;
+ private string title;
- private bool infoEditMode = false;
- private string? shortDescription;
- private string? longDescription;
+ private bool infoEditMode = false;
+ private string? shortDescription;
+ private string? longDescription;
- private MediaTagTable mediaTagTable;
- private Dialog deleteDialog;
- private Dialog ocrDialog;
- private TagSelectDialog tagDialog;
+ private MediaTagTable mediaTagTable;
+ // private Dialog deleteDialog;
+ // private Dialog ocrDialog;
+ // private TagSelectDialog tagDialog;
- private ElementReference shortDescriptionInput;
+ private ElementReference shortDescriptionInput;
- protected override void OnInitialized() =>
- LoadMedia();
+ protected override void OnInitialized() =>
+ LoadMedia();
- protected override async void OnAfterRender(bool firstRender) {
- if(infoEditMode)
- await shortDescriptionInput.FocusAsync();
- }
+ protected override async void OnAfterRender(bool firstRender) {
+ if(infoEditMode)
+ await shortDescriptionInput.FocusAsync();
+ }
- private void LoadMedia() {
- using var db = dbFactory.CreateDbContext();
- media = db.Media
- .Include(m => m.Tags)
- .ThenInclude(t => t.TagDefinition)
- .Include(m => m.CurrentUploadedFile)
- .Include(m => m.UploadedFiles)
- .Include(m => m.OcrData)
- .First(m => m.Guid == MediaId);
+ private async void LoadMedia() {
+ media = await hb.Media.GetAsync(MediaId);
+ // using var db = dbFactory.CreateDbContext();
+ // media = db.Media
+ // .Include(m => m.Tags)
+ // .ThenInclude(t => t.TagDefinition)
+ // .Include(m => m.CurrentUploadedFile)
+ // .Include(m => m.UploadedFiles)
+ // .Include(m => m.OcrData)
+ // .First(m => m.Guid == MediaId);
- title = media.DisplayName ?? "Media View";
- }
+ // title = media.DisplayName ?? "Media View";
+ // InvokeAsync(() => StateHasChanged());
+ }
- private void AddTags(TagDefinition[] tagDefs) {
- foreach(var tagDef in tagDefs)
- tagService.AddTag(media, tagDef);
- mediaTagTable.Refresh();
- }
+ // private void AddTags(TagDefinition[] tagDefs) {
+ // foreach(var tagDef in tagDefs)
+ // tagService.AddTag(media, tagDef);
+ // mediaTagTable.Refresh();
+ // }
private async void SetIngest(bool ingest) {
- mediaService.SetIngest(media, ingest);
+ // mediaService.SetIngest(media, ingest);
LoadMedia();
if(ingest)
@@ -249,17 +254,17 @@
}
}
- private void ApplyInfoEdit(bool apply) {
- if(apply) {
- mediaService.SetDescription(media, shortDescription, longDescription);
- LoadMedia();
- }
+ // private void ApplyInfoEdit(bool apply) {
+ // if(apply) {
+ // mediaService.SetDescription(media, shortDescription, longDescription);
+ // LoadMedia();
+ // }
- infoEditMode = false;
- }
+ // infoEditMode = false;
+ // }
- private async void DeleteMedia() {
- mediaService.Delete(media);
- await jsRuntime.InvokeVoidAsync("history.back");
- }
+ // private async void DeleteMedia() {
+ // mediaService.Delete(media);
+ // await jsRuntime.InvokeVoidAsync("history.back");
+ // }
}
diff --git a/Pages/ViewMedia.razor.css b/Server.Client/Pages/ViewMedia.razor.css
index 1a856d3..1a856d3 100644
--- a/Pages/ViewMedia.razor.css
+++ b/Server.Client/Pages/ViewMedia.razor.css
diff --git a/Server.Client/Program.cs b/Server.Client/Program.cs
new file mode 100644
index 0000000..21f3e23
--- /dev/null
+++ b/Server.Client/Program.cs
@@ -0,0 +1,16 @@
+using HyperBooru.ApiClient;
+using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
+
+namespace HyperBooru.Client;
+
+internal class Program {
+ static async Task Main(string[] args) {
+ var builder = WebAssemblyHostBuilder.CreateDefault(args);
+
+ builder.Services.AddSingleton<HBSession>(sp => new(new HttpClient {
+ BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
+ }));
+
+ await builder.Build().RunAsync();
+ }
+}
diff --git a/Server.Client/Routes.razor b/Server.Client/Routes.razor
new file mode 100644
index 0000000..105855d
--- /dev/null
+++ b/Server.Client/Routes.razor
@@ -0,0 +1,6 @@
+<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
+ <Found Context="routeData">
+ <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
+ <FocusOnNavigate RouteData="routeData" Selector="h1" />
+ </Found>
+</Router>
diff --git a/Server.Client/_Imports.razor b/Server.Client/_Imports.razor
new file mode 100644
index 0000000..a420c68
--- /dev/null
+++ b/Server.Client/_Imports.razor
@@ -0,0 +1,13 @@
+@using System.Net.Http
+@using System.Net.Http.Json
+@using Microsoft.AspNetCore.Components.Forms
+@using Microsoft.AspNetCore.Components.Routing
+@using Microsoft.AspNetCore.Components.Web
+@using static Microsoft.AspNetCore.Components.Web.RenderMode
+@using Microsoft.AspNetCore.Components.Web.Virtualization
+@using Microsoft.JSInterop
+@using HyperBooru.ApiClient
+@using HyperBooru.ApiModels
+@using HyperBooru.Client
+@using HyperBooru.Client.Layout
+@using HyperBooru.Client.Pages.Component
diff --git a/Server.Client/wwwroot/appsettings.Development.json b/Server.Client/wwwroot/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/Server.Client/wwwroot/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/Server.Client/wwwroot/appsettings.json b/Server.Client/wwwroot/appsettings.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/Server.Client/wwwroot/appsettings.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/Server/Components/App.razor b/Server/Components/App.razor
new file mode 100644
index 0000000..614372e
--- /dev/null
+++ b/Server/Components/App.razor
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
+ <base href="/" />
+ <ResourcePreloader />
+ <link rel="stylesheet" href="@Assets["app.css"]" />
+ <link rel="stylesheet" href="@Assets["HyperBooru.Server.styles.css"]" />
+ <link rel="stylesheet" href="/styles/global.css" />
+ <link href="/favicon.ico" rel="icon" />
+ <link href="/manifest.webmanifest" rel="manifest" />
+ <script type="text/javascript" src="/js/dialog.js"></script>
+ <script type="text/javascript" src="/js/keyboard.js"></script>
+ <script type="text/javascript" src="/js/mobile.js"></script>
+ <ImportMap />
+ <HeadOutlet @rendermode="InteractiveWebAssembly" />
+ </head>
+
+ <body>
+ <Routes @rendermode="InteractiveWebAssembly" />
+ <script src="@Assets["_framework/blazor.web.js"]"></script>
+ </body>
+</html>
diff --git a/Server/Components/Pages/Error.razor b/Server/Components/Pages/Error.razor
new file mode 100644
index 0000000..576cc2d
--- /dev/null
+++ b/Server/Components/Pages/Error.razor
@@ -0,0 +1,36 @@
+@page "/Error"
+@using System.Diagnostics
+
+<PageTitle>Error</PageTitle>
+
+<h1 class="text-danger">Error.</h1>
+<h2 class="text-danger">An error occurred while processing your request.</h2>
+
+@if (ShowRequestId)
+{
+ <p>
+ <strong>Request ID:</strong> <code>@RequestId</code>
+ </p>
+}
+
+<h3>Development Mode</h3>
+<p>
+ Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
+</p>
+<p>
+ <strong>The Development environment shouldn't be enabled for deployed applications.</strong>
+ It can result in displaying sensitive information from exceptions to end users.
+ For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
+ and restarting the app.
+</p>
+
+@code{
+ [CascadingParameter]
+ private HttpContext? HttpContext { get; set; }
+
+ private string? RequestId { get; set; }
+ private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
+
+ protected override void OnInitialized() =>
+ RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
+}
diff --git a/Server/Components/_Imports.razor b/Server/Components/_Imports.razor
new file mode 100644
index 0000000..2986fa6
--- /dev/null
+++ b/Server/Components/_Imports.razor
@@ -0,0 +1,11 @@
+@using System.Net.Http
+@using System.Net.Http.Json
+@using Microsoft.AspNetCore.Components.Forms
+@using Microsoft.AspNetCore.Components.Routing
+@using Microsoft.AspNetCore.Components.Web
+@using static Microsoft.AspNetCore.Components.Web.RenderMode
+@using Microsoft.AspNetCore.Components.Web.Virtualization
+@using Microsoft.JSInterop
+@using HyperBooru.Server
+@using HyperBooru.Client
+@using HyperBooru.Server.Components
diff --git a/Controllers/ApiFeedController.cs b/Server/Controllers/ApiFeedController.cs
index 382169e..fb260e6 100644
--- a/Controllers/ApiFeedController.cs
+++ b/Server/Controllers/ApiFeedController.cs
@@ -1,13 +1,11 @@
using HyperBooru.ApiModels;
using HyperBooru.Services;
-using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace HyperBooru.Controllers;
[ApiController]
-[Authorize]
[Route("/api/feed")]
public class ApiFeedController : Controller {
private IFeedService feedService;
diff --git a/Controllers/ApiMediaController.cs b/Server/Controllers/ApiMediaController.cs
index a1b07b1..5a8ef21 100644
--- a/Controllers/ApiMediaController.cs
+++ b/Server/Controllers/ApiMediaController.cs
@@ -1,6 +1,5 @@
using HyperBooru.ApiModels;
using HyperBooru.Services;
-using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
@@ -8,7 +7,6 @@ using System.Text.Json;
namespace HyperBooru.Controllers;
[ApiController]
-[Authorize]
[Route("/api/media")]
public class ApiMediaController : Controller {
private IDbContextFactory<HBContext> dbFactory;
diff --git a/Controllers/ApiTagController.cs b/Server/Controllers/ApiTagController.cs
index f48cc05..e8417d2 100644
--- a/Controllers/ApiTagController.cs
+++ b/Server/Controllers/ApiTagController.cs
@@ -6,7 +6,6 @@ using Microsoft.EntityFrameworkCore;
namespace HyperBooru.Controllers;
[ApiController]
-[Authorize]
[Route("/api/tag")]
public class ApiTagController : Controller {
private IDbContextFactory<HBContext> dbFactory;
diff --git a/Controllers/ApiUserController.cs b/Server/Controllers/ApiUserController.cs
index d678287..d678287 100644
--- a/Controllers/ApiUserController.cs
+++ b/Server/Controllers/ApiUserController.cs
diff --git a/Controllers/MediaController.cs b/Server/Controllers/MediaController.cs
index 6a9e1fc..27c3cbd 100644
--- a/Controllers/MediaController.cs
+++ b/Server/Controllers/MediaController.cs
@@ -8,7 +8,6 @@ using Microsoft.EntityFrameworkCore;
namespace HyperBooru.Controllers;
[ApiController]
-[Authorize]
[Route("/media")]
public class MediaController : Controller {
private IHttpContextAccessor httpContextAccessor;
diff --git a/Dockerfile b/Server/Dockerfile
index 7769bf4..7769bf4 100644
--- a/Dockerfile
+++ b/Server/Dockerfile
diff --git a/ExceptionMiddleware.cs b/Server/ExceptionMiddleware.cs
index 29d0e10..29d0e10 100644
--- a/ExceptionMiddleware.cs
+++ b/Server/ExceptionMiddleware.cs
diff --git a/HBContext.cs b/Server/HBContext.cs
index b684a51..b684a51 100644
--- a/HBContext.cs
+++ b/Server/HBContext.cs
diff --git a/HBObject.cs b/Server/HBObject.cs
index 8001ea3..8001ea3 100644
--- a/HBObject.cs
+++ b/Server/HBObject.cs
diff --git a/Server/LICENSE.txt b/Server/LICENSE.txt
new file mode 100644
index 0000000..0ad25db
--- /dev/null
+++ b/Server/LICENSE.txt
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<https://www.gnu.org/licenses/>.
diff --git a/Media.cs b/Server/Media.cs
index 2ff9e63..2ff9e63 100644
--- a/Media.cs
+++ b/Server/Media.cs
diff --git a/Migrations/20260131125650_InitialMigration.Designer.cs b/Server/Migrations/20260131125650_InitialMigration.Designer.cs
index 2e4a05e..2e4a05e 100644
--- a/Migrations/20260131125650_InitialMigration.Designer.cs
+++ b/Server/Migrations/20260131125650_InitialMigration.Designer.cs
diff --git a/Migrations/20260131125650_InitialMigration.cs b/Server/Migrations/20260131125650_InitialMigration.cs
index a1a7d8f..a1a7d8f 100644
--- a/Migrations/20260131125650_InitialMigration.cs
+++ b/Server/Migrations/20260131125650_InitialMigration.cs
diff --git a/Migrations/HBContextModelSnapshot.cs b/Server/Migrations/HBContextModelSnapshot.cs
index 422037f..422037f 100644
--- a/Migrations/HBContextModelSnapshot.cs
+++ b/Server/Migrations/HBContextModelSnapshot.cs
diff --git a/Program.cs b/Server/Program.cs
index 5863368..687c6f8 100644
--- a/Program.cs
+++ b/Server/Program.cs
@@ -1,40 +1,38 @@
+using HyperBooru.ApiClient;
+using HyperBooru.Server.Components;
using HyperBooru.Services;
-using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.DataProtection;
-using Microsoft.AspNetCore.Http.Json;
using Microsoft.EntityFrameworkCore;
using System.Text.Json.Serialization;
-namespace HyperBooru;
+namespace HyperBooru.Server;
public class Program {
public static void Main(string[] args) {
var builder = WebApplication.CreateBuilder(args);
- builder.Services.AddSession();
+
+ // Add services to the container.
builder.Services.AddHttpContextAccessor();
- builder.Services.AddAuthentication(
- CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
- builder.Services.AddAuthorization();
builder.Services.AddControllers().AddJsonOptions(o => {
var converter = new JsonStringEnumConverter();
o.JsonSerializerOptions.Converters.Add(converter);
});
- builder.Services.Configure<JsonOptions>(o => {
- o.SerializerOptions.TypeInfoResolverChain.Insert(0, new ExceptionJsonResolver());
- });
- builder.Services.AddRazorPages();
- builder.Services.AddServerSideBlazor();
+ builder.Services.AddRazorComponents()
+ .AddInteractiveWebAssemblyComponents();
// Add our custom services
builder.Services.AddSingleton<IConfigService, ConfigService>();
builder.Services.AddDbContextFactory<HBContext>();
+ builder.Services.AddSingleton<IGlobalUserService, GlobalUserService>();
+ builder.Services.AddScoped<IMediaService, MediaService>();
builder.Services.AddScoped<IFeedService, FeedService>();
builder.Services.AddScoped<ITagService, TagService>();
- builder.Services.AddScoped<IMediaService, MediaService>();
- builder.Services.AddSingleton<IGlobalUserService, GlobalUserService>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddHostedService<OcrService>();
builder.Services.AddSingleton<ISourceService, SourceService>();
+ builder.Services.AddSingleton(sp => new HBSession() {
+ BaseUri = new("https://127.0.0.1:7084")
+ });
// Ensure session keys are stored in a persistent location on all platforms
builder.Services.AddDataProtection()
@@ -50,17 +48,25 @@ public class Program {
using var db = scope.ServiceProvider.GetRequiredService<HBContext>();
db.Database.Migrate();
- app.UseRouting();
- app.UseSession();
- app.UseAuthentication();
- app.UseAuthorization();
- app.UseHsts();
+ // Configure the HTTP request pipeline.
+ if(app.Environment.IsDevelopment()) {
+ app.UseWebAssemblyDebugging();
+ } else {
+ app.UseExceptionHandler("/Error");
+ // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
+ app.UseHsts();
+ }
+
+ app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
app.UseHttpsRedirection();
- app.UseStaticFiles();
- app.UseMiddleware<ExceptionMiddleware>();
- app.MapBlazorHub();
+
+ app.UseAntiforgery();
+
+ app.MapStaticAssets();
app.MapControllers();
- app.MapFallbackToPage("/_Host");
+ app.MapRazorComponents<App>()
+ .AddInteractiveWebAssemblyRenderMode()
+ .AddAdditionalAssemblies(typeof(Client._Imports).Assembly);
app.Run();
}
diff --git a/Server/Properties/launchSettings.json b/Server/Properties/launchSettings.json
new file mode 100644
index 0000000..f37fc08
--- /dev/null
+++ b/Server/Properties/launchSettings.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
+ "applicationUrl": "http://localhost:5062",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
+ "applicationUrl": "https://localhost:7084;http://localhost:5062",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+ }
diff --git a/Server.csproj b/Server/Server.csproj
index 45bb9bd..829efa4 100644
--- a/Server.csproj
+++ b/Server/Server.csproj
@@ -1,32 +1,12 @@
-<Project Sdk="Microsoft.NET.Sdk.Web">
+<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
- <AssemblyName>HyperBooru</AssemblyName>
- <RootNamespace>HyperBooru</RootNamespace>
- <AssemblyVersion>0.17.0.0</AssemblyVersion>
- <FileVersion>$(AssemblyVersion)</FileVersion>
- <Version>0.17-alpha</Version>
- <UserSecretsId>2907567f-4640-4581-8f4d-0977952d26bd</UserSecretsId>
- </PropertyGroup>
-
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
- <NoWarn>1701;1702;8618</NoWarn>
- </PropertyGroup>
-
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
- <NoWarn>1701;1702;8618</NoWarn>
- </PropertyGroup>
-
- <PropertyGroup>
- <DefaultItemExcludes>$(DefaultItemExcludes);Data/**</DefaultItemExcludes>
- </PropertyGroup>
-
- <PropertyGroup Condition="'$(Configuration)' == 'Release'">
- <DebugType>None</DebugType>
- <DebugSymbols>false</DebugSymbols>
+ <BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
+ <AssemblyName>HyperBooru.Server</AssemblyName>
+ <RootNamespace>HyperBooru.Server</RootNamespace>
</PropertyGroup>
<ItemGroup>
@@ -37,6 +17,8 @@
</ItemGroup>
<ItemGroup>
+ <ProjectReference Include="..\Server.Client\Client.csproj" />
+ <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.8" />
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.13.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7">
@@ -45,13 +27,8 @@
</PackageReference>
<PackageReference Include="Mime-Detective" Version="25.8.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
- <PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
<PackageReference Include="System.Drawing.Common" Version="10.0.7" />
<PackageReference Include="Tesseract" Version="5.2.0" />
</ItemGroup>
- <ItemGroup>
- <ProjectReference Include="..\ApiModels\ApiModels.csproj" />
- </ItemGroup>
-
</Project>
diff --git a/Services/ConfigService.cs b/Server/Services/ConfigService.cs
index ac1f155..ac1f155 100644
--- a/Services/ConfigService.cs
+++ b/Server/Services/ConfigService.cs
diff --git a/Services/FeedService.cs b/Server/Services/FeedService.cs
index 3744e73..3744e73 100644
--- a/Services/FeedService.cs
+++ b/Server/Services/FeedService.cs
diff --git a/Services/MediaService.cs b/Server/Services/MediaService.cs
index e497570..e497570 100644
--- a/Services/MediaService.cs
+++ b/Server/Services/MediaService.cs
diff --git a/Services/OcrService.cs b/Server/Services/OcrService.cs
index d43db2e..d43db2e 100644
--- a/Services/OcrService.cs
+++ b/Server/Services/OcrService.cs
diff --git a/Services/SourceService.cs b/Server/Services/SourceService.cs
index d145346..d145346 100644
--- a/Services/SourceService.cs
+++ b/Server/Services/SourceService.cs
diff --git a/Services/TagService.cs b/Server/Services/TagService.cs
index f7b91dc..f7b91dc 100644
--- a/Services/TagService.cs
+++ b/Server/Services/TagService.cs
diff --git a/Services/UserService.cs b/Server/Services/UserService.cs
index 9e79dc6..9e79dc6 100644
--- a/Services/UserService.cs
+++ b/Server/Services/UserService.cs
diff --git a/Tag.cs b/Server/Tag.cs
index c857c66..c857c66 100644
--- a/Tag.cs
+++ b/Server/Tag.cs
diff --git a/Todo.md b/Server/Todo.md
index 23c406d..23c406d 100644
--- a/Todo.md
+++ b/Server/Todo.md
diff --git a/User.cs b/Server/User.cs
index 87384d2..87384d2 100644
--- a/User.cs
+++ b/Server/User.cs
diff --git a/Util.cs b/Server/Util.cs
index 6af6c81..6af6c81 100644
--- a/Util.cs
+++ b/Server/Util.cs
diff --git a/Server/appsettings.Development.json b/Server/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/Server/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/appsettings.json b/Server/appsettings.json
index 414e673..414e673 100644
--- a/appsettings.json
+++ b/Server/appsettings.json
diff --git a/.config/dotnet-tools.json b/Server/dotnet-tools.json
index e3d2b85..7dcefc3 100644
--- a/.config/dotnet-tools.json
+++ b/Server/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
- "version": "8.0.23",
+ "version": "10.0.8",
"commands": [
"dotnet-ef"
],
diff --git a/tessdata/eng.traineddata b/Server/tessdata/eng.traineddata
index 176dc32..176dc32 100644
--- a/tessdata/eng.traineddata
+++ b/Server/tessdata/eng.traineddata
Binary files differ
diff --git a/Server/wwwroot/app.css b/Server/wwwroot/app.css
new file mode 100644
index 0000000..73a69d6
--- /dev/null
+++ b/Server/wwwroot/app.css
@@ -0,0 +1,60 @@
+html, body {
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+}
+
+a, .btn-link {
+ color: #006bb7;
+}
+
+.btn-primary {
+ color: #fff;
+ background-color: #1b6ec2;
+ border-color: #1861ac;
+}
+
+.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
+ box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
+}
+
+.content {
+ padding-top: 1.1rem;
+}
+
+h1:focus {
+ outline: none;
+}
+
+.valid.modified:not([type=checkbox]) {
+ outline: 1px solid #26b050;
+}
+
+.invalid {
+ outline: 1px solid #e50000;
+}
+
+.validation-message {
+ color: #e50000;
+}
+
+.blazor-error-boundary {
+ background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
+ padding: 1rem 1rem 1rem 3.7rem;
+ color: white;
+}
+
+ .blazor-error-boundary::after {
+ content: "An error has occurred."
+ }
+
+.darker-border-checkbox.form-check-input {
+ border-color: #929292;
+}
+
+.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
+ color: var(--bs-secondary-color);
+ text-align: end;
+}
+
+.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
+ text-align: start;
+} \ No newline at end of file
diff --git a/wwwroot/css/site.css b/Server/wwwroot/css/site.css
index 21f9a94..21f9a94 100644
--- a/wwwroot/css/site.css
+++ b/Server/wwwroot/css/site.css
diff --git a/wwwroot/favicon.ico b/Server/wwwroot/favicon.ico
index a1be4cc..a1be4cc 100644
--- a/wwwroot/favicon.ico
+++ b/Server/wwwroot/favicon.ico
Binary files differ
diff --git a/wwwroot/icon-192.png b/Server/wwwroot/icon-192.png
index 28ce06d..28ce06d 100644
--- a/wwwroot/icon-192.png
+++ b/Server/wwwroot/icon-192.png
Binary files differ
diff --git a/wwwroot/icon-512.png b/Server/wwwroot/icon-512.png
index 8c28696..8c28696 100644
--- a/wwwroot/icon-512.png
+++ b/Server/wwwroot/icon-512.png
Binary files differ
diff --git a/wwwroot/images/book.svg b/Server/wwwroot/images/book.svg
index 6cdfc79..6cdfc79 100644
--- a/wwwroot/images/book.svg
+++ b/Server/wwwroot/images/book.svg
diff --git a/wwwroot/images/checkmark.svg b/Server/wwwroot/images/checkmark.svg
index 5e55d9e..5e55d9e 100644
--- a/wwwroot/images/checkmark.svg
+++ b/Server/wwwroot/images/checkmark.svg
diff --git a/wwwroot/images/cross.svg b/Server/wwwroot/images/cross.svg
index 0c37363..0c37363 100644
--- a/wwwroot/images/cross.svg
+++ b/Server/wwwroot/images/cross.svg
diff --git a/wwwroot/images/edit.svg b/Server/wwwroot/images/edit.svg
index d4c6ec4..d4c6ec4 100644
--- a/wwwroot/images/edit.svg
+++ b/Server/wwwroot/images/edit.svg
diff --git a/wwwroot/images/info.svg b/Server/wwwroot/images/info.svg
index b194f05..b194f05 100644
--- a/wwwroot/images/info.svg
+++ b/Server/wwwroot/images/info.svg
diff --git a/wwwroot/images/loginbg.webp b/Server/wwwroot/images/loginbg.webp
index 759e666..759e666 100644
--- a/wwwroot/images/loginbg.webp
+++ b/Server/wwwroot/images/loginbg.webp
Binary files differ
diff --git a/wwwroot/images/photo.svg b/Server/wwwroot/images/photo.svg
index 486c360..486c360 100644
--- a/wwwroot/images/photo.svg
+++ b/Server/wwwroot/images/photo.svg
diff --git a/wwwroot/images/tag.svg b/Server/wwwroot/images/tag.svg
index 3eb8843..3eb8843 100644
--- a/wwwroot/images/tag.svg
+++ b/Server/wwwroot/images/tag.svg
diff --git a/wwwroot/images/trash.svg b/Server/wwwroot/images/trash.svg
index 18ff9c1..18ff9c1 100644
--- a/wwwroot/images/trash.svg
+++ b/Server/wwwroot/images/trash.svg
diff --git a/wwwroot/js/dialog.js b/Server/wwwroot/js/dialog.js
index 418962f..418962f 100644
--- a/wwwroot/js/dialog.js
+++ b/Server/wwwroot/js/dialog.js
diff --git a/wwwroot/js/keyboard.js b/Server/wwwroot/js/keyboard.js
index 8b46639..8b46639 100644
--- a/wwwroot/js/keyboard.js
+++ b/Server/wwwroot/js/keyboard.js
diff --git a/wwwroot/js/mobile.js b/Server/wwwroot/js/mobile.js
index 0af11cc..0af11cc 100644
--- a/wwwroot/js/mobile.js
+++ b/Server/wwwroot/js/mobile.js
diff --git a/wwwroot/loginbg.webm b/Server/wwwroot/loginbg.webm
index 139ed0d..139ed0d 100644
--- a/wwwroot/loginbg.webm
+++ b/Server/wwwroot/loginbg.webm
Binary files differ
diff --git a/wwwroot/manifest.webmanifest b/Server/wwwroot/manifest.webmanifest
index f150f98..f150f98 100644
--- a/wwwroot/manifest.webmanifest
+++ b/Server/wwwroot/manifest.webmanifest
diff --git a/wwwroot/styles/data-table.css b/Server/wwwroot/styles/data-table.css
index 994d625..994d625 100644
--- a/wwwroot/styles/data-table.css
+++ b/Server/wwwroot/styles/data-table.css
diff --git a/wwwroot/styles/global.css b/Server/wwwroot/styles/global.css
index 9de9fc1..9de9fc1 100644
--- a/wwwroot/styles/global.css
+++ b/Server/wwwroot/styles/global.css
diff --git a/_Imports.razor b/_Imports.razor
deleted file mode 100644
index 4c5566b..0000000
--- a/_Imports.razor
+++ /dev/null
@@ -1,9 +0,0 @@
-@using HyperBooru
-@using HyperBooru.Pages.Component
-@using HyperBooru.Services
-@using Microsoft.AspNetCore.Authorization
-@using Microsoft.AspNetCore.Components.Authorization
-@using Microsoft.AspNetCore.Components.Routing
-@using Microsoft.AspNetCore.Components.Web
-@using Microsoft.EntityFrameworkCore
-@using Microsoft.JSInterop
diff --git a/appsettings.Development.json b/appsettings.Development.json
deleted file mode 100644
index b3da6a4..0000000
--- a/appsettings.Development.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "AllowedHosts": "*",
- "Kestrel": {
- "Endpoints": {
- "Http": {
- "Url": "http://0.0.0.0:7132"
- }
- }
- },
- "DetailedErrors": true,
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft.AspNetCore": "Warning",
- "HyperBooru.Services.OcrService": "Debug"
- }
- },
- "DisableOcr": true
-}