Added chara support
This commit is contained in:
parent
c8f2cd8b66
commit
f6bc4b2fee
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -17,6 +17,12 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||
|
||||
[[package]]
|
||||
name = "chara"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2653cb9243a7d4453127141c162c1ba619d99c7230f1d8ad0a9cdab9cf0651ea"
|
||||
|
||||
[[package]]
|
||||
name = "data-stream"
|
||||
version = "0.3.0"
|
||||
@ -243,6 +249,7 @@ dependencies = [
|
||||
name = "pukram-server"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chara",
|
||||
"data-stream",
|
||||
"dialogi",
|
||||
"event-simulation",
|
||||
|
||||
@ -16,3 +16,4 @@ indexmap = "2.9.0"
|
||||
multilinear = "0.3.1"
|
||||
multilinear-parser = "0.3.3"
|
||||
event-simulation = "0.1.2"
|
||||
chara = "0.2.0"
|
||||
|
||||
112
src/chara.rs
Normal file
112
src/chara.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use chara::CharacterDefinition;
|
||||
use maud::{Markup, html};
|
||||
|
||||
pub fn render_character(def: &CharacterDefinition, relative_path: &str) -> Markup {
|
||||
let relative_path = relative_path.rsplit_once('/');
|
||||
let relative_path = relative_path.map_or("", |(l, _r)| l);
|
||||
|
||||
html! {
|
||||
style { (CHARACTER_CSS) }
|
||||
|
||||
div.customizer-container {
|
||||
div.character-container {
|
||||
@for layer in &def.layers {
|
||||
img.character-layer
|
||||
id=(format!("{}-layer", layer.internal_name))
|
||||
src=(layer.entries.first().map(|e| format!("/{relative_path}/{}", e.path)).unwrap_or_default())
|
||||
style=(format!("display: {};",
|
||||
if !layer.entries.is_empty() && !layer.entries[0].path.is_empty() {
|
||||
"block"
|
||||
} else {
|
||||
"none"
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
div.controls-column {
|
||||
@for layer in &def.layers {
|
||||
@if let Some(display_name) = &layer.display_name {
|
||||
div.layer-group {
|
||||
div.layer-title { (display_name) }
|
||||
div.layer-options {
|
||||
@for (i, entry) in layer.entries.iter().enumerate() {
|
||||
label.option {
|
||||
input type="radio"
|
||||
name=(layer.internal_name)
|
||||
value=(entry.path)
|
||||
checked[i==0]
|
||||
onchange=(format!(
|
||||
"var img=document.getElementById('{}-layer');img.src=this.value;img.style.display=this.value==''?'none':'block';",
|
||||
layer.internal_name
|
||||
));
|
||||
(entry.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CHARACTER_CSS: &str = r"
|
||||
.customizer-container { display: flex; gap: 20px; margin-top: 20px; }
|
||||
.character-container {
|
||||
position: relative;
|
||||
width: 300px;
|
||||
height: 400px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 10px;
|
||||
background-color: white;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.character-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
.controls-column {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
.layer-group {
|
||||
background-color: white;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
.layer-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
.layer-options {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
.option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.option input {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
.back-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
";
|
||||
26
src/main.rs
26
src/main.rs
@ -14,6 +14,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use ::chara::CharacterDefinition;
|
||||
use data_stream::{
|
||||
FromStream, ToStream, collections::SizeSettings, default_settings::PortableSettings,
|
||||
from_stream, to_stream,
|
||||
@ -24,10 +25,12 @@ use percent_encoding::percent_decode_str;
|
||||
use pukram2html::{Settings, convert, convert_extended, convert_subheader};
|
||||
use threadpool::ThreadPool;
|
||||
|
||||
mod chara;
|
||||
mod dialog;
|
||||
mod request;
|
||||
mod vn;
|
||||
|
||||
use chara::render_character;
|
||||
use request::Request;
|
||||
use vn::render_novel;
|
||||
|
||||
@ -374,6 +377,7 @@ fn handle_connection(
|
||||
reply_binary(stream, &relative_path, "image", &ending, path)
|
||||
}
|
||||
"mp3" | "wav" => reply_binary(stream, &relative_path, "audio", &ending, path),
|
||||
"chara" => reply_chara(stream, &relative_path, path),
|
||||
_ => fail(stream),
|
||||
}
|
||||
return;
|
||||
@ -449,6 +453,28 @@ fn reply_binary(
|
||||
}
|
||||
}
|
||||
|
||||
fn load_character_file(path: &Path) -> Option<CharacterDefinition> {
|
||||
std::fs::read_to_string(path)
|
||||
.ok()
|
||||
.map(|content| CharacterDefinition::parse(&content))
|
||||
}
|
||||
|
||||
fn reply_chara(mut stream: TcpStream, relative_path: &str, mut path: PathBuf) {
|
||||
path.push(relative_path);
|
||||
|
||||
let Some(def) = load_character_file(&path) else {
|
||||
fail(stream);
|
||||
return;
|
||||
};
|
||||
|
||||
let _ = write!(stream, "HTTP/1.1 200 OK\r\n");
|
||||
let _ = write!(stream, "Content-Type: text/html\r\n");
|
||||
let _ = write!(stream, "\r\n");
|
||||
|
||||
let html = render_character(&def, relative_path);
|
||||
let _ = stream.write_all(html.into_string().as_bytes());
|
||||
}
|
||||
|
||||
fn handle_relative_connection(
|
||||
info: Arc<SiteInfo>,
|
||||
mut stream: TcpStream,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user