search tags

loading placeholder items
This commit is contained in:
2025-07-03 21:54:29 -04:00
parent 2a0907cf0d
commit cf55a7d47b
9 changed files with 255 additions and 211 deletions

View File

@@ -1,37 +1,37 @@
[package] [package]
name = "aoba-client" name = "aoba-client"
version = "0.1.0" version = "0.1.0"
authors = ["Amatsugu <khamraj@kaisei.app>"] authors = ["Amatsugu <khamraj@kaisei.app>"]
edition = "2024" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
dioxus = { version = "0.6.0", features = ["router"] } dioxus = { version = "0.6.0", features = ["router"] }
serde = "1.0.219" serde = "1.0.219"
serde_repr = "0.1.20" serde_repr = "0.1.20"
tonic = { version = "*", default-features = false, features = [ tonic = { version = "*", default-features = false, features = [
"codegen", "codegen",
"prost", "prost",
] } ] }
prost = "0.13" prost = "0.13"
tonic-web-wasm-client = "0.7" tonic-web-wasm-client = "0.7"
web-sys = { version = "0.3.77", features = ["Storage", "Window"] } web-sys = { version = "0.3.77", features = ["Storage", "Window"] }
[build-dependencies] [build-dependencies]
tonic-build = { version = "*", default-features = false, features = ["prost"] } tonic-build = { version = "*", default-features = false, features = ["prost"] }
[features] [features]
default = ["web"] default = ["web"]
web = ["dioxus/web"] web = ["dioxus/web"]
[profile] [profile]
[profile.wasm-dev] [profile.wasm-dev]
inherits = "dev" inherits = "dev"
opt-level = 1 opt-level = 1
[profile.server-dev] [profile.server-dev]
inherits = "dev" inherits = "dev"
[profile.android-dev] [profile.android-dev]
inherits = "dev" inherits = "dev"

View File

@@ -1,151 +1,154 @@
@import "mixins"; @import "mixins";
@import "colors"; @import "colors";
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
:root { :root {
background-color: $mainBGColor; background-color: $mainBGColor;
color: $mainTextColor; color: $mainTextColor;
box-sizing: border-box; box-sizing: border-box;
font-family: "Noto Sans", sans-serif; font-family: "Noto Sans", sans-serif;
font-optical-sizing: auto; font-optical-sizing: auto;
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
font-variation-settings: "wdth" 100; font-variation-settings: "wdth" 100;
} }
.stickyTop { .stickyTop {
top: 0; top: 0;
position: sticky; position: sticky;
} }
body { body {
padding: 0; padding: 0;
margin: 0; margin: 0;
} }
#main:has(#content) { #main:has(#content) {
display: grid; display: grid;
grid-template-columns: $navBarSize 1fr; grid-template-columns: $navBarSize 1fr;
grid-template-areas: "Nav Content"; grid-template-areas: "Nav Content";
} }
#content { #content {
grid-area: Content; grid-area: Content;
overflow-x: hidden; overflow-x: hidden;
padding: 10px; padding: 10px;
/* margin-left: $navBarSize; */ /* margin-left: $navBarSize; */
} }
$mediaItemSize: 300px; $mediaItemSize: 300px;
.mediaGrid { .mediaGrid {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
gap: 10px; gap: 10px;
margin: 10px 0; margin: 10px 0;
.mediaItem { .mediaItem {
width: $mediaItemSize; width: $mediaItemSize;
height: $mediaItemSize; height: $mediaItemSize;
overflow: hidden; overflow: hidden;
display: grid; display: grid;
grid-template-columns: $mediaItemSize; grid-template-columns: $mediaItemSize;
grid-template-areas: "A"; grid-template-areas: "A";
box-shadow: 0 0 2px #000; box-shadow: 0 0 2px #000;
color: $mainTextColor; color: $mainTextColor;
text-decoration: none; text-decoration: none;
transition: transition:
transform 0.25s ease-out, transform 0.25s ease-out,
box-shadow 0.25s ease-out; box-shadow 0.25s ease-out;
> * { > * {
grid-area: A; grid-area: A;
} }
img { img {
aspect-ratio: 1; aspect-ratio: 1;
object-fit: cover; object-fit: cover;
width: 100%; width: 100%;
object-position: center; object-position: center;
background-color: $invertTextColor; background-color: $invertTextColor;
border: 0; border: 0;
outline: none; outline: none;
} }
.info { .info {
align-self: end; align-self: end;
backdrop-filter: blur(20px) brightness(0.5); backdrop-filter: blur(20px) brightness(0.5);
transition: transform 0.25s ease-out; transition: transform 0.25s ease-out;
transform: translateY(100%); transform: translateY(100%);
padding: 2px; padding: 2px;
.name { .name {
text-align: center; text-align: center;
width: 100%; width: 100%;
display: block; display: block;
overflow: hidden; overflow: hidden;
} }
.details { .details {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
} }
&:hover { &:hover {
transform: scale(110%) translateZ(2px); transform: scale(110%) translateZ(2px);
box-shadow: 0 0 8px #000; box-shadow: 0 0 8px #000;
.info { .info {
transform: translateY(0%); transform: translateY(0%);
} }
} }
}
} &.placeholder {
}
#main:has(#centralModal) { }
display: grid; }
place-items: center;
height: 100dvh; #main:has(#centralModal) {
width: 100dvw; display: grid;
} place-items: center;
height: 100dvh;
#centralModal { width: 100dvw;
display: flex; }
flex-direction: column;
} #centralModal {
display: flex;
form { flex-direction: column;
display: flex; }
flex-direction: column;
gap: 5px; form {
} display: flex;
flex-direction: column;
.notif { gap: 5px;
background-color: red; }
display: grid;
grid-template-columns: 50px 1fr; .notif {
height: 50px; background-color: red;
border-radius: 20px; display: grid;
border-bottom-left-radius: 0; grid-template-columns: 50px 1fr;
border-top-right-radius: 0; height: 50px;
border-radius: 20px;
.icon { border-bottom-left-radius: 0;
padding: 10px; border-top-right-radius: 0;
}
.icon {
.message { padding: 10px;
padding: 10px; }
align-self: center;
} .message {
} padding: 10px;
align-self: center;
.codeSelect { }
line-break: anywhere; }
white-space: pre-wrap;
background-color: $featureColor; .codeSelect {
padding: 5px; line-break: anywhere;
user-select: all; white-space: pre-wrap;
} background-color: $featureColor;
padding: 5px;
user-select: all;
}

