From bf963c4ab830fadc059ddb55e995bfb16984ecd8 Mon Sep 17 00:00:00 2001 From: CenTdemeern1 Date: Thu, 13 Feb 2025 03:24:59 +0100 Subject: [PATCH 1/5] Wip about page --- static/about.mts | 17 +++++++++++++++++ static/crossroad.html | 26 ++++++++++++++++++++++++++ static/main.css | 16 ++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 static/about.mts diff --git a/static/about.mts b/static/about.mts new file mode 100644 index 0000000..709e958 --- /dev/null +++ b/static/about.mts @@ -0,0 +1,17 @@ +import { findImageOrFail } from "./dom.mjs"; + +async function populateUser(selector: string, origin: string, userId: string) { + const avatarImage = findImageOrFail(document.body, selector); + const userData = await fetch(`https://${origin}/api/users/show`, { + method: "POST", + body: JSON.stringify({ + userId, + }) + }).then(r => r.json()); + if (userData.avatarUrl) { + avatarImage.src = userData.avatarUrl; + } +} + +populateUser("#charlotteAvatar", "eepy.moe", "9xt2s326nxev039h"); +populateUser("#kioAvatar", "kitsunes.club", "9810gvfne3"); diff --git a/static/crossroad.html b/static/crossroad.html index 8ca3031..b769c4d 100644 --- a/static/crossroad.html +++ b/static/crossroad.html @@ -11,6 +11,7 @@ +
@@ -120,6 +121,31 @@ Unchecking this is not recommended, and this option only exists for exceptional
+ +
+
+

About FeDirect

+

  (v0.1-alpha)

+
+

+ FeDirect links the Fediverse together by allowing you to create generic links that + link people to their native instance! +

+

About Nekomata

