Moved tabs to separate module
This commit is contained in:
parent
097a5303e9
commit
f8f656fcd0
231
src/main.rs
231
src/main.rs
@ -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
238
src/tabs.rs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user