View File

@@ -52,7 +52,7 @@ pub fn MediaGrid(props: MediaGridProps) -> Element {
div { div {
class: "mediaGrid", class: "mediaGrid",
{result.items.iter().map(|itm| rsx!{ {result.items.iter().map(|itm| rsx!{
MediaItem { item: itm.clone() } MediaItem { item: Some(itm.clone()) }
})}, })},
} }
}, },
@@ -68,9 +68,9 @@ pub fn MediaGrid(props: MediaGridProps) -> Element {
None => rsx! { None => rsx! {
div{ div{
class: "mediaGrid", class: "mediaGrid",
div { {(0..50).map(|_| rsx!{
"Loading..." MediaItem {}
} })}
} }
}, },
} }

View File

@@ -4,26 +4,41 @@ use crate::{HOST, rpc::aoba::MediaModel};
#[derive(PartialEq, Clone, Props)] #[derive(PartialEq, Clone, Props)]
pub struct MediaItemProps { pub struct MediaItemProps {
pub item: MediaModel, pub item: Option<MediaModel>,
} }
#[component] #[component]
pub fn MediaItem(props: MediaItemProps) -> Element { pub fn MediaItem(props: MediaItemProps) -> Element {
let mtype = props.item.media_type().as_str_name(); if let Some(item) = props.item {
let filename = props.item.file_name; let mtype = item.media_type().as_str_name();
let id = props.item.media_id.unwrap().value; let filename = item.file_name;
let id = item.media_id.unwrap().value;
let src = format!("{HOST}/m/thumb/{id}"); let src = format!("{HOST}/m/thumb/{id}");
rsx! { return rsx! {
a { class: "mediaItem", href: "{HOST}/m/{id}", target: "_blank", a { class: "mediaItem", href: "{HOST}/m/{id}", target: "_blank",
img { src } img { src }
span { class: "info", span { class: "info",
span { class: "name", "{filename}" } span { class: "name", "{filename}" }
span { class: "details", span { class: "details",
span { "{mtype}" } span { "{mtype}" }
span { "{props.item.view_count}" } span { "{item.view_count}" }
}
} }
} }
} };
} else {
return rsx! {
div { class: "mediaItem placeholder",
img { },
span { class: "info",
span { class: "name" }
span { class: "details",
span { }
span { }
}
}
}
};
} }
} }

