diff --git a/server/src/app/mod.rs b/server/src/app/mod.rs index 3bb0412..360d556 100644 --- a/server/src/app/mod.rs +++ b/server/src/app/mod.rs @@ -231,9 +231,9 @@ impl App { )).await.ok(); } - QRPacketData::RUserInfo(response) => { + QRPacketData::RUserProfile(response) => { socket.send(Message::Binary( - encode_response(CODE_USER_INFO, response.encode()) + encode_response(CODE_USER_PROFILE, response.encode()) )).await.ok(); } diff --git a/server/src/manager/data.rs b/server/src/manager/data.rs index 30b8c5c..fc54036 100644 --- a/server/src/manager/data.rs +++ b/server/src/manager/data.rs @@ -414,18 +414,6 @@ pub async fn thread_system(mut app:App, bus:Bus) QRPacketData::QSessionList(request) => { app.log.log("Request: Session List"); - println!("range {} {}", request.filter.start, request.filter.count); - - if let Some(value) = request.filter.is_complete { - println!("is_complete {}", value); - } - if let Some(value) = request.filter.is_live { - println!("is_live {}", value); - } - if let Some(value) = request.filter.is_player { - println!("is_player {}", value); - } - let mut response = PacketSessionListResponse::new(); response.records = filter_sessions(&app, user_id, request.filter); Some(QRPacket::new(qr.id, QRPacketData::RSessionList(response))) @@ -923,11 +911,11 @@ pub async fn thread_system(mut app:App, bus:Bus) Some(QRPacket::new(qr.id, QRPacketData::RUserList(response))) } - // UserInfo - QRPacketData::QUserInfo(request) => { - app.log.log("Request: User Info"); + // UserProfile + QRPacketData::QUserProfile(request) => { + app.log.log("Request: User Profile"); - let mut response = PacketUserInfoResponse::new(); + let mut response = PacketUserProfileResponse::new(); if let Some(uid) = app.user_handle.get(&request.handle.to_lowercase().as_bytes()).cloned() { response.status = STATUS_OK; @@ -938,14 +926,22 @@ pub async fn thread_system(mut app:App, bus:Bus) } } else { response.status = STATUS_ERROR; - } + }; // Get user sessions if response.status == STATUS_OK { - + + let mut filter = SessionFilter::new(); + filter.count = 30; + filter.player = [Some(request.handle.to_lowercase()), None]; + + response.history = filter_sessions(&app, user_id, filter); + response.history.sort_by(|a, b| { + a.is_complete.cmp(&b.is_complete) + }); } - Some(QRPacket::new(qr.id, QRPacketData::RUserInfo(response))) + Some(QRPacket::new(qr.id, QRPacketData::RUserProfile(response))) } // InviteAcquire diff --git a/server/src/manager/ws.rs b/server/src/manager/ws.rs index afcd178..f519efc 100644 --- a/server/src/manager/ws.rs +++ b/server/src/manager/ws.rs @@ -198,6 +198,16 @@ pub async fn handle_ws(ws:WebSocketStream>, args:HttpServiceAr ).ok(); } + CODE_USER_PROFILE => match PacketUserProfile::decode(&data, &mut index) { + Ok(packet) => { + args.bus.send( + bus_ds, + QRPacket::new(conn_id, QRPacketData::QUserProfile(packet)) + ).ok(); + } + Err(_) => { println!("error: packet decode failed."); } + } + CODE_INVITE_LIST => { args.bus.send( bus_ds, diff --git a/server/src/protocol/code.rs b/server/src/protocol/code.rs index f67a2a9..7122cdc 100644 --- a/server/src/protocol/code.rs +++ b/server/src/protocol/code.rs @@ -43,7 +43,7 @@ pub const CODE_CHALLENGE_ANSWER :u16 = 0x0061; pub const CODE_CHALLENGE_LIST :u16 = 0x0062; pub const CODE_USER_LIST :u16 = 0x0100; -pub const CODE_USER_INFO :u16 = 0x0101; +pub const CODE_USER_PROFILE :u16 = 0x0102; pub const CODE_INVITE_LIST :u16 = 0x0180; pub const CODE_INVITE_ACQUIRE :u16 = 0x0181; diff --git a/server/src/protocol/mod.rs b/server/src/protocol/mod.rs index eeb77f6..5bdcbf5 100644 --- a/server/src/protocol/mod.rs +++ b/server/src/protocol/mod.rs @@ -29,8 +29,8 @@ pub enum QRPacketData { QUserList, RUserList(PacketUserListResponse), - QUserInfo(PacketUserInfo), - RUserInfo(PacketUserInfoResponse), + QUserProfile(PacketUserProfile), + RUserProfile(PacketUserProfileResponse), QAccountInfo(PacketAccountInfo), RAccountInfo(PacketAccountInfoResponse), diff --git a/server/src/protocol/packet/mod.rs b/server/src/protocol/packet/mod.rs index a2b9100..050fddc 100644 --- a/server/src/protocol/packet/mod.rs +++ b/server/src/protocol/packet/mod.rs @@ -19,7 +19,7 @@ mod challenge_answer; pub use challenge_answer::*; mod challenge_list; pub use challenge_list::*; mod user_list; pub use user_list::*; -mod user_info; pub use user_info::*; +mod user_profile; pub use user_profile::*; mod account_info; pub use account_info::*; mod account_update; pub use account_update::*; diff --git a/server/src/protocol/packet/user_info.rs b/server/src/protocol/packet/user_profile.rs similarity index 88% rename from server/src/protocol/packet/user_info.rs rename to server/src/protocol/packet/user_profile.rs index 81914dc..2c18081 100644 --- a/server/src/protocol/packet/user_info.rs +++ b/server/src/protocol/packet/user_profile.rs @@ -6,11 +6,11 @@ use super::{ }; #[derive(Clone)] -pub struct PacketUserInfo { +pub struct PacketUserProfile { pub handle:String, } -impl PacketUserInfo { +impl PacketUserProfile { pub fn new() -> Self { Self { @@ -18,7 +18,7 @@ impl PacketUserInfo { } } } -impl Packet for PacketUserInfo { +impl Packet for PacketUserProfile { type Data = Self; fn decode(data:&Vec, index:&mut usize) -> Result @@ -35,13 +35,13 @@ impl Packet for PacketUserInfo { } #[derive(Clone)] -pub struct PacketUserInfoResponse { +pub struct PacketUserProfileResponse { pub status:u16, pub handle:String, pub is_online:bool, pub history:Vec, } -impl PacketUserInfoResponse { +impl PacketUserProfileResponse { pub fn new() -> Self { Self { @@ -52,7 +52,7 @@ impl PacketUserInfoResponse { } } } -impl Packet for PacketUserInfoResponse { +impl Packet for PacketUserProfileResponse { type Data = Self; fn encode(&self) -> Vec diff --git a/www/css/profile.css b/www/css/profile.css index 7a37b95..605faa2 100644 --- a/www/css/profile.css +++ b/www/css/profile.css @@ -17,9 +17,18 @@ main.profile>header>h1 { line-height: 3rem; font-size: 2.5rem; + color: #909090; +} +main.profile>header>h1.online { color: #f0f0f0; } main.profile>section.history { + width: 100%; flex-grow: 1; + overflow-y: scroll; +} + +main.profile>section.history>table { + width: 100%; } diff --git a/www/css/util.css b/www/css/util.css index 60042ed..f60b912 100644 --- a/www/css/util.css +++ b/www/css/util.css @@ -9,3 +9,7 @@ button.warn { } span.monospace {font-family:monospace;} + +a { + cursor: pointer; +} diff --git a/www/js/const.js b/www/js/const.js index 7b586dd..8db49c6 100644 --- a/www/js/const.js +++ b/www/js/const.js @@ -52,7 +52,7 @@ const OpCode = { ChallengeList :0x0062, UserList :0x0100, - UserInfo :0x0101, + UserProfile :0x0102, InviteList :0x0180, InviteAcquire :0x0181, diff --git a/www/js/scene.js b/www/js/scene.js index aecb191..36834d2 100644 --- a/www/js/scene.js +++ b/www/js/scene.js @@ -467,7 +467,7 @@ const SCENES = { let table = document.createElement("table"); table.setAttribute("id", "content"); table.setAttribute("class", "list session"); - table.appendChild(UI.session_table(this.data)); + table.appendChild(UI.session_table_resume(this.data)); UI.maincontent(table); UI.page_indicator((this.data.length > 0)? 1 : 0, this.data.length, this.data.length); @@ -721,24 +721,32 @@ const SCENES = { Profile:class{ constructor() { this.handle = ""; + this.is_online = false; this.stats = { games_played: 0, }; this.history = [ ]; } preload(data) { - this.data.handle = data.handle; + this.handle = data.handle; + MESSAGE_COMPOSE([ - PACK.u16(OpCode.UserInfo), - PACK.string(this.data.handle, PACK.u8), + PACK.u16(OpCode.UserProfile), + PACK.string(this.handle, PACK.u8), ]); return true; } load(msg) { - if(msg.code != OpCode.UserInfo) { + if(msg.code == OpCode.UserProfile) { + if(msg.data.status != Status.Ok) { + return false; + } + } else { return null; } - this.handle = msg.handle; + this.handle = msg.data.handle; + this.is_online = msg.data.is_online; + this.history = msg.data.history; UI.mainmenu_account(this.handle, "profile"); @@ -755,7 +763,12 @@ const SCENES = { let header = document.createElement("header"); let title = document.createElement("h1"); - title.innerText = this.handle; + if(this.is_online) { + title.innerText = this.handle + " ●"; + title.setAttribute("class", "online"); + } else { + title.innerText = this.handle + " ○"; + } header.appendChild(title); MAIN.appendChild(header); @@ -763,7 +776,11 @@ const SCENES = { let container_history = document.createElement("section"); container_history.setAttribute("class", "history"); + let table_history = document.createElement("table"); + table_history.setAttribute("id", "history"); + table_history.setAttribute("class", "list session"); + table_history.appendChild(UI.session_table_history(this.history)); container_history.appendChild(table_history); MAIN.appendChild(container_history); diff --git a/www/js/system.js b/www/js/system.js index 930ad90..ffbe5da 100644 --- a/www/js/system.js +++ b/www/js/system.js @@ -491,6 +491,100 @@ function MESSAGE(event) { } } break; + case OpCode.UserProfile: { + data = { + status:0, + handle:"", + is_online:false, + history:[ ], + }; + + // Status + result = UNPACK.u16(bytes, index); + index = result.index; + data.status = result.data; + + // Flags + result = UNPACK.u16(bytes, index); + index = result.index; + let flags = result.data; + data.is_online = (flags & 1) != 0; + + // Handle + result = UNPACK.string(bytes, index, UNPACK.u8); + index = result.index; + data.handle = result.data; + + // History + result = UNPACK.u16(bytes, index); + index = result.index; + let length = result.data; + + for(let i = 0; i < length; ++i) { + let record = { + token: new Uint8Array(8), + dawn: "", + dusk: "", + turn: 0, + moves: [ ], + viewers: 0, + player: false, + is_turn: false, + is_complete: 0, + }; + + // Token + if(index <= bytes.length + 8) { + for(let i = 0; i < 8; ++i) { + record.token[i] = bytes[index]; + index += 1; + } + } + + // Flags + result = UNPACK.u32(bytes, index); + index = result.index; + let flags = result.data; + + record.player = flags & 3; + record.is_turn = ((flags >> 2) & 1) != 0; + record.is_complete = ((flags >> 3) & 3); + + // Dawn handle + result = UNPACK.string(bytes, index); + index = result.index; + record.dawn = result.data; + + // Dusk handle + result = UNPACK.string(bytes, index); + index = result.index; + record.dusk = result.data; + + // Turn number + result = UNPACK.u16(bytes, index); + index = result.index; + record.turn = result.data; + + // Last moves + result = UNPACK.u32(bytes, index); + index = result.index; + let moves = result.data; + for(let m = 0; m < 4; ++m) { + if((moves & 0x80) != 0) { + record.moves.push(moves & 0xFF); + moves >>= 8; + } + } + + // Reviewer count + result = UNPACK.u32(bytes, index); + index = result.index; + record.viewers = result.data; + + data.history.push(record); + } + } break; + case OpCode.InviteList: { console.log("RECV InviteList"); diff --git a/www/js/ui.js b/www/js/ui.js index 3888a65..ae303ad 100644 --- a/www/js/ui.js +++ b/www/js/ui.js @@ -10,7 +10,14 @@ const UI = { return document.createTextNode(value); }, - button(text, callback, select=false) { + link(text, callback=null) { + let link = document.createElement("a"); + link.innerText = text; + if(callback !== null) { link.addEventListener("click", callback); } + return link; + }, + + button(text, callback=null, select=false) { let button = document.createElement("button"); button.innerText = text; if(select) { button.setAttribute("class", "selected"); } @@ -246,8 +253,14 @@ const UI = { buttons.push(UI.button(LANG("view"), spectate_callback)); } - let dawn = UI.text(record.dawn); - let dusk = UI.text(record.dusk); + let callback_profile_dawn = function() { SCENE.load(SCENES.Profile, { handle: this.handle }); } + callback_profile_dawn = callback_profile_dawn.bind({ handle: record.dawn }); + + let callback_profile_dusk = function() { SCENE.load(SCENES.Profile, { handle: this.handle }); } + callback_profile_dusk = callback_profile_dusk.bind({ handle: record.dusk }); + + let dawn = UI.link(record.dawn, callback_profile_dawn); + let dusk = UI.link(record.dusk, callback_profile_dusk); let moves = document.createElement("canvas"); setTimeout(UI.draw_play_icons, 10, moves, record.moves); @@ -304,8 +317,13 @@ const UI = { let handle = records[r].dawn; if(records[r].player == 1) { handle = records[r].dusk; } + let callback_profile = function() { SCENE.load(SCENES.Profile, { handle: this.handle }); } + callback_profile = callback_profile.bind({ handle: handle }); + + let link_handle = UI.link(handle, callback_profile); + rows.push([ - UI.text(handle), + link_handle, UI.text(records[r].turn), UI.text(records[r].viewers), buttons, @@ -339,8 +357,14 @@ const UI = { buttons.push(UI.button(LANG("review"), view_callback)); - let dawn = UI.text(record.dawn); - let dusk = UI.text(record.dusk); + let callback_profile_dawn = function() { SCENE.load(SCENES.Profile, { handle: this.handle }); } + callback_profile_dawn = callback_profile_dawn.bind({ handle: record.dawn }); + + let callback_profile_dusk = function() { SCENE.load(SCENES.Profile, { handle: this.handle }); } + callback_profile_dusk = callback_profile_dusk.bind({ handle: record.dusk }); + + let dawn = UI.link(record.dawn, callback_profile_dawn); + let dusk = UI.link(record.dusk, callback_profile_dusk); switch(record.is_complete) { case 1: dawn = UI.span([dawn], "c_dawn bold"); break;