Compare commits

..

7 commits

Author SHA1 Message Date
2eabf66c73 leptos: begone 2024-11-14 07:35:42 -08:00
a91e66fd70 askama: link list view 2024-11-14 07:35:42 -08:00
ac1699ab39 askama: link view 2024-11-14 07:35:42 -08:00
e693115f10 askama: switch to main
I need 0.13's fragment feature
2024-11-14 07:35:42 -08:00
c5185fdcbe askama: welcome view 2024-11-14 07:35:42 -08:00
2a6d62d74b askama: sprettier upport 2024-11-14 07:35:42 -08:00
c5b4ca15fc askama: add dep 2024-11-14 07:35:42 -08:00
21 changed files with 325 additions and 1235 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
/target /target
.DS_Store .DS_Store
/.cache /.cache
/node_modules

11
.prettierrc Normal file
View file

@ -0,0 +1,11 @@
{
"plugins": ["prettier-plugin-jinja-template"],
"overrides": [
{
"files": ["*.html"],
"options": {
"parser": "jinja-template"
}
}
]
}

1118
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -27,15 +27,6 @@ bincode = "1.3.3"
chrono = { version = "0.4.24", features = ["serde"] } chrono = { version = "0.4.24", features = ["serde"] }
futures = "0.3.28" futures = "0.3.28"
headers = "0.4.0" headers = "0.4.0"
leptos = { version = "0.6.14", features = [
"ssr",
"nightly",
"tracing",
"default-tls",
"experimental-islands",
] }
leptos_meta = { version = "0.6.14", features = ["ssr"] }
leptos_router = { version = "0.6.14", features = ["ssr"] }
rand = { version = "0.8.5", features = ["small_rng"] } rand = { version = "0.8.5", features = ["small_rng"] }
reqwest = { version = "0.12.7", features = ["json", "native-tls", "blocking"] } reqwest = { version = "0.12.7", features = ["json", "native-tls", "blocking"] }
sanitize-filename-reader-friendly = "2.2.1" sanitize-filename-reader-friendly = "2.2.1"
@ -47,3 +38,11 @@ tower = { version = "0.5.0", features = ["util"] }
tower-http = { version = "0.5.0", features = ["fs", "trace", "limit"] } tower-http = { version = "0.5.0", features = ["fs", "trace", "limit"] }
tracing = "0.1.37" tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
# I want to use askama's block feature, this requries unreleased 0.13
[dependencies.askama]
git = "https://github.com/djc/askama.git"
features = ["with-axum"]
[dependencies.askama_axum]
git = "https://github.com/djc/askama.git"

39
package-lock.json generated Normal file
View file

@ -0,0 +1,39 @@
{
"name": "nyazoom",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"prettier": "^3.3.3",
"prettier-plugin-jinja-template": "^2.0.0"
}
},
"node_modules/prettier": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-plugin-jinja-template": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-jinja-template/-/prettier-plugin-jinja-template-2.0.0.tgz",
"integrity": "sha512-REZDAcZuOUvMDaPS47/GNRLKvbxh9DO9euXhWA7gJGqTLGzHPK2Z841F8I4bxsR7e2lqnHezkQ8GcWaKekKBVQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"prettier": "^3.0.0"
}
}
}
}

6
package.json Normal file
View file

@ -0,0 +1,6 @@
{
"devDependencies": {
"prettier": "^3.3.3",
"prettier-plugin-jinja-template": "^2.0.0"
}
}

View file