View File

@@ -9,13 +9,14 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="FFMpegCore" Version="5.2.0" /> <PackageReference Include="FFMpegCore" Version="5.2.0" />
<PackageReference Include="Isopoh.Cryptography.Argon2" Version="2.0.0" /> <PackageReference Include="Isopoh.Cryptography.Argon2" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.5" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.6" />
<PackageReference Include="MaybeError" Version="1.1.0" /> <PackageReference Include="MaybeError" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.5" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.6" />
<PackageReference Include="MongoDB.Driver" Version="3.4.0" /> <PackageReference Include="MongoDB.Driver" Version="3.4.0" />
<PackageReference Include="MongoDB.Driver.Core.Extensions.DiagnosticSources" Version="2.1.0" /> <PackageReference Include="MongoDB.Driver.Core.Extensions.DiagnosticSources" Version="2.1.0" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.6" /> <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.6" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.11.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.12.1" />
<PackageReference Include="ZLinq" Version="1.4.12" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -15,6 +15,7 @@ public class Media
public int ViewCount { get; set; } public int ViewCount { get; set; }
public ObjectId Owner { get; set; } public ObjectId Owner { get; set; }
public DateTime UploadDate { get; set; } public DateTime UploadDate { get; set; }
public string[] Tags { get; set; } = [];
public static readonly Dictionary<string, MediaType> KnownTypes = new() public static readonly Dictionary<string, MediaType> KnownTypes = new()
@@ -66,6 +67,7 @@ public class Media
Owner = owner; Owner = owner;
Id = ObjectId.GenerateNewId(); Id = ObjectId.GenerateNewId();
UploadDate = DateTime.UtcNow; UploadDate = DateTime.UtcNow;
Tags = DeriveTags(filename);
} }
public string GetMediaUrl() public string GetMediaUrl()
@@ -85,6 +87,14 @@ public class Media
else else
return MediaType.Raw; return MediaType.Raw;
} }
public static string[] DeriveTags(string filename)
{
return filename.Split('_')
.SelectMany(v => v.Split('-'))
.SelectMany(v => v.Split(' '))
.ToArray();
}
} }
public enum MediaType public enum MediaType

View File

@@ -17,7 +17,9 @@ public class AobaIndexCreationService(IMongoDatabase db): BackgroundService
{ {
BsonSerializer.RegisterSerializer(new EnumSerializer<ThumbnailSize>(BsonType.String)); BsonSerializer.RegisterSerializer(new EnumSerializer<ThumbnailSize>(BsonType.String));
var textKeys = Builders<Media>.IndexKeys var textKeys = Builders<Media>.IndexKeys
.Text(m => m.Filename); .Text(m => m.Filename)
.Text(m => m.Ext)
.Text(m => m.Tags);
var textModel = new CreateIndexModel<Media>(textKeys, new CreateIndexOptions var textModel = new CreateIndexModel<Media>(textKeys, new CreateIndexOptions
{ {

View File

@@ -95,4 +95,17 @@ public class AobaService(IMongoDatabase db)
//ignore if file was not found //ignore if file was not found
} }
} }
public async Task DeriveTagsAsync(CancellationToken cancellationToken = default)
{
var mediaItems = await _media.Find(Builders<Media>.Filter.Exists(m => m.Tags, false))
.ToListAsync(cancellationToken);
Console.WriteLine($"Derving Tag for {mediaItems.Count} items");
foreach (var mediaItem in mediaItems)
{
mediaItem.Tags = Media.DeriveTags(mediaItem.Filename);
await _media.UpdateOneAsync(m => m.Id == mediaItem.Id, Builders<Media>.Update.Set(m => m.Tags, mediaItem.Tags), null, cancellationToken);
}
Console.WriteLine("All Tags Derived");
}
} }

View File

@@ -18,7 +18,7 @@
<PackageReference Include="Isopoh.Cryptography.Argon2" Version="2.0.0" /> <PackageReference Include="Isopoh.Cryptography.Argon2" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.6" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.6" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.12.1" /> <PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.12.1" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.2" /> <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.22.1" />
<PackageReference Include="MimeTypesMap" Version="1.0.9" /> <PackageReference Include="MimeTypesMap" Version="1.0.9" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" /> <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.9.0-beta.2" /> <PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.9.0-beta.2" />