search tags
loading placeholder items
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {}
|
||||||
}
|
})}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
Reference in New Issue
Block a user