+
+
+ Charlotte's avatar + Charlotte + @CenTdemeern1@eepy.moe +
+
+ Charlotte's avatar + Kio + @Kio@kitsunes.club +
+
+
+
diff --git a/static/main.css b/static/main.css index 7667227..4781220 100644 --- a/static/main.css +++ b/static/main.css @@ -63,6 +63,10 @@ abbr[title] { /* Generic styling properties */ +.wrap-balance { + text-wrap: balance; +} + .align-start { text-align: start; } @@ -139,6 +143,14 @@ abbr[title] { min-height: 50%; } +.half-width-max { + max-width: 50%; +} + +.half-height-max { + max-height: 50%; +} + .full-width { min-width: 100%; } @@ -163,6 +175,10 @@ abbr[title] { margin-top: auto; } +.margin-none-top { + margin-top: 0; +} + .margin-large-bottom { margin-bottom: var(--large); } From 3f52ae2147f5760bb02d34e0e968a4f01d21d48f Mon Sep 17 00:00:00 2001 From: CenTdemeern1 Date: Thu, 13 Feb 2025 05:15:06 +0100 Subject: [PATCH 2/5] Populate nekomata pfps Now that there's an init function I should probably fail early on invalid knownsoftware json --- Cargo.lock | 1 + Cargo.toml | 2 +- src/api/mod.rs | 6 +++++ src/api/nekomata_avatars.rs | 49 +++++++++++++++++++++++++++++++++++++ src/main.rs | 2 ++ static/about.mts | 20 ++++++--------- 6 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 src/api/nekomata_avatars.rs diff --git a/Cargo.lock b/Cargo.lock index 202ebf4..a7c10a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1527,6 +1527,7 @@ dependencies = [ "base64", "bytes", "encoding_rs", + "futures-channel", "futures-core", "futures-util", "h2 0.4.7", diff --git a/Cargo.toml b/Cargo.toml index 95fdd5d..8f8fb7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] bytes = "1.9.0" favicon-scraper = "0.3.1" -reqwest = { version = "0.12.12", features = ["stream"] } +reqwest = { version = "0.12.12", features = ["stream", "blocking"] } rocket = { version = "0.5.1", features = ["json"] } semver = "1.0.24" serde = { version = "1.0.217", features = ["derive"] } diff --git a/src/api/mod.rs b/src/api/mod.rs index 5faff5a..6417434 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,11 +1,17 @@ use rocket::Route; pub mod instance_info; +pub mod nekomata_avatars; pub mod proxy; +pub fn init() { + nekomata_avatars::init(); +} + pub fn get_routes() -> Vec { routes![ instance_info::instance_info, + nekomata_avatars::nekomata_avatars, // Proxy is temporarily disabled as it's not needed // proxy::proxy ] diff --git a/src/api/nekomata_avatars.rs b/src/api/nekomata_avatars.rs new file mode 100644 index 0000000..6dfb8f7 --- /dev/null +++ b/src/api/nekomata_avatars.rs @@ -0,0 +1,49 @@ +use reqwest::blocking::Client; +use rocket::serde::json::Json; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::sync::OnceLock; + +#[derive(Debug, Serialize)] +pub struct Avatars { + charlotte: Option, + kio: Option, +} + +#[derive(Deserialize)] +struct MisskeyUser { + #[serde(rename = "avatarUrl")] + avatar_url: String, +} + +fn get_avatar(client: &Client, origin: &str, user_id: &str) -> Option { + let body = json!({ + "userId": user_id + }) + .to_string(); + println!("{body}"); + let user: MisskeyUser = client + .post(format!("https://{origin}/api/users/show")) + .header("content-type", "application/json") + .body(body) + .send() + .ok()? + .json() + .ok()?; + Some(user.avatar_url) +} + +pub static AVATARS: OnceLock = OnceLock::new(); + +pub fn init() { + let client = Client::new(); + let charlotte = get_avatar(&client, "eepy.moe", "9xt2s326nxev039h"); + let kio = get_avatar(&client, "kitsunes.club", "9810gvfne3"); + AVATARS.set(Avatars { charlotte, kio }).unwrap(); +} + +/// Gets (relatively) up-to-date Nekomata avatars +#[get("/nekomata_avatars")] +pub async fn nekomata_avatars() -> Json<&'static Avatars> { + Json(AVATARS.get().unwrap()) +} diff --git a/src/main.rs b/src/main.rs index c12ec48..734b6d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,6 +44,8 @@ fn route_for_unknown_instance_software(instance: &str, route: PathBuf) -> (Conte #[launch] fn rocket() -> _ { + api::init(); + rocket::build() .mount("/static", FileServer::from("static").rank(0)) .mount("/api", api::get_routes()) diff --git a/static/about.mts b/static/about.mts index 709e958..741170c 100644 --- a/static/about.mts +++ b/static/about.mts @@ -1,17 +1,11 @@ import { findImageOrFail } from "./dom.mjs"; -async function populateUser(selector: string, origin: string, userId: string) { - const avatarImage = findImageOrFail(document.body, selector); - const userData = await fetch(`https://${origin}/api/users/show`, { - method: "POST", - body: JSON.stringify({ - userId, - }) - }).then(r => r.json()); - if (userData.avatarUrl) { - avatarImage.src = userData.avatarUrl; - } +async function populateUsers(charlotteSelector: string, kioSelector: string) { + const charlotteImage = findImageOrFail(document.body, charlotteSelector); + const kioImage = findImageOrFail(document.body, kioSelector); + const { charlotte, kio } = await fetch("/api/nekomata_avatars").then(r => r.json()); + if (charlotte) charlotteImage.src = charlotte; + if (kio) kioImage.src = kio; } -populateUser("#charlotteAvatar", "eepy.moe", "9xt2s326nxev039h"); -populateUser("#kioAvatar", "kitsunes.club", "9810gvfne3"); +populateUsers("#charlotteAvatar", "#kioAvatar"); From fd63c0b12a9ad110823098e3d8c2fdea729a4eb0 Mon Sep 17 00:00:00 2001 From: CenTdemeern1 Date: Thu, 13 Feb 2025 06:06:15 +0100 Subject: [PATCH 3/5] Almost finished about dialog --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/api/{nekomata_avatars.rs => about.rs} | 9 +++- src/api/mod.rs | 7 +-- static/about.mts | 10 +++- static/crossroad.html | 24 ++++++---- static/main.css | 56 +++++++++++++++++++++-- 7 files changed, 87 insertions(+), 23 deletions(-) rename src/api/{nekomata_avatars.rs => about.rs} (88%) diff --git a/Cargo.lock b/Cargo.lock index a7c10a0..7760ec8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -346,7 +346,7 @@ dependencies = [ [[package]] name = "fedirect" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "bytes", "favicon-scraper", diff --git a/Cargo.toml b/Cargo.toml index 8f8fb7d..ed5a0ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fedirect" -version = "0.1.0" +version = "0.1.0-alpha" edition = "2021" [dependencies] diff --git a/src/api/nekomata_avatars.rs b/src/api/about.rs similarity index 88% rename from src/api/nekomata_avatars.rs rename to src/api/about.rs index 6dfb8f7..657aa5b 100644 --- a/src/api/nekomata_avatars.rs +++ b/src/api/about.rs @@ -4,6 +4,12 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use std::sync::OnceLock; +/// Gets the FeDirect version +#[get("/about/version")] +pub async fn version() -> &'static str { + env!("CARGO_PKG_VERSION") +} + #[derive(Debug, Serialize)] pub struct Avatars { charlotte: Option, @@ -21,7 +27,6 @@ fn get_avatar(client: &Client, origin: &str, user_id: &str) -> Option { "userId": user_id }) .to_string(); - println!("{body}"); let user: MisskeyUser = client .post(format!("https://{origin}/api/users/show")) .header("content-type", "application/json") @@ -43,7 +48,7 @@ pub fn init() { } /// Gets (relatively) up-to-date Nekomata avatars -#[get("/nekomata_avatars")] +#[get("/about/nekomata_avatars")] pub async fn nekomata_avatars() -> Json<&'static Avatars> { Json(AVATARS.get().unwrap()) } diff --git a/src/api/mod.rs b/src/api/mod.rs index 6417434..a3e775a 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,17 +1,18 @@ use rocket::Route; +pub mod about; pub mod instance_info; -pub mod nekomata_avatars; pub mod proxy; pub fn init() { - nekomata_avatars::init(); + about::init(); } pub fn get_routes() -> Vec { routes![ instance_info::instance_info, - nekomata_avatars::nekomata_avatars, + about::version, + about::nekomata_avatars, // Proxy is temporarily disabled as it's not needed // proxy::proxy ] diff --git a/static/about.mts b/static/about.mts index 741170c..4d96b26 100644 --- a/static/about.mts +++ b/static/about.mts @@ -1,11 +1,17 @@ -import { findImageOrFail } from "./dom.mjs"; +import { findImageOrFail, findSpanOrFail } from "./dom.mjs"; + +async function populateVersion(versionSelector: string) { + const versionParagraph = findSpanOrFail(document.body, versionSelector); + versionParagraph.innerText = await fetch("/api/about/version").then(r => r.text()); +} async function populateUsers(charlotteSelector: string, kioSelector: string) { const charlotteImage = findImageOrFail(document.body, charlotteSelector); const kioImage = findImageOrFail(document.body, kioSelector); - const { charlotte, kio } = await fetch("/api/nekomata_avatars").then(r => r.json()); + const { charlotte, kio } = await fetch("/api/about/nekomata_avatars").then(r => r.json()); if (charlotte) charlotteImage.src = charlotte; if (kio) kioImage.src = kio; } +populateVersion("#version"); populateUsers("#charlotteAvatar", "#kioAvatar"); diff --git a/static/crossroad.html b/static/crossroad.html index b769c4d..3047ebd 100644 --- a/static/crossroad.html +++ b/static/crossroad.html @@ -121,28 +121,32 @@ Unchecking this is not recommended, and this option only exists for exceptional - +

About FeDirect

-

  (v0.1-alpha)

+

  (v)

FeDirect links the Fediverse together by allowing you to create generic links that link people to their native instance!

+ Source code

About Nekomata

-
- Charlotte's avatar - Charlotte - @CenTdemeern1@eepy.moe +
+ Charlotte's avatar +

Charlotte

+ @CenTdemeern1@eepy.moe +

Programming, design

-
- Charlotte's avatar - Kio - @Kio@kitsunes.club +
+ Charlotte's avatar +

Kio

+ @Kio@kitsunes.club +

Funding, hosting, design

+ Nekomata Logo
diff --git a/static/main.css b/static/main.css index 4781220..1441b99 100644 --- a/static/main.css +++ b/static/main.css @@ -147,10 +147,6 @@ abbr[title] { max-width: 50%; } -.half-height-max { - max-height: 50%; -} - .full-width { min-width: 100%; } @@ -163,6 +159,16 @@ abbr[title] { height: var(--medium); } +.xl-size { + width: var(--xl); + height: var(--xl); +} + +.xl-size-max { + max-width: var(--xl); + max-height: var(--xl); +} + .separator-bottom { border-bottom: solid 1px var(--transparent-black); } @@ -191,6 +197,13 @@ abbr[title] { aspect-ratio: 1; } +.absolute-centered { + position: absolute; + top: 50%; + left: 50%; + translate: -50% -50%; +} + /* Specialized elements */ .iconContainer { @@ -222,6 +235,31 @@ abbr[title] { margin-top: min(var(--xl), 6vh); } +.circlingMembers { + position: relative; + width: 24em; + height: 24em; + animation-play-state: running; + transition: animation-play-state 1s; +} + +.circlingMembers:hover { + animation-play-state: paused; +} + +.member { + animation: 8s infinite linear orbit; + animation-play-state: inherit; +} + +.member.charlotte { + --orbit-translate-x: 8em; +} + +.member.kio { + --orbit-translate-x: -8em; +} + /* Animations */ .pulse-red { @@ -236,4 +274,14 @@ abbr[title] { 100% { box-shadow: 0px 0px 20px var(--red); } +} + +@keyframes orbit { + 0% { + transform: rotate(0deg) translateX(var(--orbit-translate-x)) rotate(0deg); + } + + 100% { + transform: rotate(360deg) translateX(var(--orbit-translate-x)) rotate(-360deg); + } } \ No newline at end of file From dd53074458fad990454c1e4466f721525e9adb26 Mon Sep 17 00:00:00 2001 From: CenTdemeern1 Date: Thu, 13 Feb 2025 06:53:11 +0100 Subject: [PATCH 4/5] Open/close about dialog --- static/about.mts | 28 +++++++++++++++++++--------- static/crossroad.html | 4 +++- static/dom.mts | 7 +++++++ 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/static/about.mts b/static/about.mts index 4d96b26..6aaa2d0 100644 --- a/static/about.mts +++ b/static/about.mts @@ -1,17 +1,27 @@ -import { findImageOrFail, findSpanOrFail } from "./dom.mjs"; +import { Dialog } from "./dialog.mjs"; +import { findAnchorOrFail, findButtonOrFail, findDialogOrFail, findImageOrFail, findSpanOrFail } from "./dom.mjs"; -async function populateVersion(versionSelector: string) { - const versionParagraph = findSpanOrFail(document.body, versionSelector); +const openButton = findAnchorOrFail(document.body, "#aboutLink"); +const dialog = findDialogOrFail(document.body, "#about") +const versionParagraph = findSpanOrFail(dialog, "#version"); +const charlotteImage = findImageOrFail(dialog, "#charlotteAvatar"); +const kioImage = findImageOrFail(dialog, "#kioAvatar"); +const closeButton = findButtonOrFail(dialog, ".close"); + +const aboutDialog = new Dialog(dialog); + +openButton.addEventListener("click", e => aboutDialog.open()); +closeButton.addEventListener("click", e => aboutDialog.close()); + +populateVersion(); +populateUsers(); + +async function populateVersion() { versionParagraph.innerText = await fetch("/api/about/version").then(r => r.text()); } -async function populateUsers(charlotteSelector: string, kioSelector: string) { - const charlotteImage = findImageOrFail(document.body, charlotteSelector); - const kioImage = findImageOrFail(document.body, kioSelector); +async function populateUsers() { const { charlotte, kio } = await fetch("/api/about/nekomata_avatars").then(r => r.json()); if (charlotte) charlotteImage.src = charlotte; if (kio) kioImage.src = kio; } - -populateVersion("#version"); -populateUsers("#charlotteAvatar", "#kioAvatar"); diff --git a/static/crossroad.html b/static/crossroad.html index 3047ebd..4fef273 100644 --- a/static/crossroad.html +++ b/static/crossroad.html @@ -17,7 +17,8 @@

FeDirect

-

  By Nekomata

+

  By Nekomata

@@ -148,6 +149,7 @@ Unchecking this is not recommended, and this option only exists for exceptional Nekomata Logo
+ diff --git a/static/dom.mts b/static/dom.mts index a4bf983..e879ec8 100644 --- a/static/dom.mts +++ b/static/dom.mts @@ -1,6 +1,13 @@ // I would've LOVED to use generics for this but unfortunately that's not possible. // Type safety, but at what cost... >~< thanks TypeScript +export function findAnchorOrFail(on: Element, selector: string): HTMLAnchorElement { + const element = on.querySelector(selector); + if (!(element instanceof HTMLAnchorElement)) + throw new Error(`${selector} isn't an a`); + return element; +} + export function findDivOrFail(on: Element, selector: string): HTMLDivElement { const element = on.querySelector(selector); if (!(element instanceof HTMLDivElement)) From d72ebcdbef952ff527e7e910ec16f377626e66bc Mon Sep 17 00:00:00 2001 From: CenTdemeern1 Date: Thu, 13 Feb 2025 06:58:17 +0100 Subject: [PATCH 5/5] Add about dialog to config as well --- static/config.html | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/static/config.html b/static/config.html index ac206fb..fb20253 100644 --- a/static/config.html +++ b/static/config.html @@ -11,12 +11,14 @@ +

FeDirect

-

  By Nekomata

+

  By Nekomata

@@ -109,6 +111,36 @@ Unchecking this is not recommended, and this option only exists for exceptional
+ +
+
+

About FeDirect

+

  (v)

+
+

+ FeDirect links the Fediverse together by allowing you to create generic links that + link people to their native instance! +

+ Source code +

About Nekomata

+
+
+ Charlotte's avatar +

Charlotte

+ @CenTdemeern1@eepy.moe +

Programming, design

+
+
+ Charlotte's avatar +

Kio

+ @Kio@kitsunes.club +

Funding, hosting, design

+
+ Nekomata Logo +
+ +
+