diff options
| author | Jake Mannens <jake@asger.xyz> | 2026-04-08 03:58:07 +1000 |
|---|---|---|
| committer | Jake Mannens <jake@asger.xyz> | 2026-04-08 03:58:07 +1000 |
| commit | 23bc4d5d0e2e0e5172f539242e78cf86ddd05a92 (patch) | |
| tree | e01cfa7f8de40e7189e3b64fde6275640010e355 | |
| parent | 0d517e182dcf97e9fe46f6524f1d116ca5a94929 (diff) | |
v0.7av0.7a
| -rw-r--r-- | Dockerfile | 4 | ||||
| -rw-r--r-- | MainLayout.razor | 2 | ||||
| -rw-r--r-- | Pages/Component/Dialog.razor.css | 2 | ||||
| -rw-r--r-- | Pages/Component/MediaTagTable.razor.css | 8 | ||||
| -rw-r--r-- | Pages/Component/MobileMenu.razor | 32 | ||||
| -rw-r--r-- | Pages/Component/MobileMenu.razor.css | 46 | ||||
| -rw-r--r-- | Pages/Component/TabContainer.razor.css | 14 | ||||
| -rw-r--r-- | Pages/Component/TagSelectDialog.razor.css | 6 | ||||
| -rw-r--r-- | Pages/Component/Titlebar.razor | 29 | ||||
| -rw-r--r-- | Pages/Component/Titlebar.razor.css | 28 | ||||
| -rw-r--r-- | Pages/Gallery.razor.css | 7 | ||||
| -rw-r--r-- | Pages/TagDefinitions.razor.css | 5 | ||||
| -rw-r--r-- | Pages/Upload.razor | 2 | ||||
| -rw-r--r-- | Pages/Upload.razor.css | 25 | ||||
| -rw-r--r-- | Pages/ViewMedia.razor | 204 | ||||
| -rw-r--r-- | Pages/ViewMedia.razor.css | 134 | ||||
| -rw-r--r-- | Pages/_Host.cshtml | 4 | ||||
| -rw-r--r-- | Server.csproj | 18 | ||||
| -rw-r--r-- | appsettings.Development.json | 8 | ||||
| -rw-r--r-- | wwwroot/js/mobile.js | 3 | ||||
| -rw-r--r-- | wwwroot/styles/global.css | 46 |
21 files changed, 460 insertions, 167 deletions
@@ -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();">☰</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);">🖼</a> + <a href="javascript:showSidebar(true);">🛈</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 { |
