From 7ea088737dd83cbb8054cbc07e48b604c02745b0 Mon Sep 17 00:00:00 2001 From: yukirij Date: Thu, 8 Aug 2024 00:37:58 -0700 Subject: [PATCH] Remove wasm; add js client. --- .gitignore | 1 + Cargo.toml | 1 - client-web/Cargo.toml | 24 -- client-web/src/app/mod.rs | 67 ------ client-web/src/app/scene/list_online.rs | 79 ------- client-web/src/app/scene/mod.rs | 3 - client-web/src/app/scene/ui/mod.rs | 27 --- client-web/src/app/scene/util.rs | 56 ----- client-web/src/app/session/mod.rs | 23 -- client-web/src/ext.rs | 4 - client-web/src/lib.rs | 32 --- client-web/src/prelude.rs | 8 - server/Cargo.toml | 1 + server/src/main.rs | 86 ++++--- server/src/system/cache/mod.rs | 21 +- server/src/system/net/certstore/mod.rs | 6 +- server/src/system/net/tls.rs | 2 +- www/.css | 62 ++--- www/.html | 3 +- www/.js | 300 +++++++++++++++++++++++- www/favicon.png | Bin 0 -> 3227 bytes 21 files changed, 406 insertions(+), 400 deletions(-) delete mode 100644 client-web/Cargo.toml delete mode 100644 client-web/src/app/mod.rs delete mode 100644 client-web/src/app/scene/list_online.rs delete mode 100644 client-web/src/app/scene/mod.rs delete mode 100644 client-web/src/app/scene/ui/mod.rs delete mode 100644 client-web/src/app/scene/util.rs delete mode 100644 client-web/src/app/session/mod.rs delete mode 100644 client-web/src/ext.rs delete mode 100644 client-web/src/lib.rs delete mode 100644 client-web/src/prelude.rs create mode 100644 www/favicon.png diff --git a/.gitignore b/.gitignore index 8f96d6f..1713fd6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target +/data Cargo.lock *.pem diff --git a/Cargo.toml b/Cargo.toml index 79163af..1f5d947 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,5 @@ resolver = "2" members = [ "game", - "client-web", "server", ] diff --git a/client-web/Cargo.toml b/client-web/Cargo.toml deleted file mode 100644 index cd50bd7..0000000 --- a/client-web/Cargo.toml +++ /dev/null @@ -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', -] diff --git a/client-web/src/app/mod.rs b/client-web/src/app/mod.rs deleted file mode 100644 index 1834186..0000000 --- a/client-web/src/app/mod.rs +++ /dev/null @@ -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, -} -impl App { - pub fn init() -> Result - { - 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(&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 => { } - } - } -} diff --git a/client-web/src/app/scene/list_online.rs b/client-web/src/app/scene/list_online.rs deleted file mode 100644 index 5819865..0000000 --- a/client-web/src/app/scene/list_online.rs +++ /dev/null @@ -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 => { } - } - } -} diff --git a/client-web/src/app/scene/mod.rs b/client-web/src/app/scene/mod.rs deleted file mode 100644 index c0e1d3b..0000000 --- a/client-web/src/app/scene/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod util; -pub mod ui; -mod list_online; pub use list_online::*; diff --git a/client-web/src/app/scene/ui/mod.rs b/client-web/src/app/scene/ui/mod.rs deleted file mode 100644 index a047b25..0000000 --- a/client-web/src/app/scene/ui/mod.rs +++ /dev/null @@ -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 -{ - match document.create_element("button") { - Ok(element) => { - element.set_text_content(Some(text)); - - let cl = Closure::wrap(Box::new(callback) as Box); - 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 -{ - match document.create_element("div") { - Ok(element) => { - element.set_text_content(Some(text)); - Ok(element) - } - Err(_) => Err(()) - } -} diff --git a/client-web/src/app/scene/util.rs b/client-web/src/app/scene/util.rs deleted file mode 100644 index c22a9f9..0000000 --- a/client-web/src/app/scene/util.rs +++ /dev/null @@ -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::(); } - 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::(); } - 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::(); } - None => { } - } - }) { - Ok(button) => { app.menu.append_child(&button).ok(); } - Err(_) => { } - } - - - } None => { } - } -} diff --git a/client-web/src/app/session/mod.rs b/client-web/src/app/session/mod.rs deleted file mode 100644 index c8157dc..0000000 --- a/client-web/src/app/session/mod.rs +++ /dev/null @@ -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], - } - } -} diff --git a/client-web/src/ext.rs b/client-web/src/ext.rs deleted file mode 100644 index dd2eed0..0000000 --- a/client-web/src/ext.rs +++ /dev/null @@ -1,4 +0,0 @@ -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -extern "C" { } diff --git a/client-web/src/lib.rs b/client-web/src/lib.rs deleted file mode 100644 index be58054..0000000 --- a/client-web/src/lib.rs +++ /dev/null @@ -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> = 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::(); - } None => { } - } - } - } - Err(_) => { } - } - Ok(()) -} diff --git a/client-web/src/prelude.rs b/client-web/src/prelude.rs deleted file mode 100644 index c1113e9..0000000 --- a/client-web/src/prelude.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub trait Scene { - fn load(); - fn unload() { } - - fn hover() { } - fn click() { } - fn keydown() { } -} diff --git a/server/Cargo.toml b/server/Cargo.toml index 5aff0f6..b0eb50a 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -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" } diff --git a/server/src/main.rs b/server/src/main.rs index 2bcc9e9..6ea2e23 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -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>, 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>, args:Htt ws_stream.close(None).await.ok(); } -async fn service_http(request:hyper::Request, args:HttpServiceArgs) -> Result, std::convert::Infallible> +async fn service_http(request:hyper::Request, args:HttpServiceArgs) -> Result>, 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) { - match hyper::upgrade::on(request).await { - Ok(upgraded) => { - match upgraded.downcast::>>() { - Ok(parts) => { - match accept_async(parts.io.into_inner()).await { - Ok(ws_stream) => { handle_ws(ws_stream, args).await } - Err(_) => { } + "/.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::>>() { + Ok(parts) => { + match accept_async(parts.io.into_inner()).await { + Ok(ws_stream) => { + println!("here"); + handle_ws(ws_stream, args).await + } + Err(e) => { println!("ws not accepted: {}", e.to_string()); } + } } + Err(_) => { println!("transfer error"); } } - Err(_) => { } } + Err(e) => { println!("upgrade error: {}", e.to_string()); } } - Err(_) => { } - } + }); + + Ok(response) + } else { + Ok(Response::builder() + .header(CONTENT_TYPE, "text/html") + .header(CACHE_CONTROL, "no-cache") + .body(Full::new(Bytes::from(args.cache.html()))).unwrap()) } - Ok(Response::builder() - .status(101) - .header(hyper::header::UPGRADE, "websocket") - .body(String::new()) - .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(), diff --git a/server/src/system/cache/mod.rs b/server/src/system/cache/mod.rs index 1de2a17..29c5cd3 100644 --- a/server/src/system/cache/mod.rs +++ b/server/src/system/cache/mod.rs @@ -6,7 +6,7 @@ struct WebCacheData { html:String, css:String, js:String, - wasm:String, + favicon:Vec, } #[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::::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 { match self.data.read() { - Ok(reader) => reader.wasm.to_string(), - Err(_) => String::new(), + Ok(reader) => reader.favicon.clone(), + Err(_) => Vec::new(), } } } diff --git a/server/src/system/net/certstore/mod.rs b/server/src/system/net/certstore/mod.rs index 7d44285..4b2997e 100644 --- a/server/src/system/net/certstore/mod.rs +++ b/server/src/system/net/certstore/mod.rs @@ -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(()) } } diff --git a/server/src/system/net/tls.rs b/server/src/system/net/tls.rs index b6918b6..702c56a 100644 --- a/server/src/system/net/tls.rs +++ b/server/src/system/net/tls.rs @@ -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(()) } } diff --git a/www/.css b/www/.css index 3accc10..ec562b1 100644 --- a/www/.css +++ b/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;} diff --git a/www/.html b/www/.html index 302352f..88845bf 100644 --- a/www/.html +++ b/www/.html @@ -6,8 +6,9 @@ + - + diff --git a/www/.js b/www/.js index 3162079..f938ef9 100644 --- a/www/.js +++ b/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); +}); diff --git a/www/favicon.png b/www/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..9ceced97bf6dd0eaec2ba70d7e11cd7d010f612f GIT binary patch literal 3227 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