Add about section #6
10 changed files with 231 additions and 5 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -346,7 +346,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fedirect"
|
||||
version = "0.1.0"
|
||||
version = "0.1.0-alpha"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"favicon-scraper",
|
||||
|
@ -1527,6 +1527,7 @@ dependencies = [
|
|||
"base64",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.4.7",
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
[package]
|
||||
name = "fedirect"
|
||||
version = "0.1.0"
|
||||
version = "0.1.0-alpha"
|
||||
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"] }
|
||||
|
|
54
src/api/about.rs
Normal file
54
src/api/about.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use reqwest::blocking::Client;
|
||||
use rocket::serde::json::Json;
|
||||
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<String>,
|
||||
kio: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MisskeyUser {
|
||||
#[serde(rename = "avatarUrl")]
|
||||
avatar_url: String,
|
||||
}
|
||||
|
||||
fn get_avatar(client: &Client, origin: &str, user_id: &str) -> Option<String> {
|
||||
let body = json!({
|
||||
"userId": user_id
|
||||
})
|
||||
.to_string();
|
||||
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<Avatars> = 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("/about/nekomata_avatars")]
|
||||
pub async fn nekomata_avatars() -> Json<&'static Avatars> {
|
||||
Json(AVATARS.get().unwrap())
|
||||
}
|
|
@ -1,11 +1,18 @@
|
|||
use rocket::Route;
|
||||
|
||||
pub mod about;
|
||||
pub mod instance_info;
|
||||
pub mod proxy;
|
||||
|
||||
pub fn init() {
|
||||
about::init();
|
||||
}
|
||||
|
||||
pub fn get_routes() -> Vec<Route> {
|
||||
routes![
|
||||
instance_info::instance_info,
|
||||
about::version,
|
||||
about::nekomata_avatars,
|
||||
// Proxy is temporarily disabled as it's not needed
|
||||
// proxy::proxy
|
||||
]
|
||||
|
|
|
@ -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())
|
||||
|
|
27
static/about.mts
Normal file
27
static/about.mts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { Dialog } from "./dialog.mjs";
|
||||
import { findAnchorOrFail, findButtonOrFail, findDialogOrFail, findImageOrFail, findSpanOrFail } from "./dom.mjs";
|
||||
|
||||
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() {
|
||||
const { charlotte, kio } = await fetch("/api/about/nekomata_avatars").then(r => r.json());
|
||||
if (charlotte) charlotteImage.src = charlotte;
|
||||
if (kio) kioImage.src = kio;
|
||||
}
|
|
@ -11,12 +11,14 @@
|
|||
|
||||
<body>
|
||||
<script type="module" src="/static/config.mjs"></script>
|
||||
<script type="module" src="/static/about.mjs"></script>
|
||||
<div class="flex-vcenter">
|
||||
<dialog id="mainDialog" class="half-width half-height">
|
||||
<header class="separator-bottom margin-large-bottom">
|
||||
<div class="flex-row">
|
||||
<h1>FeDirect</h1>
|
||||
<p class="margin-auto-top">  By Nekomata</p>
|
||||
<p class="margin-auto-top">  <a id="aboutLink" href="#"
|
||||
title="About FeDirect & Nekomata">By Nekomata</a></p>
|
||||
</div>
|
||||
<img src="/static/nekomata_small.png" alt="Nekomata Logo" class="logo" />
|
||||
</header>
|
||||
|
@ -109,6 +111,36 @@ Unchecking this is not recommended, and this option only exists for exceptional
|
|||
<button type="reset" class="close">Cancel</button>
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog id="about" class="half-width-max half-height">
|
||||
<center>
|
||||
<div class="flex-row wfit-content">
|
||||
<h1>About FeDirect</h1>
|
||||
<p class="margin-auto-top">  (v<span id="version"></span>)</p>
|
||||
</div>
|
||||
<p class="margin-none-top wrap-balance">
|
||||
FeDirect links the Fediverse together by allowing you to create generic links that
|
||||
link people to their native instance!
|
||||
</p>
|
||||
<a href="https://kitsunes.dev/Nekomata/FeDirect">Source code</a>
|
||||
<h2>About Nekomata</h2>
|
||||
<div class="circlingMembers">
|
||||
<center class="absolute-centered member charlotte align-content-center flex-column">
|
||||
<img id="charlotteAvatar" class="xl-size" alt="Charlotte's avatar" />
|
||||
<p class="margin-none">Charlotte</p>
|
||||
<a href="https://eepy.moe/@CenTdemeern1" class="margin-none">@CenTdemeern1@eepy.moe</a>
|
||||
<p class="margin-none">Programming, design</p>
|
||||
</center>
|
||||
<center class="absolute-centered member kio align-content-center flex-column">
|
||||
<img id="kioAvatar" class="xl-size" alt="Charlotte's avatar" />
|
||||
<p class="margin-none">Kio</p>
|
||||
<a href="https://kitsunes.club/@Kio" class="margin-none">@Kio@kitsunes.club</a>
|
||||
<p class="margin-none">Funding, hosting, design</p>
|
||||
</center>
|
||||
<img src="/static/nekomata_small.png" alt="Nekomata Logo" class="absolute-centered xl-size-max" />
|
||||
</div>
|
||||
<button class="close">Close</button>
|
||||
</center>
|
||||
</dialog>
|
||||
<dialog id="spinner"><span class="spinner"></span></dialog>
|
||||
</body>
|
||||
|
||||
|
|
|
@ -11,12 +11,14 @@
|
|||
|
||||
<body>
|
||||
<script type="module" src="/static/crossroad.mjs"></script>
|
||||
<script type="module" src="/static/about.mjs"></script>
|
||||
<div class="flex-vcenter">
|
||||
<dialog id="mainDialog" class="half-width half-height">
|
||||
<header class="separator-bottom margin-large-bottom">
|
||||
<div class="flex-row">
|
||||
<h1>FeDirect</h1>
|
||||
<p class="margin-auto-top">  By Nekomata</p>
|
||||
<p class="margin-auto-top">  <a id="aboutLink" href="#"
|
||||
title="About FeDirect & Nekomata">By Nekomata</a></p>
|
||||
</div>
|
||||
<img src="/static/nekomata_small.png" alt="Nekomata Logo" class="logo" />
|
||||
</header>
|
||||
|
@ -120,6 +122,36 @@ Unchecking this is not recommended, and this option only exists for exceptional
|
|||
<button type="reset" class="close">Cancel</button>
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog id="about" class="half-width-max half-height">
|
||||
<center>
|
||||
<div class="flex-row wfit-content">
|
||||
<h1>About FeDirect</h1>
|
||||
<p class="margin-auto-top">  (v<span id="version"></span>)</p>
|
||||
</div>
|
||||
<p class="margin-none-top wrap-balance">
|
||||
FeDirect links the Fediverse together by allowing you to create generic links that
|
||||
link people to their native instance!
|
||||
</p>
|
||||
<a href="https://kitsunes.dev/Nekomata/FeDirect">Source code</a>
|
||||
<h2>About Nekomata</h2>
|
||||
<div class="circlingMembers">
|
||||
<center class="absolute-centered member charlotte align-content-center flex-column">
|
||||
<img id="charlotteAvatar" class="xl-size" alt="Charlotte's avatar" />
|
||||
<p class="margin-none">Charlotte</p>
|
||||
<a href="https://eepy.moe/@CenTdemeern1" class="margin-none">@CenTdemeern1@eepy.moe</a>
|
||||
<p class="margin-none">Programming, design</p>
|
||||
</center>
|
||||
<center class="absolute-centered member kio align-content-center flex-column">
|
||||
<img id="kioAvatar" class="xl-size" alt="Charlotte's avatar" />
|
||||
<p class="margin-none">Kio</p>
|
||||
<a href="https://kitsunes.club/@Kio" class="margin-none">@Kio@kitsunes.club</a>
|
||||
<p class="margin-none">Funding, hosting, design</p>
|
||||
</center>
|
||||
<img src="/static/nekomata_small.png" alt="Nekomata Logo" class="absolute-centered xl-size-max" />
|
||||
</div>
|
||||
<button class="close">Close</button>
|
||||
</center>
|
||||
</dialog>
|
||||
<dialog id="spinner"><span class="spinner"></span></dialog>
|
||||
</body>
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -63,6 +63,10 @@ abbr[title] {
|
|||
|
||||
/* Generic styling properties */
|
||||
|
||||
.wrap-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.align-start {
|
||||
text-align: start;
|
||||
}
|
||||
|
@ -139,6 +143,10 @@ abbr[title] {
|
|||
min-height: 50%;
|
||||
}
|
||||
|
||||
.half-width-max {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
@ -151,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);
|
||||
}
|
||||
|
@ -163,6 +181,10 @@ abbr[title] {
|
|||
margin-top: auto;
|
||||
}
|
||||
|
||||
.margin-none-top {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.margin-large-bottom {
|
||||
margin-bottom: var(--large);
|
||||
}
|
||||
|
@ -175,6 +197,13 @@ abbr[title] {
|
|||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.absolute-centered {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
translate: -50% -50%;
|
||||
}
|
||||
|
||||
/* Specialized elements */
|
||||
|
||||
.iconContainer {
|
||||
|
@ -206,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 {
|
||||
|
@ -221,3 +275,13 @@ abbr[title] {
|
|||
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);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue