diff --git a/iris b/iris index 426ff0a..1aadef0 160000 --- a/iris +++ b/iris @@ -1 +1 @@ -Subproject commit 426ff0adfc6943f14a41729837c630147588fca5 +Subproject commit 1aadef0e7e755fd211eea30b611c8194a6e63442 diff --git a/src/bin/client/main.rs b/src/bin/client/main.rs index fbb42e3..c403a88 100644 --- a/src/bin/client/main.rs +++ b/src/bin/client/main.rs @@ -44,6 +44,7 @@ impl DefaultAppState for Client { _: Proxy, ) -> Self { let notif = WidgetPtr::default().add(rsc); + // let popup = WidgetPtr::default().add(rsc); let main_ui = WidgetPtr::default().add(rsc); let bg = ( image(include_bytes!("./assets/fuit.jpg")), diff --git a/src/bin/client/ui/connect.rs b/src/bin/client/ui/connect.rs index 8be7f83..f6953fe 100644 --- a/src/bin/client/ui/connect.rs +++ b/src/bin/client/ui/connect.rs @@ -1,9 +1,9 @@ use openworm::net::{CreateAccount, CreateAccountResp, Login, LoginResp}; use crate::{ - session::Session, data::{AccountInfo, ClientData, ServerInfo, ServerList}, net::{ConnectInfo, NetHandle}, + session::Session, }; use super::*; @@ -51,7 +51,7 @@ pub fn start(rsc: &mut Rsc, data: &ClientData) -> WeakWidget { let mut fail = |reason: &str| { let reason = reason.to_string(); ctx.update(move |ctx, rsc| { - rsc[ctx.notif].inner = Some(werror(&reason, rsc)); + ctx.error(&reason, rsc); }) }; let con = match NetHandle::connect( @@ -122,11 +122,11 @@ pub fn start(rsc: &mut Rsc, data: &ClientData) -> WeakWidget { } pub fn create_account(rsc: &mut Rsc) -> WeakWidget { - let url = field("", "server", rsc); - let token = field("", "account creation token", rsc); - let cert = field("", "certificate hex", rsc); - let username = field("", "username", rsc); - let password = field("", "password", rsc); + let url = field("", "server").add(rsc); + let token = field("", "account creation token").add(rsc); + let cert = field("", "certificate hex").add(rsc); + let username = field("", "username").add(rsc); + let password = field("", "password").add(rsc); let create = Button::submit("create", rsc); rsc.events.register(create, Submit, move |ctx, rsc| { @@ -145,7 +145,7 @@ pub fn create_account(rsc: &mut Rsc) -> WeakWidget { let mut fail = |reason: &str| { let reason = reason.to_string(); ctx.update(move |ctx, rsc| { - rsc[ctx.notif].inner = Some(werror(&reason, rsc)); + ctx.error(&reason, rsc); create.enable(rsc); }) }; @@ -200,11 +200,11 @@ pub fn create_account(rsc: &mut Rsc) -> WeakWidget { ( wtext("Create Account").text_align(Align::CENTER).size(30), - field_box(url, rsc), - field_box(token, rsc), - field_box(cert, rsc), - field_box(username, rsc), - field_box(password, rsc), + field_box(url), + field_box(token), + field_box(cert), + field_box(username), + field_box(password), create, ) .span(Dir::DOWN) diff --git a/src/bin/client/ui/friends.rs b/src/bin/client/ui/friends.rs index 27a72fe..286c081 100644 --- a/src/bin/client/ui/friends.rs +++ b/src/bin/client/ui/friends.rs @@ -1,12 +1,113 @@ -// pub fn view(rsc: &mut Rsc, session: &Session) -> StrongWidget { -// let mut view = WidgetSelector::new(View::Info, info(rsc)); -// view.set(View::User, users(rsc, session)); -// let view = view.add(rsc); -// let [info, server] = tabs(rsc, view, [("info", View::Info), ("users", View::User)]); -// -// let side_bar = rect(Color::BLACK.alpha(150)) -// .foreground((info, server).span(Dir::DOWN)) -// .width(260); -// -// (side_bar, view).span(Dir::RIGHT).add_strong(rsc) -// } +use openworm::net::{AddFriend, AddFriendResp, GenerateToken, RequestFriends, ServerPerms}; + +use super::*; + +pub fn view(rsc: &mut Rsc, session: &Session) -> StrongWidget { + ( + gen_token(rsc, session), + add_friend_area(rsc, session), + friends_list(rsc, session), + ) + .span(Dir::DOWN) + .add_strong(rsc) +} + +fn add_friend_area(rsc: &mut Rsc, session: &Session) -> WeakWidget { + let username_field = field("", "username").add(rsc); + let add = Button::submit("add friend", rsc); + let con = session.con.clone(); + rsc.events.register(add, Submit, move |ctx, rsc| { + let con = con.clone(); + let username = username_field.edit(rsc).take(); + add.disable(rsc); + rsc.tasks.spawn(async move |mut ctx| { + let Ok(resp) = con.request(AddFriend { username }).await else { + ctx.update(move |ctx, rsc| { + ctx.error("failed to add user", rsc); + }); + return; + }; + ctx.update(move |ctx, rsc| { + add.enable(rsc); + match resp { + AddFriendResp::Ok => {} + AddFriendResp::UnknownUser => { + ctx.error("unknown user", rsc); + } + AddFriendResp::CannotAddSelf => { + ctx.error("cannot add self", rsc); + } + AddFriendResp::AlreadySent => { + ctx.error("already sent request to user", rsc); + } + AddFriendResp::AlreadyFriends => { + ctx.error("already friends with user", rsc); + } + } + }); + }); + }); + (field_box(username_field).width(300), add.width(200)) + .span(Dir::RIGHT) + .add(rsc) +} + +fn gen_token(rsc: &mut Rsc, session: &Session) -> WeakWidget { + let generate = Button::normal("generate token", rsc); + let con = session.con.clone(); + let token = wtext("") + .size(30) + .editable(EditMode::SingleLine) + .attr::(()) + .add(rsc); + rsc.events.register(generate, Submit, move |_, rsc| { + let con = con.clone(); + token.edit(rsc).set("loading..."); + rsc.spawn_task(async move |mut ctx| { + let resp = con + .request(GenerateToken { + perms: ServerPerms::NONE, + }) + .await; + ctx.update(move |_, rsc| match resp { + Ok(resp) => { + token.edit(rsc).set(&resp.token); + } + Err(_) => { + token.edit(rsc).set("failed to generate token"); + } + }); + }); + }); + (generate.width(200), token).span(Dir::RIGHT).add(rsc) +} + +fn friends_list(rsc: &mut Rsc, session: &Session) -> WeakWidget { + let ptr = loading_area("loading friends").add(rsc); + let con = session.con.clone(); + rsc.events.register(ptr, Draw, move |_, rsc| { + let con = con.clone(); + // TODO: maybe have rsc.request method that takes in &con? + // need to also handle error tho + rsc.spawn_task(async move |mut ctx| { + let Ok(resp) = con.request(RequestFriends).await else { + return; + }; + ctx.update(move |_, rsc| { + let mut all = Span::empty(Dir::DOWN); + if !resp.incoming.is_empty() { + all.push(section_label("incoming").add_strong(rsc)); + all.push(user_list(&resp.incoming, rsc).add_strong(rsc)) + } + all.push(section_label("friends").add_strong(rsc)); + all.push(user_list(&resp.current, rsc).add_strong(rsc)); + if !resp.outgoing.is_empty() { + all.push(section_label("outgoing").add_strong(rsc)); + all.push(user_list(&resp.outgoing, rsc).add_strong(rsc)) + } + all.set_ptr(ptr, rsc); + }); + }); + }); + ptr +} diff --git a/src/bin/client/ui/main.rs b/src/bin/client/ui/main.rs index 2980e14..745d5ee 100644 --- a/src/bin/client/ui/main.rs +++ b/src/bin/client/ui/main.rs @@ -1,7 +1,5 @@ use std::hash::Hash; -use crate::{Rsc, session::Session}; - use super::*; pub const SIZE: u32 = 20; @@ -16,6 +14,7 @@ pub enum MainView { pub fn main_view(rsc: &mut Rsc, session: Session) -> WeakWidget { let mut view = WidgetSelector::new(MainView::Channel, channel::view(rsc)); view.set(MainView::Server, server::view(rsc, &session)); + view.set(MainView::Friends, friends::view(rsc, &session)); let view = view.add(rsc); let [channel, friends, server] = tabs( rsc, diff --git a/src/bin/client/ui/misc.rs b/src/bin/client/ui/misc.rs index a3abe2d..3d4ef60 100644 --- a/src/bin/client/ui/misc.rs +++ b/src/bin/client/ui/misc.rs @@ -1,3 +1,7 @@ +use openworm::net::UserId; + +use crate::Client; + use super::*; pub fn werror(msg: &str, rsc: &mut Rsc) -> StrongWidget { @@ -16,20 +20,18 @@ pub fn large_hint_text(msg: impl Into) -> TextBuilder { wtext(msg).size(30).color(Color::GRAY) } -pub fn field(default: &str, hint: &str, rsc: &mut Rsc) -> WeakWidget { +pub fn field(default: &str, hint: &str) -> impl FnOnce(&mut Rsc) -> TextEdit { wtext(default) .editable(EditMode::SingleLine) .size(20) .hint(hint_text(hint)) - .add(rsc) } -pub fn field_box(field: WeakWidget, rsc: &mut Rsc) -> WeakWidget { +pub fn field_box(field: WeakWidget) -> impl WidgetIdFn { field .pad(10) .background(rect(Color::BLACK.brighter(0.1)).radius(15)) .attr::(field) - .add(rsc) } #[derive(Clone, Copy, WidgetView)] @@ -92,6 +94,10 @@ impl Button { Self::new(text, color::GREEN, rsc) } + pub fn normal(text: &str, rsc: &mut Rsc) -> Self { + Self::new(text, color::DARK, rsc) + } + pub fn disable(&self, rsc: &mut Rsc) { rsc[self.enabled] = false; rsc[self.rect].color = self.color.darker(0.8); @@ -123,10 +129,43 @@ pub fn tabs( list: [(&'static str, T); N], ) -> [WeakWidget; N] { list.map(|(name, sel)| { - Button::new(name, color::DARK, rsc) + Button::normal(name, rsc) .on(Submit, move |_, rsc: &mut Rsc| { rsc[view].select(sel); }) .add(rsc) }) } + +pub fn loading_area(text: &str) -> impl FnOnce(&mut Rsc) -> WidgetPtr { + move |rsc| { + WidgetPtr::new( + large_hint_text(text) + .center_text() + .width(rest(1)) + .add_strong(rsc), + ) + } +} + +pub fn section_label(text: impl Into) -> TextBuilder { + wtext(text).size(30) +} + +pub fn user_list<'a>(ids: impl IntoIterator, rsc: &mut Rsc) -> Span { + let mut span = Span::empty(Dir::DOWN); + for id in ids { + let thing = (wtext(id.to_string()).size(20),) + .span(Dir::RIGHT) + .gap(30) + .pad(15); + span.push(thing.add_strong(rsc)); + } + span +} + +impl Client { + pub fn error(&self, text: &str, rsc: &mut Rsc) { + rsc[self.notif].inner = Some(werror(text, rsc)); + } +} diff --git a/src/bin/client/ui/mod.rs b/src/bin/client/ui/mod.rs index efcf4bc..e7fc639 100644 --- a/src/bin/client/ui/mod.rs +++ b/src/bin/client/ui/mod.rs @@ -1,4 +1,4 @@ -use crate::Rsc; +use crate::{Rsc, session::Session}; use iris::prelude::*; mod channel; diff --git a/src/bin/client/ui/server.rs b/src/bin/client/ui/server.rs index 4b2c77f..c429efc 100644 --- a/src/bin/client/ui/server.rs +++ b/src/bin/client/ui/server.rs @@ -28,13 +28,7 @@ fn info(rsc: &mut Rsc) -> StrongWidget { } fn users(rsc: &mut Rsc, session: &Session) -> StrongWidget { - let ptr = WidgetPtr::new( - large_hint_text("loading users...") - .center_text() - .width(rest(1)) - .add_strong(rsc), - ) - .add(rsc); + let ptr = loading_area("loading users").add(rsc); let con = session.con.clone(); rsc.events.register(ptr, Draw, move |_, rsc| { let con = con.clone(); diff --git a/src/bin/server/handle.rs b/src/bin/server/handle.rs index 9483a33..c441aa9 100644 --- a/src/bin/server/handle.rs +++ b/src/bin/server/handle.rs @@ -158,26 +158,26 @@ impl ClientHandler { let Some(mut user) = tx.get(&db.users, &user_id) else { reply!(AccountDeleted); }; - let Some(friend_id) = tx.get(&db.usernames, &info.username) else { + let Some(other_id) = tx.get(&db.usernames, &info.username) else { reply!(AddFriendResp::UnknownUser); }; - if user.friends.outgoing.contains(&friend_id) { + if user.friends.outgoing.contains(&other_id) { reply!(AddFriendResp::AlreadySent); } - if friend_id == user_id { + if other_id == user_id { reply!(AddFriendResp::CannotAddSelf); } - let Some(mut other) = tx.get(&db.users, &user_id) else { + let Some(mut other) = tx.get(&db.users, &other_id) else { println!("WARNING: username without valid user!"); reply!(AddFriendResp::UnknownUser); }; if other.friends.current.contains(&user_id) { reply!(AddFriendResp::AlreadyFriends); } - user.friends.outgoing.insert(friend_id); + user.friends.outgoing.insert(other_id); other.friends.incoming.insert(user_id); tx.insert(&db.users, &user_id, &user); - tx.insert(&db.users, &friend_id, &other); + tx.insert(&db.users, &other_id, &other); if tx.commit() { break; } @@ -189,15 +189,15 @@ impl ClientHandler { loop { let mut tx = db.write_tx(); let mut user = tx.get(&db.users, &user_id)?; - let friend_id = info.id; - let Some(mut other) = tx.get(&db.users, &user_id) else { + let other_id = info.id; + let Some(mut other) = tx.get(&db.users, &other_id) else { println!("WARNING: username without valid user!"); return None; }; - user.friends.current.remove(&friend_id); + user.friends.current.remove(&other_id); other.friends.current.remove(&user_id); tx.insert(&db.users, &user_id, &user); - tx.insert(&db.users, &friend_id, &other); + tx.insert(&db.users, &other_id, &other); if tx.commit() { break; } @@ -218,7 +218,7 @@ impl ClientHandler { let mut tx = db.write_tx(); let mut user = tx.get(&db.users, &user_id)?; let other_id = answer.id; - let Some(mut other) = tx.get(&db.users, &user_id) else { + let Some(mut other) = tx.get(&db.users, &other_id) else { println!("WARNING: username without valid user!"); return None; };