Moved tabs to separate module

This commit is contained in:
p11 2025-05-31 15:45:06 +02:00
parent 097a5303e9
commit f8f656fcd0
2 changed files with 241 additions and 228 deletions

View File

@ -20,20 +20,19 @@ use data_stream::{
from_stream, to_stream, from_stream, to_stream,
}; };
use header_config::parse_config; use header_config::parse_config;
use indexmap::IndexMap;
use maud::html; use maud::html;
use percent_encoding::percent_decode_str; use percent_encoding::percent_decode_str;
use pukram2html::{Settings, convert, convert_extended, convert_subheader}; use pukram2html::convert;
use threadpool::ThreadPool; use threadpool::ThreadPool;
mod chara; mod chara;
mod dialog; mod dialog;
mod request; mod request;
mod tabs;
mod vn; mod vn;
use chara::render_character;
use request::Request; use request::Request;
use vn::render_novel; use tabs::{Tab, TabInfo, write_tab_styles};
#[derive(Clone)] #[derive(Clone)]
struct Comment { struct Comment {
@ -593,230 +592,6 @@ impl<'a> RequestData<'a> {
} }
} }
static GENERAL_TAB_STYLE: &str = r"
.tab-system {
margin: 20px;
}
.tab-radio {
display: none;
}
.tab-nav {
display: flex;
gap: 5px;
margin-bottom: -1px;
}
.tab-button {
padding: 8px 15px;
background: #f0f0f0;
border: 1px solid #ddd;
border-bottom: 1px solid transparent;
cursor: pointer;
border-radius: 4px 4px 0 0;
position: relative;
}
.tab-button:hover {
background: #e0e0e0;
}
.tab-radio:checked + .tab-button {
background: white;
border-color: #ddd;
border-bottom-color: white;
z-index: 1;
}
.tab-content {
display: none;
padding: 15px;
border: 1px solid #ddd;
}";
fn write_tab_styles(stream: &mut TcpStream, count: usize) {
let _ = writeln!(stream, "<style>");
let _ = write!(stream, "{GENERAL_TAB_STYLE}");
for i in 1..count {
let _ = write!(stream, "#tab-{i}:checked ~ #content-{i},");
}
let _ = writeln!(
stream,
"#tab-{count}:checked ~ #content-{count} {{ display: block; }}"
);
let _ = writeln!(stream, "</style>");
}
fn render_comments(stream: &mut TcpStream, comments: Vec<Comment>) {
let html = html! {
h2 { "Comments" }
form method="POST" {
input type="text" name="name" value="anon" placeholder="Name";
br;
textarea rows="5" cols="60" name="text" placeholder="Enter comment..." {}
br;
input type="submit" value="Send!";
}
form method="POST" {
input type="submit" value="💖️" name="up";
input type="submit" value="💔️" name="down";
}
@for comment in comments {
fieldset {
legend { (comment.name) }
(maud::PreEscaped(comment.text))
}
}
};
let _ = stream.write_all(html.into_string().as_bytes());
}
enum TabInfo {
Lines(Vec<String>),
Chara(Vec<String>),
Game(IndexMap<Box<str>, Box<str>>),
Description,
Comment(Vec<Comment>),
}
impl TabInfo {
fn render_tab_content(
self,
stream: &mut TcpStream,
path: &Path,
relative_path: &str,
censored: bool,
choice: usize,
progress: &str,
mlc_path: Option<&Path>,
file_paths: &DocumentPaths,
) {
match self {
Self::Lines(lines) => {
convert_extended(
lines,
stream,
Settings::default()
.with_handler(entry_handler(path, relative_path, censored))
.with_start_level(1)
.with_use_textboxes(true),
);
}
Self::Chara(lines) => {
convert_extended(
lines,
stream,
Settings::default()
.with_handler(chara_handler(path, relative_path))
.with_start_level(1)
.with_use_textboxes(true),
);
}
Self::Game(config_map) => {
let _ = render_novel(
config_map,
file_paths.pk,
mlc_path,
file_paths.mld,
relative_path,
stream,
choice,
progress,
);
}
Self::Description => {
let Ok(pki_file) = File::open(file_paths.pki.unwrap()) else {
return;
};
let _ = writeln!(stream, "<h2>Description</h2>");
if let Some(audio_path) = &file_paths.audio {
if audio_path.is_file() {
let _ = writeln!(
stream,
"<p><audio controls src=\"/{relative_path}.mp3\"/></p>"
);
}
}
let lines = BufReader::new(pki_file).lines().map_while(Result::ok);
convert_subheader(lines, stream, 1);
}
Self::Comment(comments) => {
render_comments(stream, comments);
}
}
}
}
struct Tab {
title: Box<str>,
info: TabInfo,
}
fn entry_handler<W: Write>(
path: &Path,
relative_path: &str,
censored: bool,
) -> impl Fn(&str, &mut W, usize) {
move |mut entry, output, level| {
let level = level + 1;
let mut pki_path = path.to_path_buf();
let mut audio_path = path.to_path_buf();
if let Some((real_entry, _)) = entry.split_once(':') {
entry = real_entry
}
let pki_extension = if censored { "pkc" } else { "pki" };
if relative_path.is_empty() {
pki_path.push(format!("{entry}.{pki_extension}"));
audio_path.push(format!("{entry}.mp3"));
} else {
pki_path.push(format!("{relative_path}/{entry}.{pki_extension}"));
audio_path.push(format!("{relative_path}/{entry}.mp3"));
}
let Ok(file) = File::open(pki_path) else {
return;
};
let _ = writeln!(
output,
"<h{level}><a href=\"{relative_path}/{entry}\">{entry}</a></h{level}>"
);
if Path::is_file(&audio_path) {
let _ = writeln!(
output,
"<p><audio controls src=\"/{relative_path}/{entry}.mp3\"/></p>"
);
}
convert_subheader(
BufReader::new(file).lines().map(Result::unwrap_or_default),
output,
level,
);
}
}
fn chara_handler<W: Write>(path: &Path, relative_path: &str) -> impl Fn(&str, &mut W, usize) {
move |entry, output, _level| {
let mut chara_path = path.to_path_buf();
chara_path.push(relative_path);
chara_path.push(format!("{entry}.chara"));
let Some(def) = load_character_file(&chara_path) else {
return;
};
let html = render_character(entry, &def, relative_path);
let _ = output.write_all(html.into_string().as_bytes());
}
}
fn handle_relative_connection( fn handle_relative_connection(
info: Arc<SharedSiteInfo>, info: Arc<SharedSiteInfo>,
mut stream: TcpStream, mut stream: TcpStream,

238
src/tabs.rs Normal file
View File

@ -0,0 +1,238 @@
use std::{
fs::File,
io::{BufRead, BufReader, Write},
net::TcpStream,
path::Path,
};
use indexmap::IndexMap;
use maud::html;
use pukram2html::{Settings, convert_extended, convert_subheader};
use crate::{
Comment, DocumentPaths, chara::render_character, load_character_file, vn::render_novel,
};
static GENERAL_TAB_STYLE: &str = r"
.tab-system {
margin: 20px;
}
.tab-radio {
display: none;
}
.tab-nav {
display: flex;
gap: 5px;
margin-bottom: -1px;
}
.tab-button {
padding: 8px 15px;
background: #f0f0f0;
border: 1px solid #ddd;
border-bottom: 1px solid transparent;
cursor: pointer;
border-radius: 4px 4px 0 0;
position: relative;
}
.tab-button:hover {
background: #e0e0e0;
}
.tab-radio:checked + .tab-button {
background: white;
border-color: #ddd;
border-bottom-color: white;
z-index: 1;
}
.tab-content {
display: none;
padding: 15px;
border: 1px solid #ddd;
}";
pub fn write_tab_styles(stream: &mut TcpStream, count: usize) {
let _ = writeln!(stream, "<style>");
let _ = write!(stream, "{GENERAL_TAB_STYLE}");
for i in 1..count {
let _ = write!(stream, "#tab-{i}:checked ~ #content-{i},");
}
let _ = writeln!(
stream,
"#tab-{count}:checked ~ #content-{count} {{ display: block; }}"
);
let _ = writeln!(stream, "</style>");
}
pub enum TabInfo {
Lines(Vec<String>),
Chara(Vec<String>),
Game(IndexMap<Box<str>, Box<str>>),
Description,
Comment(Vec<Comment>),
}
impl TabInfo {
pub fn render_tab_content(
self,
stream: &mut TcpStream,
path: &Path,
relative_path: &str,
censored: bool,
choice: usize,
progress: &str,
mlc_path: Option<&Path>,
file_paths: &DocumentPaths,
) {
match self {
Self::Lines(lines) => {
convert_extended(
lines,
stream,
Settings::default()
.with_handler(entry_handler(path, relative_path, censored))
.with_start_level(1)
.with_use_textboxes(true),
);
}
Self::Chara(lines) => {
convert_extended(
lines,
stream,
Settings::default()
.with_handler(chara_handler(path, relative_path))
.with_start_level(1)
.with_use_textboxes(true),
);
}
Self::Game(config_map) => {
let _ = render_novel(
config_map,
file_paths.pk,
mlc_path,
file_paths.mld,
relative_path,
stream,
choice,
progress,
);
}
Self::Description => {
let Ok(pki_file) = File::open(file_paths.pki.unwrap()) else {
return;
};
let _ = writeln!(stream, "<h2>Description</h2>");
if let Some(audio_path) = &file_paths.audio {
if audio_path.is_file() {
let _ = writeln!(
stream,
"<p><audio controls src=\"/{relative_path}.mp3\"/></p>"
);
}
}
let lines = BufReader::new(pki_file).lines().map_while(Result::ok);
convert_subheader(lines, stream, 1);
}
Self::Comment(comments) => {
render_comments(stream, comments);
}
}
}
}
fn render_comments(stream: &mut TcpStream, comments: Vec<Comment>) {
let html = html! {
h2 { "Comments" }
form method="POST" {
input type="text" name="name" value="anon" placeholder="Name";
br;
textarea rows="5" cols="60" name="text" placeholder="Enter comment..." {}
br;
input type="submit" value="Send!";
}
form method="POST" {
input type="submit" value="💖️" name="up";
input type="submit" value="💔️" name="down";
}
@for comment in comments {
fieldset {
legend { (comment.name) }
(maud::PreEscaped(comment.text))
}
}
};
let _ = stream.write_all(html.into_string().as_bytes());
}
pub struct Tab {
pub title: Box<str>,
pub info: TabInfo,
}
fn entry_handler<W: Write>(
path: &Path,
relative_path: &str,
censored: bool,
) -> impl Fn(&str, &mut W, usize) {
move |mut entry, output, level| {
let level = level + 1;
let mut pki_path = path.to_path_buf();
let mut audio_path = path.to_path_buf();
if let Some((real_entry, _)) = entry.split_once(':') {
entry = real_entry
}
let pki_extension = if censored { "pkc" } else { "pki" };
if relative_path.is_empty() {
pki_path.push(format!("{entry}.{pki_extension}"));
audio_path.push(format!("{entry}.mp3"));
} else {
pki_path.push(format!("{relative_path}/{entry}.{pki_extension}"));
audio_path.push(format!("{relative_path}/{entry}.mp3"));
}
let Ok(file) = File::open(pki_path) else {
return;
};
let _ = writeln!(
output,
"<h{level}><a href=\"{relative_path}/{entry}\">{entry}</a></h{level}>"
);
if Path::is_file(&audio_path) {
let _ = writeln!(
output,
"<p><audio controls src=\"/{relative_path}/{entry}.mp3\"/></p>"
);
}
convert_subheader(
BufReader::new(file).lines().map(Result::unwrap_or_default),
output,
level,
);
}
}
fn chara_handler<W: Write>(path: &Path, relative_path: &str) -> impl Fn(&str, &mut W, usize) {
move |entry, output, _level| {
let mut chara_path = path.to_path_buf();
chara_path.push(relative_path);
chara_path.push(format!("{entry}.chara"));
let Some(def) = load_character_file(&chara_path) else {
return;
};
let html = render_character(entry, &def, relative_path);
let _ = output.write_all(html.into_string().as_bytes());
}
}