From 877ce477cc137150316359fcd9128945801fa351 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Sun, 29 Dec 2024 18:18:01 +0100 Subject: [PATCH] update ui --- src/archive.rs | 103 +++++++++++++++++++++++++++++++++++++++-------- src/main.rs | 9 ++++- src/pages/mod.rs | 89 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 177 insertions(+), 24 deletions(-) diff --git a/src/archive.rs b/src/archive.rs index 149372d..d46cbdd 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -4,6 +4,22 @@ use std::{ path::{Path, PathBuf}, }; +pub fn read_dir(dir: &PathBuf) -> Vec { + let mut list = Vec::new(); + + if let Ok(entries) = std::fs::read_dir(dir) { + for entry in entries { + if let Ok(entry) = entry { + if let Some(file_name) = entry.file_name().to_str() { + list.push(file_name.to_string()); + } + } + } + } + + list +} + fn internalize_urls(input: &str) -> String { let url_pattern = r"https?://([a-zA-Z0-9.-]+)(/[\w./-]*)"; let re = regex::Regex::new(url_pattern).unwrap(); @@ -39,6 +55,57 @@ impl Domain { pub fn path(&self, path: &str) -> Document { Document::new(&self.name, path, self.dir.parent().unwrap().to_path_buf()) } + + pub fn paths(&self, path: &str) -> Vec { + let mut base_path = self.dir.clone(); + + for p in path.split('/') { + base_path = base_path.join(p); + } + + let dir_content = read_dir(&base_path); + + let mut ret = Vec::new(); + + for entry in dir_content { + let url_path = format!("{path}/{entry}"); + let is_doc = read_dir(&base_path.join(entry)) + .into_iter() + .any(|x| x.starts_with("index_") && x.ends_with(".html")); + if is_doc { + ret.push(PathEntry::Document(Document::new( + &self.name, + &url_path, + self.dir.parent().unwrap().to_path_buf(), + ))); + } else { + ret.push(PathEntry::Path(self.name.clone(), url_path)); + } + } + + ret + } +} + +pub enum PathEntry { + Path(String, String), + Document(Document), +} + +impl PathEntry { + pub fn url(&self) -> String { + match self { + PathEntry::Path(domain, path) => format!("/d/{domain}/{path}"), + PathEntry::Document(document) => document.url(), + } + } + + pub fn path(&self) -> String { + match self { + PathEntry::Path(_, path) => path.to_string(), + PathEntry::Document(document) => document.path.clone(), + } + } } pub struct Document { @@ -56,17 +123,17 @@ impl Document { } } - pub fn render_local(&self, version: Option) -> Option { - let mut file_path = self.base_dir.join(&self.domain); + pub fn url(&self) -> String { + format!("/s/{}/{}", self.domain, self.path) + } - for p in self.path.split('/') { - file_path = file_path.join(p); - } + pub fn render_local(&self, version: Option) -> Option { + let mut file_path = self.doc_dir(); let latest_version = if let Some(version) = version { format!("index_{version}.html") } else { - let versions = Self::versions(&file_path); + let versions = self.versions(); versions.first().cloned()? }; @@ -81,20 +148,18 @@ impl Document { } } - pub fn versions(path: &PathBuf) -> Vec { - let mut version_list = Vec::new(); + pub fn doc_dir(&self) -> PathBuf { + let mut file_path = self.base_dir.join(&self.domain); - if let Ok(entries) = std::fs::read_dir(path) { - for entry in entries { - if let Ok(entry) = entry { - if let Some(file_name) = entry.file_name().to_str() { - version_list.push(file_name.to_string()); - } - } - } + for p in self.path.split('/') { + file_path = file_path.join(p); } - version_list + file_path + } + + pub fn versions(&self) -> Vec { + read_dir(&self.doc_dir()) } } @@ -105,6 +170,10 @@ impl WebsiteArchive { } } + pub fn domains(&self) -> Vec { + read_dir(&self.dir) + } + pub fn get_domain(&self, domain: &str) -> Domain { Domain::new(domain, self.dir.join(domain)) } diff --git a/src/main.rs b/src/main.rs index d728d1f..98d5fe8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,13 @@ async fn launch() -> _ { let arc = WebsiteArchive::new("./websites"); rocket::build() - .mount("/", routes![pages::index, pages::render_website]) + .mount( + "/", + routes![ + pages::index, + pages::render_website, + pages::domain_info_route + ], + ) .manage(arc) } diff --git a/src/pages/mod.rs b/src/pages/mod.rs index 87ba675..af1ce9e 100644 --- a/src/pages/mod.rs +++ b/src/pages/mod.rs @@ -1,15 +1,92 @@ use std::path::PathBuf; -use based::request::{respond_html, StringResponse}; -use maud::html; +use based::{ + page::Shell, + request::{respond_html, RequestContext, StringResponse}, +}; +use maud::{html, PreEscaped}; use rocket::{get, State}; -use crate::archive::WebsiteArchive; +use crate::archive::{PathEntry, WebsiteArchive}; + +pub async fn render_page(content: PreEscaped, ctx: RequestContext) -> StringResponse { + based::page::render_page( + content, + "Website Archive", + ctx, + &Shell::new( + html! { + script src="https://cdn.tailwindcss.com" {}; + }, + html! {}, + Some("bg-zinc-950 text-white min-h-screen flex pt-8 justify-center".to_string()), + ), + ) + .await +} #[get("/")] -pub async fn index() -> StringResponse { - // TODO : websites overview grid - unimplemented!() +pub async fn index(ctx: RequestContext, arc: &State) -> StringResponse { + let websites = arc.domains(); + + let content = html! { + div class="container mx-auto p-4" { + h1 class="text-5xl font-bold text-center mb-10" { "Websites" }; + div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-6 w-screen" { + + @for site in websites { + a href=(format!("/d/{site}")) class="bg-neutral-900 shadow-md rounded-lg hover:bg-neutral-800 bg-gray-1 hover:cursor-pointer transition-all duration-300 flex flex-col items-center justify-center aspect-square max-w-60" { + div class="bg-blue-500 text-white rounded-full p-4" { + img class="h-8 w-8" src=(format!("/favicon/{site}")) {}; + }; + p class="mt-4 text-base font-medium" { (site) }; + }; + }; + }; + } + }; + + render_page(content, ctx).await +} + +pub fn arrow_icon(color: &str) -> PreEscaped { + html! { + svg class=(format!("w-5 h-5 text-{color}-500")) xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" { + path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" {}; + }; + } +} + +#[get("/d//")] +pub async fn domain_info_route( + ctx: RequestContext, + domain: &str, + paths: PathBuf, + arc: &State, +) -> StringResponse { + let domain = arc.get_domain(domain); + let paths = domain.paths(paths.to_str().unwrap()); + + let content = html! { + h2 class="text-xl font-bold mb-4" { (domain.name) }; + div class="max-w-md mx-auto p-4 bg-neutral-900 rounded-lg shadow-md" { + ul class="space-y-2" { + @for path in paths { + a href=(path.url()) class="flex items-center gap-2 p-3 border bg-neutral-800 rounded hover:shadow-lg transition" { + @if matches!(path, PathEntry::Document(_)) { + (arrow_icon("red")) + } @else { + (arrow_icon("blue")) + }; + + span class="font-medium" { (path.path()) }; + }; + }; + }; + }; + }; + + render_page(content, ctx).await } #[get("/s//?