summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Dockerfile4
-rw-r--r--MainLayout.razor2
-rw-r--r--Pages/Component/Dialog.razor.css2
-rw-r--r--Pages/Component/MediaTagTable.razor.css8
-rw-r--r--Pages/Component/MobileMenu.razor32
-rw-r--r--Pages/Component/MobileMenu.razor.css46
-rw-r--r--Pages/Component/TabContainer.razor.css14
-rw-r--r--Pages/Component/TagSelectDialog.razor.css6
-rw-r--r--Pages/Component/Titlebar.razor29
-rw-r--r--Pages/Component/Titlebar.razor.css28
-rw-r--r--Pages/Gallery.razor.css7
-rw-r--r--Pages/TagDefinitions.razor.css5
-rw-r--r--Pages/Upload.razor2
-rw-r--r--Pages/Upload.razor.css25
-rw-r--r--Pages/ViewMedia.razor204
-rw-r--r--Pages/ViewMedia.razor.css134
-rw-r--r--Pages/_Host.cshtml4
-rw-r--r--Server.csproj18
-rw-r--r--appsettings.Development.json8
-rw-r--r--wwwroot/js/mobile.js3
-rw-r--r--wwwroot/styles/global.css46
21 files changed, 460 insertions, 167 deletions
diff --git a/Dockerfile b/Dockerfile
index 463a11d..a93e994 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,11 +1,11 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0@sha256:aa05b91be697b83229cb000b90120f0783604ad74ed92a0b45cdf3d1a9c873de AS build
+FROM mcr.microsoft.com/dotnet/sdk:10.0@sha256:f061e5a7532b36fa1d1b684857fe1f504ba92115b9934f154643266613c44c62 AS build
WORKDIR /App
COPY . ./
RUN dotnet restore
RUN dotnet publish -o out
-FROM mcr.microsoft.com/dotnet/aspnet:8.0@sha256:4b8f0b08534833b39bb662fb19a65e78cb086f5ca8dd35de3f87026de8885be4
+FROM mcr.microsoft.com/dotnet/aspnet:10.0@sha256:ccdca44cd4f256d50187f920dc8ccc2a9ea7a8a4597ac1d51e08fddb2e3b3205
RUN apt update
RUN apt install -y imagemagick
RUN apt clean
diff --git a/MainLayout.razor b/MainLayout.razor
index 5d68b65..8e9f6bd 100644
--- a/MainLayout.razor
+++ b/MainLayout.razor
@@ -4,6 +4,8 @@
<Titlebar/>
+<MobileMenu/>
+
<div id="content">
@Body
</div>
diff --git a/Pages/Component/Dialog.razor.css b/Pages/Component/Dialog.razor.css
index 1447407..93680c5 100644
--- a/Pages/Component/Dialog.razor.css
+++ b/Pages/Component/Dialog.razor.css
@@ -10,7 +10,7 @@
top: 50%;
transform: translate(-50%, -50%);
transition: visibility 0.1s, opacity 0.1s linear;
- width: 450px;
+ width: min(450px, 100%);
z-index: 1000;
}
diff --git a/Pages/Component/MediaTagTable.razor.css b/Pages/Component/MediaTagTable.razor.css
index dcf5e09..4dedb3f 100644
--- a/Pages/Component/MediaTagTable.razor.css
+++ b/Pages/Component/MediaTagTable.razor.css
@@ -1,3 +1,9 @@
-td {
+th, td {
font-size: 8pt;
}
+
+@media (hover: none) and (pointer: coarse) {
+ th, td {
+ font-size: 7pt;
+ }
+}
diff --git a/Pages/Component/MobileMenu.razor b/Pages/Component/MobileMenu.razor
new file mode 100644
index 0000000..6cb3281
--- /dev/null
+++ b/Pages/Component/MobileMenu.razor
@@ -0,0 +1,32 @@
+@inject NavigationManager navigationManager
+@inject IJSRuntime jsRuntime
+@implements IDisposable
+
+<div id="mobile-menu" class="hidden">
+ <a href="/">Home</a>
+ <a href="/TagDefinitions">Tags</a>
+ <a href="/Gallery?ingest=true">Ingest</a>
+ <a href="/Upload">Upload</a>
+ <div id="nsfw-switch">
+ <p id="nsfw-label">NSFW</p>
+ <NsfwSwitch/>
+ </div>
+ <a href="javascript:logout();">Logout</a>
+</div>
+
+<script suppress-error="BL9992">
+ function hideMobileMenu() {
+ document.getElementById('mobile-menu').classList.add('hidden');
+ }
+</script>
+
+@code {
+ protected override void OnInitialized() =>
+ navigationManager.LocationChanged += LocationChanged;
+
+ public async void LocationChanged(object? sender, LocationChangedEventArgs e) =>
+ await jsRuntime.InvokeVoidAsync("hideMobileMenu");
+
+ public void Dispose() =>
+ navigationManager.LocationChanged -= LocationChanged;
+}
diff --git a/Pages/Component/MobileMenu.razor.css b/Pages/Component/MobileMenu.razor.css
new file mode 100644
index 0000000..b60e07b
--- /dev/null
+++ b/Pages/Component/MobileMenu.razor.css
@@ -0,0 +1,46 @@
+div#mobile-menu {
+ background: var(--col-bg);
+ display: flex;
+ flex-direction: column;
+ flex: 1 1 calc(100vh - 59px);
+ height: 100%;
+ overflow-y: auto;
+ position: relative;
+ width: 100%;
+}
+
+div#mobile-menu.hidden {
+ display: none;
+}
+
+div#mobile-menu > a {
+ color: #fff;
+ padding: 20px;
+}
+
+div#mobile-menu > a:not(:last-of-type) {
+ border-bottom: 1px solid var(--col-hr);
+}
+
+div#mobile-menu > a:hover {
+ background: var(--col-dialog-bg);
+ filter: none;
+}
+
+div#mobile-menu > a:active {
+ background: #fff;
+ color: var(--col-bg);
+ filter: none;
+}
+
+div#nsfw-switch {
+ align-items: center;
+ border-bottom: 1px solid var(--col-hr);
+ display: flex;
+ flex-direction: row;
+ padding: 5px 20px 5px 20px;
+}
+
+div#nsfw-switch > p#nsfw-label {
+ margin-right: auto;
+}
diff --git a/Pages/Component/TabContainer.razor.css b/Pages/Component/TabContainer.razor.css
index 6a56021..bfb5694 100644
--- a/Pages/Component/TabContainer.razor.css
+++ b/Pages/Component/TabContainer.razor.css
@@ -13,7 +13,15 @@ div.tabs > a.selected {
padding-bottom: 5px;
}
-div.tabs > a:hover {
- background: rgba(255, 255, 255, 0.4);
- filter: none;
+@media (hover: hover) {
+ div.tabs > a:hover {
+ background: rgba(255, 255, 255, 0.4);
+ filter: none;
+ }
+}
+
+@media (hover: none) and (pointer: coarse) {
+ div.tabs > a {
+ font-size: 8pt;
+ }
}
diff --git a/Pages/Component/TagSelectDialog.razor.css b/Pages/Component/TagSelectDialog.razor.css
index 7b50077..dadd0c4 100644
--- a/Pages/Component/TagSelectDialog.razor.css
+++ b/Pages/Component/TagSelectDialog.razor.css
@@ -14,8 +14,10 @@ div.tag-definitions label {
transition: background 0.1s linear;
}
-div.tag-definitions label:hover {
- background: #777;
+@media(hover: hover) {
+ div.tag-definitions label:hover {
+ background: #777;
+ }
}
div.tag-definitions input:checked + label {
diff --git a/Pages/Component/Titlebar.razor b/Pages/Component/Titlebar.razor
index 8033413..a0b9eec 100644
--- a/Pages/Component/Titlebar.razor
+++ b/Pages/Component/Titlebar.razor
@@ -43,20 +43,23 @@
<AuthorizeView>
<Authorized>
<div id="navbar">
- <a href="/">Home</a>
- <a href="/TagDefinitions">Tags</a>
- <a href="/Gallery?ingest=true">Ingest</a>
- <a href="/Upload">Upload</a>
- <a href="javascript:;" @onclick=@(() => aboutDialog.Show())>About</a>
+ <p class="mobile">HyperBooru</p>
+ <a class="mobile menu-button" href="javascript:toggleMobileMenu();">&#x2630</a>
- <p id="nsfw-label">NSFW</p>
- <div id="nsfw-switch">
- <NsfwSwitch/>
- </div>
- <form action="/Gallery" method="get">
- <input type="text" name="q" placeholder="Search"/>
- </form>
- <a href="javascript:logout();">Logout</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>
diff --git a/Pages/Component/Titlebar.razor.css b/Pages/Component/Titlebar.razor.css
index ea10740..58a1c0c 100644
--- a/Pages/Component/Titlebar.razor.css
+++ b/Pages/Component/Titlebar.razor.css
@@ -11,17 +11,20 @@ div#navbar > h2 {
margin-left: 20px;
}
-div#navbar > a {
+div#navbar > a, div#navbar > p {
align-items: center;
color: white;
display: flex;
height: 100%;
padding: 0 20px 0 20px;
+ user-select: none;
}
-div#navbar > a:hover {
- background: rgba(255, 255, 255, 0.4);
- filter: none;
+@media (hover: hover) and (pointer: fine) {
+ div#navbar > a:hover {
+ background: rgba(255, 255, 255, 0.4);
+ filter: none;
+ }
}
div#navbar > a:active {
@@ -29,6 +32,11 @@ div#navbar > a:active {
color: var(--col-navbar-bg);
}
+div#navbar > a.menu-button {
+ font-size: 18pt;
+ margin-left: auto;
+}
+
p#nsfw-label {
align-self: center;
font-size: 9pt;
@@ -77,3 +85,15 @@ input[type="text"], input[type="password"] {
input[type="password"] {
margin-left: 20px;
}
+
+@media (hover: none) and (pointer: coarse) {
+ .desktop {
+ display: none !important;
+ }
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .mobile {
+ display: none !important;
+ }
+}
diff --git a/Pages/Gallery.razor.css b/Pages/Gallery.razor.css
index 989e252..1b5ed86 100644
--- a/Pages/Gallery.razor.css
+++ b/Pages/Gallery.razor.css
@@ -4,8 +4,9 @@
}
div#feed-error {
- position: relative;
- top: 50%;
- left: 50%;
+ left: 50%;
+ padding: 10px;
+ position: relative;
+ top: 50%;
transform: translate(-50%, -50%);
} \ No newline at end of file
diff --git a/Pages/TagDefinitions.razor.css b/Pages/TagDefinitions.razor.css
new file mode 100644
index 0000000..409eacc
--- /dev/null
+++ b/Pages/TagDefinitions.razor.css
@@ -0,0 +1,5 @@
+@media (hover: none) and (pointer: coarse) {
+ td, th {
+ font-size: 6pt;
+ }
+} \ No newline at end of file
diff --git a/Pages/Upload.razor b/Pages/Upload.razor
index 614cec0..6d6e8bc 100644
--- a/Pages/Upload.razor
+++ b/Pages/Upload.razor
@@ -2,7 +2,7 @@
@attribute [Authorize]
<div id="dropzone">
- <p>Drag a file to upload it<br/>or click to select one or more file(s)</p>
+ <p></p>
<form id="uploadForm" action="/media" method="post" enctype="multipart/form-data">
<input type="file" id="fileUpload" name="fileUpload" accept="image/*,video/*" multiple/>
</form>
diff --git a/Pages/Upload.razor.css b/Pages/Upload.razor.css
index 6ff40a2..d510bc6 100644
--- a/Pages/Upload.razor.css
+++ b/Pages/Upload.razor.css
@@ -8,7 +8,7 @@
top: 50%;
transform: translate(-50%, -50%);
transition: border-color 0.1s linear;
- width: 700px;
+ width: min(700px, 85%);
}
div#dropzone p {
@@ -22,14 +22,27 @@ div#dropzone p {
transform: translate(-50%, -50%);
}
+div#dropzone p::before {
+ content: "Drag a file to upload it\Aor click to select one or more file(s)";
+ white-space: pre;
+}
+
+@media (hover: none) and (pointer: coarse) {
+ div#dropzone p::before {
+ content: "Tap to select a file to upload";
+ }
+}
+
div#dropzone input {
display: none;
}
-div#dropzone.hover, div#dropzone:hover {
- border: 3px dashed white;
-}
+@media (hover: hover) {
+ div#dropzone.hover, div#dropzone:hover {
+ border: 3px dashed white;
+ }
-div#dropzone.hover p, div#dropzone:hover p {
- color: white;
+ div#dropzone.hover p, div#dropzone:hover p {
+ color: white;
+ }
} \ No newline at end of file
diff --git a/Pages/ViewMedia.razor b/Pages/ViewMedia.razor
index b5a33a2..c6ff2ce 100644
--- a/Pages/ViewMedia.razor
+++ b/Pages/ViewMedia.razor
@@ -11,105 +11,117 @@
<script suppress-warning="BL9992">
function toggleSidebar() {
- document.getElementById("metadata").classList.toggle("hidden");
+ document.getElementById("hcontainer").classList.toggle("hide-metadata");
}
+
+ function showSidebar(visible) {
+ document.getElementById("hcontainer").classList.toggle("hide-metadata", !visible)
+ }
</script>
-<div id="content">
- <div id="image-container">
- <img
- src="/media/@(media.Guid)"
- width=@media.CurrentUploadedFile.Width
- height=@media.CurrentUploadedFile.Height
- onclick="toggleSidebar()"/>
- </div>
- <div id="metadata">
- <div id="metadata-container">
- <div id="metadata-fileinfo">
- @if(infoEditMode) {
- <form action="javascript:;" @onsubmit=@(() => ApplyInfoEdit(true))>
- <table id="edit-metadata">
- <tr>
- <td>Title:</td>
- <td><input type="text" @bind=shortDescription @ref=shortDescriptionInput/></td>
- </tr>
- <tr>
- <td>Description:</td>
- <td><textarea rows="4" @bind=longDescription/></td>
- </tr>
- </table>
- </form>
- } 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 class="heading">Upload history</p>
- <hr/>
- <table id="uploaded-files" class="data-table">
- <tr>
- <th>Created On</th>
- <th>Last Write</th>
- <th>Uploaded On</th>
- <th>Filename</th>
- <th>Size</th>
- <th>Original Checksum</th>
- </tr>
- @foreach(var file in media.UploadedFiles.OrderByDescending(uf => uf.UploadTime)) {
- string? sourceUrl = null;
- if(file.Filename is not null)
- sourceUrl = sourceService.GetUrlFromFilename(file.Filename);
- <tr>
- <td title=@file.CreateTime?.ToString()>
- @(file.CreateTime?.ToString("d") ?? "N/A")
- </td>
- <td title=@file.LastWriteTime?.ToString()>
- @(file.LastWriteTime?.ToString("d") ?? "N/A")
- </td>
- <td title=@file.UploadTime>@(file.UploadTime.ToString("d"))</td>
- <td title=@(file.Path is not null ? $"{file.Path.Replace('\\', '/')}/{file.Filename}" : file.Filename)>
- @if(sourceUrl is not null) {
- <a class="nondecorated" target="_blank" href=@sourceUrl>@file.Filename</a>
- } else {
- @file.Filename
- }
- </td>
- <td title=@file.Length>@file.Length.ToBytesSI()</td>
- <td
- title=@(file.Checksum + (file.ChecksumVerified ? " (verified)" : ""))
- class=@(file.ChecksumVerified ? "verified" : null)>
-
- @file.Checksum.Substring(0, 8)
- </td>
- </tr>
- }
- </table>
- </div>
- <div id="metadata-tags">
- <p class="heading">Tags</p>
- <hr/>
- <MediaTagTable Media=media @ref=mediaTagTable/>
- </div>
- </div>
- <div id="button-container">
- <ButtonContainer>
- <button @onclick=@(() => deleteDialog.Show()) class="warning" data-keyboard-shortcut="d"><u>D</u>elete</button>
- <button @onclick=@(() => tagDialog.Show()) class="secondary" data-keyboard-shortcut="t">Add <u>T</u>ag</button>
- <button @onclick=@(() => ocrDialog.Show()) class="secondary" data-keyboard-shortcut="o">View <u>O</u>CR</button>
- @if(infoEditMode) {
- <button @onclick=@(() => ApplyInfoEdit(false)) class="secondary">Cancel</button>
- <button @onclick=@(() => ApplyInfoEdit(true))>Apply</button>
- } else {
- <button @onclick=@(() => InfoEditMode = true) class="secondary" data-keyboard-shortcut="e"><u>E</u>dit Info</button>
- }
- @if(media.IsIngest) {
- <button @onclick=@(() => SetIngest(false)) data-keyboard-shortcut="c">Mark Tagging <u>C</u>omplete</button>
- } else {
- <button class="secondary" @onclick=@(() => SetIngest(true)) data-keyboard-shortcut="c">Mark Tagging In<u>c</u>omplete</button>
- }
- </ButtonContainer>
- </div>
- </div>
+<div id="vcontainer">
+ <div id="hcontainer" class="hide-metadata">
+ <div id="image-container">
+ <img
+ src="/media/@(media.Guid)"
+ width=@media.CurrentUploadedFile.Width
+ height=@media.CurrentUploadedFile.Height/>
+ </div>
+ <div id="metadata-show-button">
+ <a href="javascript:toggleSidebar();"></a>
+ </div>
+ <div id="metadata">
+ <div id="metadata-container">
+ <div id="metadata-fileinfo">
+ @if(infoEditMode) {
+ <form action="javascript:;" @onsubmit=@(() => ApplyInfoEdit(true))>
+ <table id="edit-metadata">
+ <tr>
+ <td>Title:</td>
+ <td><input type="text" @bind=shortDescription @ref=shortDescriptionInput/></td>
+ </tr>
+ <tr>
+ <td>Description:</td>
+ <td><textarea rows="4" @bind=longDescription/></td>
+ </tr>
+ </table>
+ </form>
+ } 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 class="heading">Upload history</p>
+ <hr/>
+ <table id="uploaded-files" class="data-table">
+ <tr>
+ <th>Created On</th>
+ <th>Last Write</th>
+ <th>Uploaded On</th>
+ <th>Filename</th>
+ <th>Size</th>
+ <th>Original Checksum</th>
+ </tr>
+ @foreach(var file in media.UploadedFiles.OrderByDescending(uf => uf.UploadTime)) {
+ string? sourceUrl = null;
+ if(file.Filename is not null)
+ sourceUrl = sourceService.GetUrlFromFilename(file.Filename);
+ <tr>
+ <td title=@file.CreateTime?.ToString()>
+ @(file.CreateTime?.ToString("d") ?? "N/A")
+ </td>
+ <td title=@file.LastWriteTime?.ToString()>
+ @(file.LastWriteTime?.ToString("d") ?? "N/A")
+ </td>
+ <td title=@file.UploadTime>@(file.UploadTime.ToString("d"))</td>
+ <td title=@(file.Path is not null ? $"{file.Path.Replace('\\', '/')}/{file.Filename}" : file.Filename)>
+ @if(sourceUrl is not null) {
+ <a class="nondecorated" target="_blank" href=@sourceUrl>@file.Filename</a>
+ } else {
+ @file.Filename
+ }
+ </td>
+ <td title=@file.Length>@file.Length.ToBytesSI()</td>
+ <td
+ title=@(file.Checksum + (file.ChecksumVerified ? " (verified)" : ""))
+ class=@(file.ChecksumVerified ? "verified" : null)>
+
+ @file.Checksum.Substring(0, 8)
+ </td>
+ </tr>
+ }
+ </table>
+ </div>
+ <div id="metadata-tags">
+ <p class="heading">Tags</p>
+ <hr/>
+ <MediaTagTable Media=media @ref=mediaTagTable/>
+ </div>
+ </div>
+ <div id="button-container">
+ <ButtonContainer>
+ <button @onclick=@(() => deleteDialog.Show()) class="warning" data-keyboard-shortcut="d"><u>D</u>elete</button>
+ <button @onclick=@(() => tagDialog.Show()) class="secondary" data-keyboard-shortcut="t">Add <u>T</u>ag</button>
+ <button @onclick=@(() => ocrDialog.Show()) class="secondary" data-keyboard-shortcut="o">View <u>O</u>CR</button>
+ @if(infoEditMode) {
+ <button @onclick=@(() => ApplyInfoEdit(false)) class="secondary">Cancel</button>
+ <button @onclick=@(() => ApplyInfoEdit(true))>Apply</button>
+ } else {
+ <button @onclick=@(() => InfoEditMode = true) class="secondary" data-keyboard-shortcut="e"><u>E</u>dit Info</button>
+ }
+ @if(media.IsIngest) {
+ <button @onclick=@(() => SetIngest(false)) data-keyboard-shortcut="c">Mark Tagging <u>C</u>omplete</button>
+ } else {
+ <button class="secondary" @onclick=@(() => SetIngest(true)) data-keyboard-shortcut="c">Mark Tagging In<u>c</u>omplete</button>
+ }
+ </ButtonContainer>
+ </div>
+ </div>
+ </div>
+ <div id="bottom-bar">
+ <a href="javascript:showSidebar(false);">&#x1F5BC;</a>
+ <a href="javascript:showSidebar(true);">&#x1F6C8;</a>
+ </div>
</div>
<Dialog Title="Delete this media?" @ref=deleteDialog>
diff --git a/Pages/ViewMedia.razor.css b/Pages/ViewMedia.razor.css
index 53d5eca..d5bac3e 100644
--- a/Pages/ViewMedia.razor.css
+++ b/Pages/ViewMedia.razor.css
@@ -1,7 +1,19 @@
-div#content {
- display: flex;
- align-items: start;
- height: 100%;
+div#vcontainer {
+ align-items: start;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ overflow: hidden;
+ width: 100%;
+}
+
+div#hcontainer {
+ align-items: start;
+ display: flex;
+ flex-direction: row;
+ height: 100%;
+ overflow: hidden;
+ width: 100%;
}
div#image-container {
@@ -23,9 +35,50 @@ div#image-container > img {
transform: translate(-50%, -50%);
}
+div#metadata-show-button {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ justify-content: center;
+ user-select: none;
+ width: 20px;
+}
+
+div#metadata-show-button > a::before {
+ content: "\25B6";
+}
+
+div#hcontainer.hide-metadata > div#metadata-show-button > a::before {
+ content: "\25C0" !important;
+}
+
+@media (hover: none) and (pointer: coarse) {
+ div#metadata-show-button {
+ display: none !important;
+ }
+}
+
+div#metadata-show-button > a {
+ /* TODO: Use colours from global.css */
+ background: #333;
+ border-radius: 10px 0 0 10px;
+ color: #fff;
+ display: block;
+ padding: 15px 0 15px 0;
+ text-align: center;
+}
+
+div#metadata-show-button > a:hover {
+ filter: brightness(1.5);
+}
+
+div#metadata-show-button > a:active {
+ background: #fff;
+}
+
div#metadata {
background: #333;
- box-shadow: rgba(0, 0, 0, 0.5) -10px 0px 10px;
+ box-shadow: rgba(0, 0, 0, 0.25) -10px 0px 10px;
box-sizing: border-box;
display: flex;
flex-direction: column;
@@ -38,15 +91,28 @@ div#metadata {
z-index: 90;
}
-div#metadata.hidden {
- box-shadow: none;
- margin-right: -700px;
+@media (hover: hover) and (pointer: fine) {
+ div#hcontainer.hide-metadata > div#metadata {
+ box-shadow: none;
+ margin-right: -700px;
+ }
}
-@media (max-aspect-ratio: 4/3) {
+@media (hover: none) and (pointer: coarse) {
+ div#image-container {
+ display: none;
+ }
+
div#metadata {
- box-shadow: none;
- margin-right: -700px;
+ width: 100%;
+ }
+
+ div#hcontainer.hide-metadata > div#image-container {
+ display: initial;
+ }
+
+ div#hcontainer.hide-metadata > div#metadata {
+ display: none;
}
}
@@ -54,6 +120,12 @@ div#button-container {
margin-top: auto;
}
+@media (hover: none) and (pointer: coarse) {
+ div#button-container button {
+ font-size: 8pt;
+ }
+}
+
div#metadata-container {
overflow-x: hidden;
overflow-y: auto;
@@ -93,12 +165,19 @@ table#uploaded-files th {
table#uploaded-files td {
font-family: 'Lucida Console';
- font-size: 8pt;
+ font-size: 7pt;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
+@media (hover: none) and (pointer: coarse) {
+ table#uploaded-files th,
+ table#uploaded-files td {
+ font-size: 6pt;
+ }
+}
+
table#uploaded-files td:nth-child(4) {
max-width: 170px;
}
@@ -114,3 +193,34 @@ p.heading {
p.newlines {
white-space: pre-line;
}
+
+div#bottom-bar {
+ /* TODO: Use colours from global.css */
+ align-items: center;
+ background: #141414;
+ box-shadow: rgba(0, 0, 0, 0.25) 0px -5px 5px;
+ display: none;
+ flex-direction: row;
+ width: 100%;
+}
+
+@media (hover: none) and (pointer: coarse) {
+ div#bottom-bar {
+ display: flex;
+ }
+}
+
+div#bottom-bar > a {
+ color: white;
+ display: block;
+ font-size: 18pt;
+ margin: auto;
+ padding: 7px;
+ user-select: none;
+}
+
+div#bottom-bar > a:active {
+ /* TODO: Use colours from global.css */
+ background: white;
+ color: #141414;
+}
diff --git a/Pages/_Host.cshtml b/Pages/_Host.cshtml
index 69bced8..abee742 100644
--- a/Pages/_Host.cshtml
+++ b/Pages/_Host.cshtml
@@ -7,13 +7,15 @@
<html lang="en">
<head>
<meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
<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/keyboard.js"></script>
<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>
diff --git a/Server.csproj b/Server.csproj
index 46a9ffd..a1ab5b8 100644
--- a/Server.csproj
+++ b/Server.csproj
@@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AssemblyName>HyperBooru</AssemblyName>
<RootNamespace>HyperBooru</RootNamespace>
- <AssemblyVersion>0.6.0.0</AssemblyVersion>
+ <AssemblyVersion>0.7.0.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
- <Version>0.6-alpha</Version>
+ <Version>0.7-alpha</Version>
<UserSecretsId>2907567f-4640-4581-8f4d-0977952d26bd</UserSecretsId>
</PropertyGroup>
@@ -32,16 +32,16 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.10.4" />
- <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.23" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.23">
+ <PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.11.1" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.5" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Mime-Detective" Version="25.8.1" />
- <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.11" />
- <PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.4" />
- <PackageReference Include="System.Drawing.Common" Version="8.0.23" />
+ <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.5" />
<PackageReference Include="Tesseract" Version="5.2.0" />
</ItemGroup>
diff --git a/appsettings.Development.json b/appsettings.Development.json
index 5e5d828..b3da6a4 100644
--- a/appsettings.Development.json
+++ b/appsettings.Development.json
@@ -1,4 +1,12 @@
{
+ "AllowedHosts": "*",
+ "Kestrel": {
+ "Endpoints": {
+ "Http": {
+ "Url": "http://0.0.0.0:7132"
+ }
+ }
+ },
"DetailedErrors": true,
"Logging": {
"LogLevel": {
diff --git a/wwwroot/js/mobile.js b/wwwroot/js/mobile.js
new file mode 100644
index 0000000..769f435
--- /dev/null
+++ b/wwwroot/js/mobile.js
@@ -0,0 +1,3 @@
+function toggleMobileMenu() {
+ document.getElementById("mobile-menu").classList.toggle("hidden");
+}
diff --git a/wwwroot/styles/global.css b/wwwroot/styles/global.css
index b635eb1..726c91a 100644
--- a/wwwroot/styles/global.css
+++ b/wwwroot/styles/global.css
@@ -18,7 +18,8 @@
--col-button-sec-disabled-bg: #000;
--col-button-warning: #ff4848;
--col-button-warning-hl: #ff9999;
- --col-scrollbar: #666666;
+ --col-hr: #888;
+ --col-scrollbar: #666;
--col-scrollbar-hover: #aaaaaa;
--col-switch-bg: var(--col-bg);
--col-switch-fg: #fff;
@@ -48,8 +49,10 @@ a {
text-decoration: none;
}
-a:hover {
- filter: brightness(1.5);
+@media (hover: hover) {
+ a:hover {
+ filter: brightness(1.5);
+ }
}
a::selection {
@@ -61,8 +64,10 @@ a.nondecorated {
color: #fff;
}
-a.nondecorated:hover {
- color: #999;
+@media (hover: hover) {
+ a.nondecorated:hover {
+ color: #999;
+ }
}
code {
@@ -98,8 +103,10 @@ button.warning {
background: var(--col-button-warning);
}
-button.warning:hover {
- background: var(--col-button-warning-hl);
+@media (hover: hover) {
+ button.warning:hover {
+ background: var(--col-button-warning-hl);
+ }
}
button.warning:active {
@@ -111,8 +118,10 @@ button.secondary {
background: var(--col-button-sec);
}
-button.secondary:hover {
- background: var(--col-button-sec-hl);
+@media (hover: hover) {
+ button.secondary:hover {
+ background: var(--col-button-sec-hl);
+ }
}
button.secondary:active {
@@ -125,8 +134,10 @@ button.secondary:disabled {
background: var(--col-button-sec-disabled-bg) !important;
}
-button:hover, input[type=submit]:hover {
- background: var(--col-button-pri-hl);
+@media (hover: hover) {
+ button:hover, input[type=submit]:hover {
+ background: var(--col-button-pri-hl);
+ }
}
button:active, input[type=submit]:active {
@@ -147,6 +158,13 @@ input {
height: 25px !important;
}
+/* disable hotkey underlines on mobile devices */
+@media (hover: none) and (pointer: coarse) {
+ button > u {
+ text-decoration: none !important;
+ }
+}
+
/* necessary for use inside flex containers */
hr {
width: 100%;
@@ -162,8 +180,10 @@ hr {
border-radius: 10px;
}
-::-webkit-scrollbar-thumb:hover {
- background: var(--col-scrollbar-hover);
+@media (hover: hover) {
+ ::-webkit-scrollbar-thumb:hover {
+ background: var(--col-scrollbar-hover);
+ }
}
::-webkit-scrollbar-corner {