@ -1,7 +1,7 @@
use axum::{ use axum::{
extract::{ConnectInfo, Request}, extract::{ConnectInfo, Request},
middleware::{self, Next}, middleware::{self, Next},
response::{Html, IntoResponse}, response::IntoResponse,
routing::get, routing::get,
Router, Router,
}; };
@ -12,7 +12,7 @@ use std::{io, net::SocketAddr};
use nyazoom::*; use nyazoom::*;
use util::{headers::ForwardedFor, logging, ssr, sweeper}; use util::{headers::ForwardedFor, logging, sweeper};
#[tokio::main] #[tokio::main]
async fn main() -> io::Result<()> { async fn main() -> io::Result<()> {
@ -44,9 +44,8 @@ async fn main() -> io::Result<()> {
async fn welcome() -> impl IntoResponse { async fn welcome() -> impl IntoResponse {
let fact = views::get_cat_fact().await; let fact = views::get_cat_fact().await;
Html(ssr::render(move || {
leptos::view! { <WelcomePage fact /> } views::askama::WelcomeTemplate { fact }
}))
} }
async fn log_source( async fn log_source(

View file

@ -6,7 +6,7 @@ use axum::{
}; };
use reqwest::StatusCode; use reqwest::StatusCode;
use crate::{util::ssr, AppState, AsyncRemoveRecord, DownloadLinkPage}; use crate::{askama::DownloadLinkTemplate, AppState, AsyncRemoveRecord};
pub fn get_link_router() -> Router<AppState> { pub fn get_link_router() -> Router<AppState> {
// Link pages // Link pages
@ -18,7 +18,7 @@ pub fn get_link_router() -> Router<AppState> {
async fn link( async fn link(
axum::extract::Path(id): axum::extract::Path<String>, axum::extract::Path(id): axum::extract::Path<String>,
State(mut state): State<AppState>, State(mut state): State<AppState>,
) -> Result<Html<String>, Redirect> { ) -> Result<impl IntoResponse, Redirect> {
{ {
let mut records = state.records.lock().await; let mut records = state.records.lock().await;
@ -26,10 +26,10 @@ async fn link(
.get_mut(&id) .get_mut(&id)
.filter(|record| record.can_be_downloaded()) .filter(|record| record.can_be_downloaded())
{ {
return Ok(Html(ssr::render({ return Ok(DownloadLinkTemplate {
let record = record.clone(); id,
|| leptos::view! { <DownloadLinkPage id record /> } record: record.clone(),
}))); });
} }
} }

View file

@ -1,12 +1,6 @@
use axum::{ use axum::{extract::State, response::IntoResponse, routing::get, Json, Router};
extract::State,
response::{Html, IntoResponse},
routing::get,
Json, Router,
};
use leptos::CollectView;
use crate::{util::ssr, AppState, HtmxPage}; use crate::{askama::LinkListTemplate, AppState};
pub fn get_records_router() -> Router<AppState> { pub fn get_records_router() -> Router<AppState> {
// Records views // Records views
@ -24,34 +18,6 @@ pub(crate) async fn records(State(state): State<AppState>) -> impl IntoResponse
pub async fn records_links(State(state): State<AppState>) -> impl IntoResponse { pub async fn records_links(State(state): State<AppState>) -> impl IntoResponse {
let records = state.records.lock().await.clone(); let records = state.records.lock().await.clone();
let records_list_view = records let record_keys: Vec<String> = records.keys().cloned().collect();
.keys() LinkListTemplate { record_keys }
.map(|key| {
leptos::view! {
<li class="link-wrapper">
<a href="/link/{key}">{key}</a>
<button
style="margin-left: 1em;"
hx-target="closest .link-wrapper"
hx-swap="outerHTML"
hx-delete="/link/{key}"
>
"X"
</button>
</li>
}
})
.collect_view();
Html(ssr::render(move || {
leptos::view! {
<HtmxPage>
<div class="form-wrapper">
<div class="column-container">
<ul>{records_list_view}</ul>
</div>
</div>
</HtmxPage>
}
}))
} }

View file

@ -1,5 +1,6 @@
use std::path::Path; use std::path::Path;
use askama::Template;
use async_zip::{base::write::ZipFileWriter, Compression, ZipEntryBuilder}; use async_zip::{base::write::ZipFileWriter, Compression, ZipEntryBuilder};
use axum::{ use axum::{
extract::{DefaultBodyLimit, Multipart, State}, extract::{DefaultBodyLimit, Multipart, State},
@ -14,11 +15,7 @@ use tokio::io;
use tokio_util::{compat::FuturesAsyncWriteCompatExt, io::StreamReader}; use tokio_util::{compat::FuturesAsyncWriteCompatExt, io::StreamReader};
use tower_http::limit::RequestBodyLimitLayer; use tower_http::limit::RequestBodyLimitLayer;
use crate::{ use crate::{askama::DownloadLinkFragment, cache, util, AppState, UploadRecord};
cache,
util::{self, ssr},
AppState, LinkView, UploadRecord,
};
pub fn get_upload_router() -> Router<AppState> { pub fn get_upload_router() -> Router<AppState> {
// Upload needs a subrouter to increase the body limit // Upload needs a subrouter to increase the body limit
@ -92,7 +89,7 @@ async fn upload_to_zip(
.status(200) .status(200)
.header("Content-Type", "text/html") .header("Content-Type", "text/html")
.header("HX-Push-Url", format!("/link/{}", &id)) .header("HX-Push-Url", format!("/link/{}", &id))
.body(ssr::render(|| leptos::view! { <LinkView id record /> })) .body(DownloadLinkFragment { id, record }.render().unwrap())
.unwrap(); .unwrap();
Ok(response) Ok(response)

View file

@ -1,6 +1,5 @@
pub mod headers; pub mod headers;
pub mod logging; pub mod logging;
pub mod ssr;
pub mod sweeper; pub mod sweeper;
use rand::{ use rand::{

View file

@ -1,7 +0,0 @@
pub fn render<F, N>(f: F) -> String
where
F: FnOnce() -> N + 'static,
N: leptos::IntoView,
{
leptos::ssr::render_to_string(f).to_string()
}

50
src/views/askama.rs Normal file
View file

@ -0,0 +1,50 @@
use askama_axum::Template;
use crate::{link::get_remaining_text, UploadRecord};
#[derive(Template)]
#[template(path = "welcome.html")]
pub struct WelcomeTemplate {
pub fact: String,
}
impl WelcomeTemplate {
pub fn new(fact: String) -> WelcomeTemplate {
WelcomeTemplate { fact }
}
}
#[derive(Template)]
#[template(path = "link.html")]
pub struct DownloadLinkTemplate {
pub id: String,
pub record: UploadRecord,
}
impl DownloadLinkTemplate {
fn get_downloads_remaining_text(record: &UploadRecord) -> String {
let downloads_remaining = record.max_downloads - record.downloads;
get_remaining_text(downloads_remaining)
}
}
#[derive(Template)]
#[template(path = "link.html", block = "content")]
pub struct DownloadLinkFragment {
pub id: String,
pub record: UploadRecord,
}
impl DownloadLinkFragment {
fn get_downloads_remaining_text(record: &UploadRecord) -> String {
let downloads_remaining = record.max_downloads - record.downloads;
get_remaining_text(downloads_remaining)
}
}
#[derive(Template)]
#[template(path = "linklist.html")]
pub struct LinkListTemplate {
pub record_keys: Vec<String>,
}

View file

@ -1,26 +0,0 @@
use leptos::{component, view, Children, IntoView};
#[component]
pub fn HtmxPage(children: Children) -> impl IntoView {
view! {
<head>
<title>"Nyazoom"</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="/css/main.css" rel="stylesheet" />
<link href="/css/link.css" rel="stylesheet" />
<script src="/scripts/file_label.js" />
<script src="/scripts/link.js" />
<script
src="https://unpkg.com/htmx.org@2.0.2"
integrity="sha384-Y7hw+L/jvKeWIRRkqWYfPcvVxHzVzn5REgzbawhxAuQGwX1XWe70vji+VSeHOThJ"
crossorigin="anonymous"
></script>
</head>
<body>
<h1>"NyaZoom"<sup>"2"</sup></h1>
{children()}
</body>
}
}

View file

@ -1,47 +0,0 @@
use leptos::{component, view, IntoView};
use crate::link::get_remaining_text;
use crate::state::UploadRecord;
use crate::HtmxPage;
// <link href="../dist/css/link.css" rel="stylesheet" />
// #TODO: Handle pushing cleaner
#[component]
pub fn DownloadLinkPage(id: String, record: UploadRecord) -> impl IntoView {
view! {
<HtmxPage>
<div class="form-wrapper">
<LinkView id record />
</div>
</HtmxPage>
}
}
#[component]
pub fn LinkView(id: String, record: UploadRecord) -> impl IntoView {
let downloads_remaining = record.max_downloads - record.downloads;
view! {
<div class="column-container">
<div class="link-wrapper">
<a id="link" href="/download/{id}">
"Download Now!"
</a>
</div>
<div
class="link-wrapper"
hx-get="/link/{id}/remaining"
hx-trigger="click from:#link delay:0.2s, every 10s"
>
{get_remaining_text(downloads_remaining)}
</div>
<button class="return-button" onclick="clipboard()">
Copy to Clipboard
</button>
<a href="/" class="return-button">
"Return to home"
</a>
</div>
}
}

View file

@ -1,13 +1,7 @@
use futures::TryFutureExt; use futures::TryFutureExt;
use serde::Deserialize; use serde::Deserialize;
pub mod base_page; pub mod askama;
pub mod links;
pub mod welcome;
pub use base_page::*;
pub use links::*;
pub use welcome::*;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct CatFact { pub struct CatFact {

View file

@ -1,49 +0,0 @@
use leptos::{component, view, IntoView};
use crate::HtmxPage;
// {https://api.thecatapi.com/v1/images/search?size=small&format=src}
// {https://cataas.com/cat?width=250&height=250}
#[component]
pub fn WelcomePage(fact: String) -> impl IntoView {
view! {
<HtmxPage>
<div class="form-wrapper">
<WelcomeView fact />
</div>
</HtmxPage>
}
}
#[component]
pub fn WelcomeView(fact: String) -> impl IntoView {
view! {
<form
id="form"
hx-swap="outerHTML"
hx-post="/upload"
hx-encoding="multipart/form-data"
class="column-container"
>
<div class="cat-img-wrapper">
<img
class="cat-img"
src="https://api.thecatapi.com/v1/images/search?size=small&format=src"
/>
</div>
<input
type="file"
id="file"
name="file"
data-multiple-caption="{{count}} files selected"
multiple
/>
<label for="file">"Select Files"</label>
<input type="submit" value="Get Link~" />
<p id="cat-fact">{fact}</p>
<progress id="progress" class="htmx-indicator" value="0" max="100"></progress>
</form>
<script src="/scripts/loading_progress.js" />
}
}

21
templates/base.html Normal file
View file

@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<title>Nyazoom</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="/css/main.css" rel="stylesheet" />
<link href="/css/link.css" rel="stylesheet" />
<script src="/scripts/file_label.js"></script>
<script src="/scripts/link.js"></script>
<script
src="https://unpkg.com/htmx.org@2.0.2"
integrity="sha384-Y7hw+L/jvKeWIRRkqWYfPcvVxHzVzn5REgzbawhxAuQGwX1XWe70vji+VSeHOThJ"
crossorigin="anonymous"
></script>
</head>
<body>
<h1>NyaZoom<sup>2</sup></h1>
{% block content %}{% endblock %}
</body>
</html>

24
templates/link.html Normal file
View file

@ -0,0 +1,24 @@
{% extends "base.html" %}
{% block content %}
<div class="form-wrapper">
<div class="column-container">
<div class="link-wrapper">
<a id="link" href="/download/{{id}}"> "Download Now!" </a>
</div>
<div
class="link-wrapper"
hx-get="/link/{{id}}/remaining"
hx-trigger="click from:#link delay:0.2s, every 10s"
>
{{Self::get_downloads_remaining_text(record)}}
</div>
<button class="return-button" onclick="clipboard()">
Copy to Clipboard
</button>
<a href="/" class="return-button"> "Return to home" </a>
</div>
</div>
{% endblock content %}

25
templates/linklist.html Normal file
View file

@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block content %}
<HtmxPage>
<div class="form-wrapper">
<div class="column-container">
<ul>
{% for key in record_keys %}
<li class="link-wrapper">
<a href="/link/{{ key }}">{{ key }}</a>
<button
style="margin-left: 1em;"
hx-target="closest .link-wrapper"
hx-swap="outerHTML"
hx-delete="/link/{{ key }}"
>
"X"
</button>
</li>
{% endfor %}
</ul>
</div>
</div>
</HtmxPage>
{% endblock content %}

38
templates/welcome.html Normal file
View file

@ -0,0 +1,38 @@
{% extends "base.html" %}
{% block content %}
<div class="form-wrapper">
<form
id="form"
hx-swap="outerHTML"
hx-post="/upload"
hx-encoding="multipart/form-data"
class="column-container"
>
<div class="cat-img-wrapper">
<img
class="cat-img"
src="https://api.thecatapi.com/v1/images/search?size=small&format=src"
/>
</div>
<input
type="file"
id="file"
name="file"
data-multiple-caption="{count} files selected"
multiple
/>
<label for="file">Select Files</label>
<input type="submit" value="Get Link~" />
<p id="cat-fact">{{ fact }}</p>
<progress
id="progress"
class="htmx-indicator"
value="0"
max="100"
></progress>
</form>
<script src="/scripts/loading_progress.js"></script>
</div>
{% endblock content %}