Implement user profiles.

This commit is contained in:
yukirij 2024-10-18 12:51:53 -07:00
parent 02be8a41b6
commit 5e9e839480
13 changed files with 199 additions and 45 deletions

View File

@ -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();
}

View File

@ -414,18 +414,6 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
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<protocol::QRPacket>)
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<protocol::QRPacket>)
}
} 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

View File

@ -198,6 +198,16 @@ pub async fn handle_ws(ws:WebSocketStream<TokioIo<Upgraded>>, 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,

View File

@ -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;

View File

@ -29,8 +29,8 @@ pub enum QRPacketData {
QUserList,
RUserList(PacketUserListResponse),
QUserInfo(PacketUserInfo),
RUserInfo(PacketUserInfoResponse),
QUserProfile(PacketUserProfile),
RUserProfile(PacketUserProfileResponse),
QAccountInfo(PacketAccountInfo),
RAccountInfo(PacketAccountInfoResponse),

View File

@ -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::*;

View File

@ -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<u8>, index:&mut usize) -> Result<Self::Data, ()>
@ -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<PacketSessionListResponseRecord>,
}
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<u8>

View File

@ -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%;
}

View File

@ -9,3 +9,7 @@ button.warn {
}
span.monospace {font-family:monospace;}
a {
cursor: pointer;
}

View File

@ -52,7 +52,7 @@ const OpCode = {
ChallengeList :0x0062,
UserList :0x0100,
UserInfo :0x0101,
UserProfile :0x0102,
InviteList :0x0180,
InviteAcquire :0x0181,

View File

@ -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);

View File

@ -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");

View File

@ -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;