Remove wasm; add js client.
This commit is contained in:
parent
26fe6c86e7
commit
7ea088737d
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
/target
|
||||
/data
|
||||
Cargo.lock
|
||||
*.pem
|
||||
|
@ -3,6 +3,5 @@ resolver = "2"
|
||||
|
||||
members = [
|
||||
"game",
|
||||
"client-web",
|
||||
"server",
|
||||
]
|
||||
|
@ -1,24 +0,0 @@
|
||||
[package]
|
||||
name = "client-web"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = "0.2.92"
|
||||
|
||||
game = { path = "../game" }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.69"
|
||||
features = [
|
||||
'Document',
|
||||
'Element',
|
||||
'HtmlElement',
|
||||
'Node',
|
||||
'Window',
|
||||
|
||||
'Event',
|
||||
]
|
@ -1,67 +0,0 @@
|
||||
use crate::prelude::Scene;
|
||||
|
||||
pub mod session; use session::Session;
|
||||
pub mod scene;
|
||||
|
||||
pub struct App {
|
||||
pub session:Session,
|
||||
|
||||
pub document:web_sys::Document,
|
||||
pub menu:web_sys::Element,
|
||||
pub main:web_sys::Element,
|
||||
|
||||
unload:Option<fn()>,
|
||||
}
|
||||
impl App {
|
||||
pub fn init() -> Result<Self,()>
|
||||
{
|
||||
match web_sys::window() {
|
||||
Some(window) => match window.document() {
|
||||
Some(document) => {
|
||||
let menu = document.create_element("nav");
|
||||
let container = document.create_element("main");
|
||||
|
||||
if menu.is_ok() && container.is_ok() {
|
||||
Ok(Self {
|
||||
session:Session::new(),
|
||||
|
||||
document:document,
|
||||
menu:menu.unwrap(),
|
||||
main:container.unwrap(),
|
||||
|
||||
unload:None,
|
||||
})
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
None => Err(())
|
||||
}
|
||||
None => Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load<S:Scene>(&mut self)
|
||||
{
|
||||
match self.unload {
|
||||
Some(proc) => { proc(); }
|
||||
None => { }
|
||||
}
|
||||
|
||||
self.document_clear();
|
||||
S::load();
|
||||
self.unload = Some(S::unload);
|
||||
}
|
||||
|
||||
pub fn document_clear(&self)
|
||||
{
|
||||
match self.document.body() {
|
||||
Some(body) => {
|
||||
while let Some(child) = body.last_child() {
|
||||
body.remove_child(&child).ok();
|
||||
}
|
||||
}
|
||||
None => { }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
use crate::{
|
||||
scene::{ui, util},
|
||||
session::SessionState,
|
||||
APP,
|
||||
};
|
||||
|
||||
/* List Online
|
||||
**
|
||||
** Displays a table of recent, ongoing contests.
|
||||
*/
|
||||
|
||||
pub struct SceneListOnline { }
|
||||
impl crate::prelude::Scene for SceneListOnline {
|
||||
fn load()
|
||||
{
|
||||
let mut app = unsafe {APP.borrow_mut()};
|
||||
match &mut *app {
|
||||
Some(app) => {
|
||||
util::load_mainmenu();
|
||||
|
||||
// Create header
|
||||
match app.document.create_element("header") {
|
||||
Ok(header) => {
|
||||
|
||||
// Left-side header
|
||||
match app.document.create_element("section") {
|
||||
Ok(section_left) => {
|
||||
|
||||
// Button: Start
|
||||
if app.session.state == SessionState::User {
|
||||
match ui::button(&app.document, "Start", |_e|{ }) {
|
||||
Ok(button) => { section_left.append_child(&button).ok(); }
|
||||
Err(_) => { }
|
||||
}
|
||||
}
|
||||
|
||||
header.append_child(§ion_left).ok();
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
|
||||
// Right-side header
|
||||
match app.document.create_element("section") {
|
||||
Ok(section_right) => {
|
||||
|
||||
// Button: Previous Page
|
||||
match ui::button(&app.document, "◀", |_e|{ }) {
|
||||
Ok(button) => { section_right.append_child(&button).ok(); }
|
||||
Err(_) => { }
|
||||
}
|
||||
|
||||
// Text: Page Number
|
||||
match ui::button(&app.document, "", |_e|{ }) {
|
||||
Ok(elem) => {
|
||||
elem.set_id("pn");
|
||||
section_right.append_child(&elem).ok();
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
|
||||
// Button: Next Page
|
||||
match ui::button(&app.document, "▶", |_e|{ }) {
|
||||
Ok(button) => { section_right.append_child(&button).ok(); }
|
||||
Err(_) => { }
|
||||
}
|
||||
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
|
||||
app.main.append_child(&header).ok();
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
}
|
||||
None => { }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
pub mod util;
|
||||
pub mod ui;
|
||||
mod list_online; pub use list_online::*;
|
@ -1,27 +0,0 @@
|
||||
use wasm_bindgen::{prelude::Closure, JsCast};
|
||||
|
||||
pub fn button(document:&web_sys::Document, text:&str, callback:fn(web_sys::Event)) -> Result<web_sys::Element,()>
|
||||
{
|
||||
match document.create_element("button") {
|
||||
Ok(element) => {
|
||||
element.set_text_content(Some(text));
|
||||
|
||||
let cl = Closure::wrap(Box::new(callback) as Box<dyn FnMut(_)>);
|
||||
element.add_event_listener_with_callback("click", cl.as_ref().unchecked_ref()).ok();
|
||||
cl.forget();
|
||||
Ok(element)
|
||||
}
|
||||
Err(_) => Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_block(document:&web_sys::Document, text:&str) -> Result<web_sys::Element,()>
|
||||
{
|
||||
match document.create_element("div") {
|
||||
Ok(element) => {
|
||||
element.set_text_content(Some(text));
|
||||
Ok(element)
|
||||
}
|
||||
Err(_) => Err(())
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
use crate::{
|
||||
scene::{self, ui},
|
||||
session::SessionState,
|
||||
APP
|
||||
};
|
||||
|
||||
pub fn load_mainmenu()
|
||||
{
|
||||
let mut app = unsafe {APP.borrow_mut()};
|
||||
match &mut *app {
|
||||
Some(app) => {
|
||||
|
||||
if app.session.state == SessionState::User {
|
||||
// Button: Online
|
||||
match ui::button(&app.document, "Online", |_e|{
|
||||
let mut app = unsafe {APP.borrow_mut()};
|
||||
match &mut *app {
|
||||
Some(app) => { app.load::<scene::SceneListOnline>(); }
|
||||
None => { }
|
||||
}
|
||||
}) {
|
||||
Ok(button) => { app.menu.append_child(&button).ok(); }
|
||||
Err(_) => { }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Button: Continue
|
||||
match ui::button(&app.document, "Continue", |_e|{
|
||||
let mut app = unsafe {APP.borrow_mut()};
|
||||
match &mut *app {
|
||||
Some(app) => { app.load::<scene::SceneListOnline>(); }
|
||||
None => { }
|
||||
}
|
||||
}) {
|
||||
Ok(button) => { app.menu.append_child(&button).ok(); }
|
||||
Err(_) => { }
|
||||
}
|
||||
|
||||
|
||||
// Button: Join
|
||||
match ui::button(&app.document, "Continue", |_e|{
|
||||
let mut app = unsafe {APP.borrow_mut()};
|
||||
match &mut *app {
|
||||
Some(app) => { app.load::<scene::SceneListOnline>(); }
|
||||
None => { }
|
||||
}
|
||||
}) {
|
||||
Ok(button) => { app.menu.append_child(&button).ok(); }
|
||||
Err(_) => { }
|
||||
}
|
||||
|
||||
|
||||
} None => { }
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum SessionState {
|
||||
Anonymous,
|
||||
User,
|
||||
}
|
||||
|
||||
pub struct Session {
|
||||
pub state:SessionState,
|
||||
pub handle:String,
|
||||
pub token:[u8; 8],
|
||||
pub secret:[u8; 16],
|
||||
}
|
||||
impl Session {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
state:SessionState::Anonymous,
|
||||
handle:String::new(),
|
||||
token:[0; 8],
|
||||
secret:[0; 16],
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" { }
|
@ -1,32 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
mod ext;
|
||||
mod prelude;
|
||||
mod app; use app::*;
|
||||
|
||||
static mut APP :RefCell<Option<App>> = RefCell::new(None);
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
fn main() -> Result<(),JsValue>
|
||||
{
|
||||
match App::init() {
|
||||
Ok(app) => {
|
||||
unsafe {
|
||||
APP = RefCell::new(Some(app));
|
||||
|
||||
let mut app = APP.borrow_mut();
|
||||
match &mut *app {
|
||||
Some(app) => {
|
||||
app.load::<scene::SceneListOnline>();
|
||||
} None => { }
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
Ok(())
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
pub trait Scene {
|
||||
fn load();
|
||||
fn unload() { }
|
||||
|
||||
fn hover() { }
|
||||
fn click() { }
|
||||
fn keydown() { }
|
||||
}
|
@ -15,6 +15,7 @@ webpki-roots = "0.26"
|
||||
opaque-ke = "2.0.0"
|
||||
hyper = { version = "1.4.1", features = ["full"] }
|
||||
hyper-util = { version = "0.1.7", features = ["tokio"] }
|
||||
http-body-util = "0.1.2"
|
||||
|
||||
game = { path = "../game" }
|
||||
|
||||
|
@ -8,6 +8,7 @@ mod system;
|
||||
mod protocol;
|
||||
|
||||
use app::App;
|
||||
use hyper::body::Bytes;
|
||||
use system::{cache::WebCache, net::Stream};
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
@ -60,6 +61,8 @@ async fn handle_ws(mut ws_stream:WebSocketStream<TlsStream<TcpStream>>, args:Htt
|
||||
packet::*,
|
||||
};
|
||||
|
||||
println!("ws ready");
|
||||
|
||||
let bus_ds = args.bus.mailbox(1).unwrap_or(1);
|
||||
|
||||
while match ws_stream.try_next().await {
|
||||
@ -102,41 +105,62 @@ async fn handle_ws(mut ws_stream:WebSocketStream<TlsStream<TcpStream>>, args:Htt
|
||||
ws_stream.close(None).await.ok();
|
||||
}
|
||||
|
||||
async fn service_http(request:hyper::Request<hyper::body::Incoming>, args:HttpServiceArgs) -> Result<hyper::Response<String>, std::convert::Infallible>
|
||||
async fn service_http(request:hyper::Request<hyper::body::Incoming>, args:HttpServiceArgs) -> Result<hyper::Response<http_body_util::Full<Bytes>>, std::convert::Infallible>
|
||||
{
|
||||
use hyper::Response;
|
||||
use hyper::{Response, body::Bytes, header::{CONTENT_TYPE, CACHE_CONTROL, UPGRADE}};
|
||||
use http_body_util::Full;
|
||||
use tokio_tungstenite::accept_async;
|
||||
use tokio_tungstenite::tungstenite::handshake::server::create_response_with_body;
|
||||
|
||||
println!("Serving: {}", request.uri().path());
|
||||
|
||||
match request.uri().path() {
|
||||
"" => Ok(Response::new(args.cache.html())),
|
||||
".css" => Ok(Response::new(args.cache.css())),
|
||||
".js" => Ok(Response::new(args.cache.js())),
|
||||
".wasm" => Ok(Response::new(args.cache.wasm())),
|
||||
//"favicon.png" => Ok(Response::new(String::new())),
|
||||
"ws" => {
|
||||
if request.headers().get(hyper::header::UPGRADE).map(|h| h == "websocket").unwrap_or(false) {
|
||||
"/.css" => Ok(Response::builder()
|
||||
.header(CONTENT_TYPE, "text/css")
|
||||
.header(CACHE_CONTROL, "no-cache")
|
||||
.body(Full::new(Bytes::from(args.cache.css()))).unwrap()),
|
||||
|
||||
"/.js" => Ok(Response::builder()
|
||||
.header(CONTENT_TYPE, "text/javascript")
|
||||
.header(CACHE_CONTROL, "no-cache")
|
||||
.body(Full::new(Bytes::from(args.cache.js()))).unwrap()),
|
||||
|
||||
"/favicon.png" => Ok(Response::builder()
|
||||
.header(CONTENT_TYPE, "image/png")
|
||||
.body(Full::new(Bytes::from(args.cache.favicon()))).unwrap()),
|
||||
|
||||
_ => {
|
||||
if request.headers().get(UPGRADE).map(|h| h == "websocket").unwrap_or(false) {
|
||||
let response = create_response_with_body(&request, || Full::new(Bytes::new())).unwrap();
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
match hyper::upgrade::on(request).await {
|
||||
Ok(upgraded) => {
|
||||
match upgraded.downcast::<TokioIo<TlsStream<TcpStream>>>() {
|
||||
Ok(parts) => {
|
||||
match accept_async(parts.io.into_inner()).await {
|
||||
Ok(ws_stream) => { handle_ws(ws_stream, args).await }
|
||||
Err(_) => { }
|
||||
Ok(ws_stream) => {
|
||||
println!("here");
|
||||
handle_ws(ws_stream, args).await
|
||||
}
|
||||
Err(e) => { println!("ws not accepted: {}", e.to_string()); }
|
||||
}
|
||||
}
|
||||
Err(_) => { }
|
||||
Err(_) => { println!("transfer error"); }
|
||||
}
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
Err(e) => { println!("upgrade error: {}", e.to_string()); }
|
||||
}
|
||||
});
|
||||
|
||||
Ok(response)
|
||||
} else {
|
||||
Ok(Response::builder()
|
||||
.status(101)
|
||||
.header(hyper::header::UPGRADE, "websocket")
|
||||
.body(String::new())
|
||||
.unwrap())
|
||||
.header(CONTENT_TYPE, "text/html")
|
||||
.header(CACHE_CONTROL, "no-cache")
|
||||
.body(Full::new(Bytes::from(args.cache.html()))).unwrap())
|
||||
}
|
||||
}
|
||||
_ => Ok(Response::new(String::new())),
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,11 +173,12 @@ async fn handle_http(stream:system::net::tls::TlsStream, addr:SocketAddr, args:H
|
||||
|
||||
let io = TokioIo::new(stream.to_stream());
|
||||
|
||||
http1::Builder::new()
|
||||
let conn = http1::Builder::new()
|
||||
.serve_connection(io, service_fn(move |req| {
|
||||
service_http(req, args.clone())
|
||||
}))
|
||||
.await.ok();
|
||||
}));
|
||||
|
||||
conn.with_upgrades().await.ok();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -195,9 +220,12 @@ async fn main()
|
||||
let cache = WebCache::init();
|
||||
|
||||
let mut server = TlsServer::new();
|
||||
server.add_cert("omen.kirisame.com", "cert/fullchain.pem", "cert/privkey.pem").await.ok();
|
||||
if server.add_cert("omen.kirisame.com", "cert/fullchain.pem", "cert/privkey.pem").await.is_ok() {
|
||||
println!("Loaded cert file.");
|
||||
}
|
||||
match server.bind("0.0.0.0:38612").await {
|
||||
Ok(_) => {
|
||||
println!("Listener bind successful.");
|
||||
tokio::spawn(async move {
|
||||
while server.accept(handle_http, HttpServiceArgs {
|
||||
bus:bus.connect().unwrap(),
|
||||
|
21
server/src/system/cache/mod.rs
vendored
21
server/src/system/cache/mod.rs
vendored
@ -6,7 +6,7 @@ struct WebCacheData {
|
||||
html:String,
|
||||
css:String,
|
||||
js:String,
|
||||
wasm:String,
|
||||
favicon:Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -21,7 +21,7 @@ impl WebCache {
|
||||
let mut html = String::new();
|
||||
let mut css = String::new();
|
||||
let mut js = String::new();
|
||||
let mut wasm = String::new();
|
||||
let mut favicon = Vec::<u8>::new();
|
||||
|
||||
// Cache html file
|
||||
match File::open("www/.html") {
|
||||
@ -45,23 +45,22 @@ impl WebCache {
|
||||
match File::open("www/.js") {
|
||||
Ok(mut file) => {
|
||||
file.read_to_string(&mut js).ok();
|
||||
js = minimize_whitespace(&js);
|
||||
//js = minimize_whitespace(&js);
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
|
||||
// Cache wasm file
|
||||
match File::open("www/.wasm") {
|
||||
// Cache favicon file
|
||||
match File::open("www/favicon.png") {
|
||||
Ok(mut file) => {
|
||||
file.read_to_string(&mut wasm).ok();
|
||||
wasm = minimize_whitespace(&wasm);
|
||||
file.read_to_end(&mut favicon).ok();
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
|
||||
Self {
|
||||
data:Arc::new(RwLock::new(WebCacheData {
|
||||
html, css, js, wasm
|
||||
html, css, js, favicon,
|
||||
})),
|
||||
}
|
||||
}
|
||||
@ -90,11 +89,11 @@ impl WebCache {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wasm(&self) -> String
|
||||
pub fn favicon(&self) -> Vec<u8>
|
||||
{
|
||||
match self.data.read() {
|
||||
Ok(reader) => reader.wasm.to_string(),
|
||||
Err(_) => String::new(),
|
||||
Ok(reader) => reader.favicon.clone(),
|
||||
Err(_) => Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,8 +63,7 @@ impl CertificateStore {
|
||||
key:key.unwrap(),
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
@ -73,8 +72,7 @@ impl CertificateStore {
|
||||
{
|
||||
if self.certs.unset(domain) {
|
||||
Ok(())
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ impl Server for TlsServer {
|
||||
else { Err(()) }
|
||||
}
|
||||
Err(e) => {
|
||||
println!("[conn failure] {:}", e.to_string());
|
||||
println!("[connection failure] {:}", e.to_string());
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
62
www/.css
62
www/.css
@ -1,8 +1,8 @@
|
||||
* {
|
||||
*{
|
||||
box-sizing:border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
html{
|
||||
display:block;
|
||||
position:relative;
|
||||
width:100%;
|
||||
@ -12,7 +12,7 @@ html {
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
body{
|
||||
display:flex;
|
||||
position:relative;
|
||||
flex-flow:row nowrap;
|
||||
@ -27,7 +27,7 @@ body {
|
||||
font-family:sans-serif;
|
||||
}
|
||||
|
||||
body>nav {
|
||||
body>nav{
|
||||
display:block;
|
||||
position:relative;
|
||||
width:9rem;
|
||||
@ -35,7 +35,7 @@ body>nav {
|
||||
|
||||
background-color:#282828;
|
||||
}
|
||||
body>nav>button {
|
||||
body>nav>button{
|
||||
display:block;
|
||||
position:relative;
|
||||
width:100%;
|
||||
@ -49,12 +49,12 @@ body>nav>button {
|
||||
background-color:#282828;
|
||||
border:0;
|
||||
color:#c0c0c0;
|
||||
} body>nav>button:hover {
|
||||
} body>nav>button:hover{
|
||||
background-color:#383838;
|
||||
color:#e0e0e0;
|
||||
}
|
||||
|
||||
body>nav>header {
|
||||
body>nav>header{
|
||||
display:block;
|
||||
position:relative;
|
||||
width:100%;
|
||||
@ -70,7 +70,7 @@ body>nav>header {
|
||||
border-bottom:1px solid #404040;
|
||||
}
|
||||
|
||||
main {
|
||||
main{
|
||||
display:block;
|
||||
position:relative;
|
||||
width:100%;
|
||||
@ -81,7 +81,7 @@ main {
|
||||
background-color:#202020;
|
||||
}
|
||||
|
||||
main>nav {
|
||||
main>nav{
|
||||
display:flex;
|
||||
position:relative;
|
||||
flex-flow:row nowrap;
|
||||
@ -91,9 +91,10 @@ main>nav {
|
||||
height:3rem;
|
||||
|
||||
background-color:#282828;
|
||||
border-bottom:1px solid #404040;
|
||||
}
|
||||
|
||||
main>nav>section:first-child {
|
||||
main>nav>section:first-child{
|
||||
display:flex;
|
||||
position:relative;
|
||||
flex-flow:row nowrap;
|
||||
@ -102,7 +103,7 @@ main>nav>section:first-child {
|
||||
height:100%;
|
||||
}
|
||||
|
||||
main>nav>section:last-child {
|
||||
main>nav>section:last-child{
|
||||
display:flex;
|
||||
position:relative;
|
||||
flex-flow:row nowrap;
|
||||
@ -112,7 +113,7 @@ main>nav>section:last-child {
|
||||
flex-grow:1;
|
||||
}
|
||||
|
||||
main>nav>section>button {
|
||||
main>nav>section>button{
|
||||
display:block;
|
||||
position:relative;
|
||||
width:auto;
|
||||
@ -125,12 +126,13 @@ main>nav>section>button {
|
||||
background-color:#282828;
|
||||
border:0;
|
||||
color:#c0c0c0;
|
||||
} main>nav>section>button:hover {
|
||||
}
|
||||
main>nav>section>button:hover{
|
||||
background-color:#383838;
|
||||
color:#e0e0e0;
|
||||
}
|
||||
|
||||
main>nav>section>div {
|
||||
main>nav>section>div{
|
||||
display:block;
|
||||
position:relative;
|
||||
width:auto;
|
||||
@ -144,14 +146,14 @@ main>nav>section>div {
|
||||
color:#e0e0e0;
|
||||
}
|
||||
|
||||
main table {
|
||||
main table{
|
||||
width:100%;
|
||||
border-collapse:collapse;
|
||||
}
|
||||
main table tr {
|
||||
main table tr{
|
||||
height:2.5rem;
|
||||
}
|
||||
main table th {
|
||||
main table th{
|
||||
padding:0 1rem 0 1rem;
|
||||
|
||||
text-align:center;
|
||||
@ -162,7 +164,7 @@ main table th {
|
||||
color:#f0f0f0;
|
||||
border-bottom:1px solid #404040;
|
||||
}
|
||||
main table td {
|
||||
main table td{
|
||||
height:100%;
|
||||
padding:0 1rem 0 1rem;
|
||||
|
||||
@ -171,7 +173,7 @@ main table td {
|
||||
background-color:#303030;
|
||||
color:#f0f0f0;
|
||||
}
|
||||
main table td:last-child {
|
||||
main table td:last-child{
|
||||
display:flex;
|
||||
flex-flow:row nowrap;
|
||||
align-items:flex-start;
|
||||
@ -179,12 +181,12 @@ main table td:last-child {
|
||||
flex-grow:1;
|
||||
}
|
||||
|
||||
main table td>img {
|
||||
main table td>img{
|
||||
height:2rem;
|
||||
vertical-align:middle;
|
||||
}
|
||||
|
||||
main table td:last-child>button {
|
||||
main table td:last-child>button{
|
||||
display:block;
|
||||
position:relative;
|
||||
width:auto;
|
||||
@ -198,11 +200,12 @@ main table td:last-child>button {
|
||||
background-color:#303030;
|
||||
border:0;
|
||||
color:#e0e0e0;
|
||||
} main table td:last-child>button:hover {
|
||||
}
|
||||
main table td:last-child>button:hover{
|
||||
background-color:#343434;
|
||||
}
|
||||
|
||||
main>canvas {
|
||||
main>canvas{
|
||||
display:block;
|
||||
position:relative;
|
||||
width:100%;
|
||||
@ -213,8 +216,7 @@ main>canvas {
|
||||
background-color:#202020;
|
||||
}
|
||||
|
||||
/* Scene:game */
|
||||
main.game>div.sidemenu {
|
||||
main.game>div.sidemenu{
|
||||
display:flex;
|
||||
position:absolute;
|
||||
bottom:0px;
|
||||
@ -232,7 +234,7 @@ main.game>div.sidemenu {
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
main.game>div.sidemenu>button {
|
||||
main.game>div.sidemenu>button{
|
||||
display:block;
|
||||
position:relative;
|
||||
padding:1rem;
|
||||
@ -245,10 +247,12 @@ main.game>div.sidemenu>button {
|
||||
font-size:1rem;
|
||||
|
||||
cursor:pointer;
|
||||
} main.game>div.sidemenu>button:hover {
|
||||
}
|
||||
main.game>div.sidemenu>button:hover{
|
||||
background-color:#404040;
|
||||
} main.game>div.sidemenu>button.warn:hover {
|
||||
}
|
||||
main.game>div.sidemenu>button.warn:hover{
|
||||
background-color:#602020;
|
||||
}
|
||||
|
||||
span.text-system { color:#909090; }
|
||||
span.text-system{color:#909090;}
|
||||
|
@ -6,8 +6,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="application-name" content="Omen">
|
||||
<meta name="description" content="Abstract strategy game mixing chess and shogi on a hexagon grid.">
|
||||
<link rel="icon" href="/favicon.png">
|
||||
<link rel="stylesheet" href=".css">
|
||||
<script src=".js" async></script>
|
||||
<script src=".js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>This application requires JavaScript to function.</noscript>
|
||||
|
300
www/.js
300
www/.js
@ -1 +1,299 @@
|
||||
WebAssembly.instantiateStreaming(fetch(".wasm"),ap);
|
||||
let MAIN = null;
|
||||
let MENU = null;
|
||||
let SCENE = null;
|
||||
|
||||
let CONNECTED = false;
|
||||
let SOCKET = null;
|
||||
let USER = null;
|
||||
let CONTEXT = null;
|
||||
|
||||
let UI = {
|
||||
text:function(value) {
|
||||
return document.createTextNode(value);
|
||||
},
|
||||
|
||||
button:function(text, callback) {
|
||||
let b = document.createElement("button");
|
||||
b.innerText = text;
|
||||
if(callback !== null) { b.addEventListener("click", callback); }
|
||||
return b;
|
||||
},
|
||||
|
||||
div:function(children) {
|
||||
let div = document.createElement("div");
|
||||
for(child of children) { div.appendChild(child); }
|
||||
return div;
|
||||
},
|
||||
|
||||
table:function(header, rows) {
|
||||
let table = document.createElement("table");
|
||||
let tbody = document.createElement("tbody");
|
||||
|
||||
if(header !== null) {
|
||||
let row = document.createElement("tr");
|
||||
for(head of header) {
|
||||
let cell = document.createElement("th");
|
||||
cell.innerText = head;
|
||||
row.appendChild(cell);
|
||||
}
|
||||
tbody.appendChild(row);
|
||||
}
|
||||
|
||||
for(row of rows) {
|
||||
let tr = document.createElement("tr");
|
||||
for(node of row) {
|
||||
let cell = document.createElement("td");
|
||||
if(Array.isArray(node)) { for(item of node) { cell.appendChild(item); } }
|
||||
else { cell.appendChild(node); }
|
||||
tr.appendChild(cell);
|
||||
}
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
|
||||
table.appendChild(tbody);
|
||||
return table;
|
||||
},
|
||||
|
||||
mainnav:function(left_children, right_children) {
|
||||
let header = document.createElement("nav");
|
||||
let left = document.createElement("section");
|
||||
for(child of left_children) { left.appendChild(child); }
|
||||
|
||||
let right = document.createElement("section");
|
||||
for(child of right_children) { right.appendChild(child); }
|
||||
|
||||
header.appendChild(left);
|
||||
header.appendChild(right);
|
||||
return header;
|
||||
},
|
||||
|
||||
mainmenu:function() {
|
||||
if(SOCKET !== null) {
|
||||
MENU.appendChild(UI.button("Online", function() { LOAD(SCENE.Online); }));
|
||||
MENU.appendChild(UI.button("Continue", function() { LOAD(SCENE.Continue); }));
|
||||
MENU.appendChild(UI.button("Join", function() { LOAD(SCENE.Continue); }));
|
||||
MENU.appendChild(UI.button("Live", function() { LOAD(SCENE.Continue); }));
|
||||
MENU.appendChild(UI.button("History", function() { LOAD(SCENE.Continue); }));
|
||||
MENU.appendChild(UI.button("Guide", function() { LOAD(SCENE.Continue); }));
|
||||
MENU.appendChild(UI.button("About", function() { LOAD(SCENE.Continue); }));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function RECONNECT() {
|
||||
if(SOCKET === null) {
|
||||
console.log("Connecting..");
|
||||
SOCKET = new WebSocket("wss://omen.kirisame.com:38612");
|
||||
SOCKET.binaryType = 'blob';
|
||||
SOCKET.addEventListener("error", function(e) {
|
||||
console.log("Failed");
|
||||
SOCKET = null;
|
||||
});
|
||||
SOCKET.addEventListener("open", function(e) {
|
||||
if(SOCKET.readyState === WebSocket.OPEN) {
|
||||
console.log("Connected.");
|
||||
|
||||
SOCKET.addEventListener("message", function(e) {
|
||||
MESSAGE(e.data);
|
||||
});
|
||||
|
||||
SOCKET.addEventListener("close", function(e) {
|
||||
console.log("Closed.");
|
||||
SOCKET = null;
|
||||
RECONNECT();
|
||||
});
|
||||
|
||||
RESUME();
|
||||
SOCKET.send(new Uint8Array([ 0 ]));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function RESUME() {
|
||||
LOAD(SCENES.Online);
|
||||
}
|
||||
|
||||
const SCENES = {
|
||||
Init:{
|
||||
load:function() {
|
||||
LOAD(SCENES.Offline);
|
||||
RECONNECT();
|
||||
return true;
|
||||
},
|
||||
},
|
||||
|
||||
Offline:{
|
||||
load:function() {
|
||||
MENU.appendChild(UI.button("Reconnect", function() { RECONNECT(); }))
|
||||
return true;
|
||||
},
|
||||
},
|
||||
|
||||
Online:{
|
||||
load:function() {
|
||||
UI.mainmenu();
|
||||
MAIN.appendChild(UI.mainnav([
|
||||
UI.button("Start", null),
|
||||
], [
|
||||
UI.div([UI.text("0 - 0 of 0")]),
|
||||
UI.button("◀", null),
|
||||
UI.button("▶", null),
|
||||
]));
|
||||
|
||||
let table = document.createElement("table");
|
||||
table.setAttribute("id", "content");
|
||||
MAIN.appendChild(table);
|
||||
|
||||
this.refresh();
|
||||
return true;
|
||||
},
|
||||
refresh:function() {
|
||||
let request = new Uint8Array();
|
||||
|
||||
//SERVER.send()
|
||||
|
||||
let cb = function() {
|
||||
let table = document.getElementById("content");
|
||||
MAIN.removeChild(table);
|
||||
|
||||
let data = [
|
||||
[ UI.text("Player1"), UI.text("Player2"), UI.text("0"), UI.text("Ha1-D ◈ Ba1-M"), UI.text("0"), [ UI.button("Join", null), UI.button("Spectate", null) ] ],
|
||||
];
|
||||
|
||||
MAIN.appendChild(UI.table(
|
||||
[ "Dawn", "Dusk", "Turn", "Move", "Spectators", "" ],
|
||||
data,
|
||||
));
|
||||
};
|
||||
cb();
|
||||
},
|
||||
},
|
||||
|
||||
Continue:{
|
||||
load:function() {
|
||||
if(USER === null) return false;
|
||||
UI.mainmenu();
|
||||
MAIN.appendChild(UI.mainnav([
|
||||
UI.button("Start", null),
|
||||
], [
|
||||
UI.button("◀", null),
|
||||
UI.button("▶", null),
|
||||
]));
|
||||
return true;
|
||||
},
|
||||
refresh:function() {
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
Join:{
|
||||
load:function() {
|
||||
if(USER === null) return false;
|
||||
UI.mainmenu();
|
||||
MAIN.appendChild(UI.mainnav([
|
||||
UI.button("Start", null),
|
||||
], [
|
||||
UI.button("◀", null),
|
||||
UI.button("▶", null),
|
||||
]));
|
||||
return true;
|
||||
},
|
||||
refresh:function() {
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
Live:{
|
||||
load:function() {
|
||||
UI.mainmenu();
|
||||
MAIN.appendChild(UI.mainnav([
|
||||
UI.button("Start", null),
|
||||
], [
|
||||
UI.button("◀", null),
|
||||
UI.button("▶", null),
|
||||
]));
|
||||
return true;
|
||||
},
|
||||
refresh:function() {
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
History:{
|
||||
load:function() {
|
||||
UI.mainmenu();
|
||||
MAIN.appendChild(UI.mainnav([ ], [
|
||||
UI.button("◀", null),
|
||||
UI.button("▶", null),
|
||||
]));
|
||||
return true;
|
||||
},
|
||||
refresh:function() {
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
Guide:{
|
||||
load:function() {
|
||||
UI.mainmenu();
|
||||
return true;
|
||||
},
|
||||
refresh:function() {
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
About:{
|
||||
load:function() {
|
||||
UI.mainmenu();
|
||||
return true;
|
||||
},
|
||||
refresh:function() {
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
Game:{
|
||||
load:function() {
|
||||
MENU.appendChild(UI.button("Back", function() { LOAD(SCENES.Online) }));
|
||||
MENU.appendChild(UI.button("Retire", function() { }));
|
||||
return true;
|
||||
},
|
||||
unload:function() {
|
||||
|
||||
},
|
||||
refresh:function() {
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function REBUILD() {
|
||||
MENU = document.createElement("nav");
|
||||
let title = document.createElement("header");
|
||||
title.innerText = "Omen";
|
||||
MENU.appendChild(title);
|
||||
|
||||
MAIN = document.createElement("main");
|
||||
|
||||
document.body.appendChild(MENU);
|
||||
document.body.appendChild(MAIN);
|
||||
}
|
||||
|
||||
function MESSAGE() {
|
||||
|
||||
}
|
||||
|
||||
function LOAD(scene) {
|
||||
if(SCENE.unload !== undefined) { SCENE.unload(); }
|
||||
while(document.body.lastChild !== null) { document.body.removeChild(document.body.lastChild); }
|
||||
REBUILD();
|
||||
SCENE = scene;
|
||||
if(!SCENE.load()) { LOAD(SCENES.Online); }
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
SCENE = SCENES.Offline;
|
||||
LOAD(SCENES.Init);
|
||||
});
|
||||
|
BIN
www/favicon.png
Normal file
BIN
www/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
Loading…
x
Reference in New Issue
Block a user