diff --git a/src/chat_pinnd.rs b/src/chat_pinnd.rs index 696437f..e5be3e4 100644 --- a/src/chat_pinnd.rs +++ b/src/chat_pinnd.rs @@ -1,24 +1,30 @@ //! Chat Pinnd(पिण्ड) is Actor to manage Websocket Chat related action -use std::collections::HashMap; +use std::{collections::HashMap, vec}; use actix::prelude::*; use actix_broker::BrokerSubscribe; use vecmap::VecMap; -use ws_sansad::WsSansad; use crate::{errors, ws_sansad, messages as ms}; #[allow(dead_code)] pub struct ChatPinnd { - grih: HashMap, // id, Grih + grih: HashMap, // kunjika, Grih vyaktigat_waitlist: Vec, - vyakti: VecMap // id, vayakti + non_connected_vyakti: VecMap, // kunjika, vayakti } pub struct Grih { length: Option, - loog: Vec> + loog: Vec +} + +pub struct Loog { + addr: Addr, + kunjika: String, + name: String, + _tags: Vec } #[derive(Debug, Clone)] @@ -48,12 +54,22 @@ impl Handler for ChatPinnd { fn handle(&mut self, msg: ms::SetInfoVyakti, _: &mut Self::Context) -> Self::Result { // check if vayakti info is not modified and do key exist if !msg.modify { - if self.vyakti.key_exist(&msg.kunjika) { + if self.non_connected_vyakti.key_exist(&msg.kunjika) { + return Some("Kunjika Exists".to_owned()); + } + if let Some(_) = self.grih.iter().position(|a| { + match a.1.loog.iter().position(|b| { + b.kunjika == msg.kunjika + }) { + Some(_) => true, + None => false + } + }) { return Some("Kunjika Exists".to_owned()); } } // change value - self.vyakti.insert(msg.kunjika, Vyakti { + self.non_connected_vyakti.insert(msg.kunjika, Vyakti { name: msg.name, tags: msg.tags }); @@ -76,25 +92,34 @@ impl Handler for ChatPinnd { } } - grih.loog.push(msg.addr); - let username = self.vyakti.get(&msg.kunjika).unwrap().name.to_owned(); - let kunjika = msg.kunjika.clone(); - grih.loog.iter().for_each(move |a: &Addr| { - a.do_send(ms::WsConnected { - name: username.clone(), - kunjika: kunjika.clone() + let vayakti = self.non_connected_vyakti.get(&msg.kunjika).unwrap(); + let name = vayakti.name.to_owned(); + let tags = vayakti.tags.to_owned(); + + let name_tmp = name.clone(); + let kunjika_tmp = msg.kunjika.clone(); + grih.loog.iter().for_each(move |a: &Loog| { + a.addr.do_send(ms::WsConnected { + name: name_tmp.clone(), + kunjika: kunjika_tmp.clone() }) }); + self.non_connected_vyakti.remove(&msg.kunjika).unwrap_or(()); + grih.loog.push(Loog::new(msg.addr, msg.kunjika,name,tags)); + + }, None => { // don't exist // add group and notify - self.grih.insert(msg.grih_kunjika, Grih { - length: msg.length, - loog: vec![msg.addr.clone()] - }); + let vayakti = self.non_connected_vyakti.get(&msg.kunjika).unwrap(); msg.addr.do_send(ms::WsConnected { - name: self.vyakti.get(&msg.kunjika).unwrap().name.to_owned(), + name: vayakti.name.clone(), kunjika: msg.kunjika.clone() }); + self.grih.insert(msg.grih_kunjika, Grih { + length: msg.length, + loog: vec![Loog::new(msg.addr,msg.kunjika.clone(),vayakti.name.clone(),vayakti.tags.clone())] + }); + self.non_connected_vyakti.remove(&msg.kunjika).unwrap_or(()); } } @@ -120,24 +145,34 @@ impl Handler for ChatPinnd { // connect 0th person let vayakti_watchlist = self.vyaktigat_waitlist.remove(0); - let vayakti1 = self.vyakti.get(&msg.kunjika).unwrap(); - let vayakti2 = self.vyakti.get(&vayakti_watchlist.kunjika).unwrap(); - let group_kunjika = format!("gupt_{}>{}",msg.kunjika, vayakti_watchlist.kunjika); - self.grih.insert(group_kunjika.clone(), Grih { - length: Some(2), - loog: vec![msg.addr.clone(), vayakti_watchlist.addr.clone()] - }); - - // notify about connection + let vayakti1_name: String; + let vayakti2_name: String; + let group_kunjika: String; + { + let vayakti1 = self.non_connected_vyakti.get(&msg.kunjika).unwrap(); + let vayakti2 = self.non_connected_vyakti.get(&vayakti_watchlist.kunjika).unwrap(); + vayakti1_name = vayakti1.name.clone(); + vayakti2_name = vayakti2.name.clone(); + group_kunjika = format!("gupt_{}>{}",msg.kunjika.clone(), vayakti_watchlist.kunjika); + self.grih.insert(group_kunjika.clone(), Grih { + length: Some(2), + loog: vec![Loog::new(msg.addr.clone(), msg.kunjika.clone(), vayakti1.name.clone(), vayakti1.tags.clone()), + Loog::new(vayakti_watchlist.addr.clone(), vayakti_watchlist.kunjika.clone(), vayakti2.name.clone(), vayakti2.tags.clone())] + }); + } + self.non_connected_vyakti.remove(&msg.kunjika).unwrap_or(()); + self.non_connected_vyakti.remove(&vayakti_watchlist.kunjika).unwrap_or(()); + + // notify about connection msg.addr.do_send(ms::WsConnectedRandom { - ajnyat_name: vayakti2.name.clone(), + ajnyat_name: vayakti2_name, grih_kunjika: group_kunjika.clone() }); vayakti_watchlist.addr.do_send(ms::WsConnectedRandom { - ajnyat_name: vayakti1.name.clone(), - grih_kunjika: group_kunjika.clone() + ajnyat_name: vayakti1_name, + grih_kunjika: group_kunjika }); Some(()) @@ -151,7 +186,7 @@ impl Handler for ChatPinnd { fn handle(&mut self, msg: ms::SendText, _: &mut Self::Context) -> Self::Result { if let Some(grih) = self.grih.get(&msg.grih_kunjika) { grih.loog.iter().for_each(|c| { - c.do_send(ms::WsText { + c.addr.do_send(ms::WsText { sender_kunjika: msg.kunjika.clone(), text: msg.text.clone(), reply: msg.reply.clone() @@ -168,7 +203,7 @@ impl Handler for ChatPinnd { fn handle(&mut self, msg: ms::SendStatus, _: &mut Self::Context) -> Self::Result { if let Some(grih) = self.grih.get(&msg.grih_kunjika) { grih.loog.iter().for_each(|c| { - c.do_send(ms::WsStatus { + c.addr.do_send(ms::WsStatus { sender_kunjika: msg.kunjika.clone(), status: msg.status.clone(), }); @@ -177,6 +212,23 @@ impl Handler for ChatPinnd { } } +/// send list of users +impl Handler for ChatPinnd { + type Result = String; + + fn handle(&mut self, msg: ms::List, _: &mut Self::Context) -> Self::Result { + if let Some(grih) = self.grih.get(&msg.grih_kunjika) { + let mut list = Vec::new(); + for x in grih.loog.iter() { + list.push((x.kunjika.clone(),x.name.clone())); + } + serde_json::json!(list).to_string() + } else { + "".to_string() + } + } +} + /// Notifiy a user disconnected and trim grih impl Handler for ChatPinnd { type Result = (); @@ -184,7 +236,7 @@ impl Handler for ChatPinnd { fn handle(&mut self, msg: ms::LeaveUser, _: &mut Self::Context) -> Self::Result { if let Some(grih_kunjika) = &msg.grih_kunjika { if let Some(grih) = self.grih.get_mut(grih_kunjika) { - if let Some(i) = grih.loog.iter().position(|x| x == &msg.addr) { + if let Some(i) = grih.loog.iter().position(|x| x.addr == msg.addr) { grih.loog.remove(i); } @@ -192,7 +244,7 @@ impl Handler for ChatPinnd { self.grih.remove(grih_kunjika); } else { grih.loog.iter().for_each(|a| { - a.do_send(ms::WsDisconnected { + a.addr.do_send(ms::WsDisconnected { kunjika: msg.kunjika.clone() }) }); @@ -200,7 +252,7 @@ impl Handler for ChatPinnd { } } - self.vyakti.remove(&msg.kunjika).unwrap_or(()); + self.non_connected_vyakti.remove(&msg.kunjika).unwrap_or(()); if let Some(i) = self.vyaktigat_waitlist.iter().position(|a| a.kunjika == msg.kunjika) { self.vyaktigat_waitlist.remove(i); } @@ -212,7 +264,21 @@ impl Default for ChatPinnd { ChatPinnd { grih: HashMap::new(), vyaktigat_waitlist: Vec::new(), - vyakti: VecMap::new() + non_connected_vyakti: VecMap::new() + } + } +} + +impl Loog { + fn new(addr: Addr, + kunjika: String, + name: String, + tags: Vec) -> Self { + Loog { + addr, + kunjika, + name, + _tags:tags } } } diff --git a/src/main.rs b/src/main.rs index bb97124..298d0e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,7 @@ mod errors; mod messages; mod ws_sansad; mod chat_pinnd; - +mod validator; #[actix_web::main] async fn main() -> std::io::Result<()> { let config = config::Config::new(); diff --git a/src/messages.rs b/src/messages.rs index 57fe20a..4dcfa62 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -52,6 +52,13 @@ pub struct SendStatus { pub status: String } +// Request to send text t +#[derive(Clone, Message)] +#[rtype(result = "String")] +pub struct List { + pub grih_kunjika: String +} + /// Request to leave grih #[derive(Clone, Message)] #[rtype(result = "()")] @@ -79,6 +86,13 @@ pub struct WsStatus { pub sender_kunjika: String } +// Request to send transfer text +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct WsList { + pub json: String +} + // Notify Someone connected #[derive(Clone, Message)] diff --git a/src/validator.rs b/src/validator.rs new file mode 100644 index 0000000..5119f08 --- /dev/null +++ b/src/validator.rs @@ -0,0 +1,55 @@ + +pub enum Validation { + NonEmpty, + NoGupt, + NoSpace, + NoHashtag, +} + +pub fn validate(val: Vec, dat: &str, entry_name: &str) -> Option { + for v in val { + let out = match v { + Validation::NonEmpty => non_empty(dat, entry_name), + Validation::NoGupt => is_gupt(dat), + Validation::NoSpace => no_space(dat, entry_name), + Validation::NoHashtag => no_hashtag(dat, entry_name) + }; + + if out != None { + return out; + } + } + None +} + +fn non_empty(dat: &str, entry_name: &str) -> Option { + if dat.len() > 0 { + None + } else { + Some(format!("{} is Required", entry_name)) + } +} + +fn is_gupt(dat: &str) -> Option { + if !dat.starts_with("gupt_") { + None + } else { + Some(format!("Restricted group")) + } +} + +fn no_space(dat: &str, entry_name: &str) -> Option { + if dat.contains(" ") { + Some(format!("{} shounld not have space", entry_name)) + } else { + None + } +} + +fn no_hashtag(dat: &str, entry_name: &str) -> Option { + if dat.contains("#") { + Some(format!("{} shounld not have Hashtag(#)", entry_name)) + } else { + None + } +} \ No newline at end of file diff --git a/src/ws_sansad.rs b/src/ws_sansad.rs index 3ba937f..0ccbf84 100644 --- a/src/ws_sansad.rs +++ b/src/ws_sansad.rs @@ -4,7 +4,7 @@ use actix_broker::{Broker, SystemBroker}; use actix_web_actors::ws; use serde_json::{json, Value}; -use crate::{chat_pinnd::ChatPinnd, messages as ms}; +use crate::{chat_pinnd::ChatPinnd, messages as ms, validator::{Validation as vl, validate}}; use crate::errors; pub struct WsSansad { @@ -77,6 +77,18 @@ impl Handler for WsSansad { } } +/// List Vayakti +impl Handler for WsSansad { + type Result = (); + fn handle(&mut self, msg: ms::WsList, ctx: &mut Self::Context) -> Self::Result { + let json = json!({ + "cmd": "list", + "vayakti": msg.json + }); + ctx.text(json.to_string()); + } +} + /// send response ok, error impl Handler for WsSansad { type Result = (); @@ -139,6 +151,7 @@ impl WsSansad { /// parse the request text from client async fn parse_text_handle(&mut self, msg: String) { + println!("{:?}", msg); if let Ok(val) = serde_json::from_str::(&msg) { match val.get("cmd").unwrap().as_str().unwrap() { "seinfo" => { self.set_info(val).await }, @@ -146,6 +159,7 @@ impl WsSansad { "rand" => { self.join_random().await }, "text" => { self.send_text(val).await }, "status" => { self.send_status(val).await }, + "list" => { self.list().await }, "leave" => { self.leave_grih().await }, _ => () } @@ -198,6 +212,16 @@ impl WsSansad { } }; + // Validate + if let Some(val ) = validate(vec![vl::NonEmpty, vl::NoSpace, vl::NoHashtag], &kunjika, "Kunjika") { + self.send_err_response(&val); + return; + } + if let Some(val ) = validate(vec![vl::NonEmpty], &name, "Name") { + self.send_err_response(&val); + return; + } + // check if eing modified let modify = self.kunjika == Some(kunjika.clone()); @@ -246,19 +270,20 @@ impl WsSansad { /// Request to join to grih async fn join_grih(&mut self, val: Value) { - //echeck user exist + //check user exist if let None = self.kunjika { self.send_err_response("No vayakti kunjika set"); return; } + // Check is already joined match self.isthiti { Isthiti::None => (), _ => { return; } } - + // parse parameter let grih_kunjika = match val.get("grih_kunjika") { Some(val) => val, @@ -267,19 +292,25 @@ impl WsSansad { return; } }.as_str().unwrap().to_owned(); - - // restrict place for vaktigat chat - if grih_kunjika.starts_with("gupt_") { - self.send_err_response("Such grih kunjika is restricted"); + println!("about to validate"); + // Validate + if let Some(val ) = validate(vec![vl::NonEmpty, vl::NoGupt, vl::NoSpace], &grih_kunjika, "Grih Kunjika") { + println!("{}", val); + self.send_err_response(&val); return; } - + let length: Option = match val.get("length") { - Some(val) => Some(val.as_i64().unwrap() as usize), + Some(val) => match val.as_i64(){ + Some(val) => Some(val as usize), + None => None + }, None => None }; - // requesy + println!("{:?} {:?} {:?}", grih_kunjika, self.kunjika, length); + + // request let result: Result<(), errors::GrihFullError> = ChatPinnd::from_registry().send(ms::JoinGrih { grih_kunjika: grih_kunjika.clone(), length, @@ -296,6 +327,32 @@ impl WsSansad { } } + /// Request to join to grih + async fn list(&mut self) { + // check if vayakti exist + if let None = self.kunjika { + self.send_err_response("No vayakti kunjika set"); + return; + } + + // check if connected to any grih + match &self.isthiti { + Isthiti::Grih(kunjika) => { + let json: String = ChatPinnd::from_registry().send(ms::List { + grih_kunjika: kunjika.clone() + }).await.unwrap(); + + self.addr.clone().unwrap().do_send(ms::WsList { + json + }) + }, + _ => { + self.send_err_response("Grih not connected"); + return; + } + } + } + /// send text to vayakti in grih async fn send_text(&mut self, val: Value) { // check if vayakti exist @@ -397,6 +454,7 @@ impl WsSansad { }); } + self.isthiti = Isthiti::None; self.send_ok_response("left"); } } diff --git a/static/css/style.css b/static/css/style.css index 69d8e98..45d0d0c 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,5 +1,13 @@ pre { font-family: Arial, Helvetica, sans-serif; + font-size: 0.9em; + margin-top: 0; + margin-bottom: 0; +} + +textarea { + font-family: Arial, Helvetica, sans-serif; + font-size: 0.9em; margin-top: 0; margin-bottom: 0; } @@ -28,22 +36,45 @@ pre { } .chatpanel-left { + position: fixed; display: inline; width: 34px; + bottom: 0; + left: 0; } .chatpanel-mid { + position: fixed; display: inline; width: calc(100% - 54px - 34px); + bottom: 3px; + left: 34px; } .chatpanel-right { + position: fixed; display: inline; width: 44px; + bottom: 5px; + right: 5px; +} + +#send_box { + width: 100%; + height: 40px; + min-height: 40px; + word-wrap: break-word; + resize: none; +} + +.status { + width: calc(100% - 6); + padding: 3px; + text-align: center; } .message { - padding: 0px 8px 5px 8px; + padding: 0px 8px 3px 8px; margin: 2px; word-wrap: break-word;; } @@ -64,10 +95,10 @@ pre { } .message .message-by { - font-size: 0.9rem; + font-size: 0.7rem; font-style: italic; text-decoration: underline; - padding-bottom: 2px; + padding-bottom: 1px; } .message-me.active { @@ -109,4 +140,4 @@ pre { border-radius: 10px; width: calc(100% - 2*8px - 3*5px); max-width: 720px; -} \ No newline at end of file +} diff --git a/static/index.html b/static/index.html index ab0055d..ad01157 100644 --- a/static/index.html +++ b/static/index.html @@ -14,7 +14,7 @@ -
+
@@ -27,20 +27,28 @@
-
Connect Grih(group) with kunjika.
+
+
Connect Grih(home) with kunjika.
-
Grih kunjika
+
Grih kunjika(id)
+
New group is created if no such group exists
-
Your kunjika
+
Your kunjika(id)
Your name
-
+
+
Size of Home
+ +
Number of perople allowed in home. Leave empty for infinite
+
+
+
Connect
@@ -48,7 +56,7 @@
-
+
-
-
Ram_1_5
-
huh
-
-
-
Ram_1_5
-
haha
-
-
-
you
-
ya
-
+ +
+
+
-
+
send
-
- +
+
-
-
Send
+
+
Send
@@ -132,7 +133,7 @@
diff --git a/static/js/app.js b/static/js/app.js index e145594..c5e7fd6 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -26,15 +26,42 @@ $(document).ready(() => { $('#selected_clip > .siimple-close').click(function() { deactivateMessages(); }); + + $('#send_box').keypress(function(e) { + if(e.originalEvent.charCode == 13 && !e.shiftKey) { + send(); + e.preventDefault(); + } + }); + + $('#send_box').bind('input propertychange keyup', function() { + var height = ($(window).height()*0.0165).toFixed(0)*20; + var sheight = this.scrollHeight; + if(sheight < height) { + $(this).height(0); + height = this.scrollHeight; + $(this).height(height - 20); + $('#reply_clip').css('bottom', (this.scrollHeight + 10) + 'px'); + $('#selected_clip').css('bottom', (this.scrollHeight + 10) + 'px'); + } + }); }); +function calcHeight(value) { + let numberOfLineBreaks = (value.match(/\n/g) || []).length; + // min-height + lines x line-height + padding + border + let newHeight = 20 + numberOfLineBreaks * 20 + 12 + 2; + return newHeight; +} + // Create WebSocket connection. -const socket = new WebSocket('ws://localhost:8080/ws/'); +const socket = new WebSocket('ws://'+window.location.host+'/ws/'); var callbacks = []; var myinfo = { kunjika: "", name: "" }; +var users = {}; // Connection opened socket.addEventListener('open', function (event) { @@ -48,17 +75,31 @@ socket.addEventListener('message', function (event) { switch(j.cmd) { case 'resp': if(j.result == 'Err') { - console.log('Error', j.message); - } else { + if($('#chat_panel').hasClass('hidden')) { + $('[name="error_msg"]').text(j.message); + $('[name="error_msg"]').removeClass('hidden'); + callbacks = []; + } else { + pushStatus(j.message); + } + } else if(j.result == 'Ok'){ if(callbacks.length > 0) { callbacks[0](); - callbacks.splice(0); + callbacks.shift(); } } break; case 'text': pushMessage(j.kunjika, j.text, j.reply); break; + case 'connected': + users[j.kunjika] = j.name; + pushStatus('Vyakti '+j.name+' connected as '+j.kunjika); + break; + case 'disconnected': + delete users[j.kunjika]; + pushStatus('Vyakti '+j.name+' disconnected as '+j.kunjika); + break; } }); @@ -70,11 +111,20 @@ function connect(frm) { frm.serializeArray().forEach(el => { data[el.name] = el.value; }); - + if(data['length'] !== undefined) { + data['length'] = parseInt(data['length']); + } callbacks.push(() => { socket.send(JSON.stringify(Object.assign({cmd: frm.attr('cmd')}, data))); + }); + callbacks.push(() => { + cleanMessage(); $('#progressbar').addClass('hidden'); + $('#send_box').text(''); + $('#connect_panel').addClass('hidden'); + $('[name="error_msg"]').addClass('hidden'); $('#chat_panel').removeClass('hidden'); + $('#send_box').focus(); myinfo.kunjika = data.kunjika; myinfo.name = data.name; }); @@ -87,6 +137,9 @@ function leave() { $('#reply_clip').addClass('hidden'); $('#selected_clip').addClass('hidden'); $('#action_clip').addClass('hidden'); + $('#connect_panel').removeClass('hidden'); + myinfo.kunjika = ""; + myinfo.name = ""; }); socket.send(JSON.stringify({ cmd: "leave" @@ -97,7 +150,7 @@ function pushMessage(sender, text, reply = null) { var isMe = myinfo.kunjika == sender; var area = $('#message_area'); var elm = $('
', {class: 'message '+(isMe?'message-me':'message-other')}); - elm.append($('
', {class: 'message-by'}).append(sender)); + elm.append($('
', {class: 'message-by'}).append(users[sender])); if(reply != null && reply.length > 0) { elm.append( $('
', {class: 'message message-reply'}) @@ -108,7 +161,23 @@ function pushMessage(sender, text, reply = null) { elm.click(function() { activateMessage(this); }); - area.append(elm) + area.append(elm); + + //to bottom + var scroll = $("#message_area_scroll"); + scroll.scrollTop(scroll[0].scrollHeight); +} + + +function pushStatus(text) { + var area = $('#message_area'); + var elm = $('
', {class: 'status'}); + elm.append($('', {class: 'siimple-tag siimple-tag--dark'}).append(text)); + area.append(elm); + + //to bottom + var scroll = $("#message_area_scroll"); + scroll.scrollTop(scroll[0].scrollHeight); } function deactivateMessages() { @@ -149,9 +218,11 @@ function prepareReply() { } function send() { + var text = $('#send_box').val().trim(); + if(text.length == 0) return; socket.send(JSON.stringify({ cmd: "text", - text: $('#send_box').val(), + text: text, reply: $('#reply_clip').attr('msg') })); $('#send_box').val(''); @@ -168,6 +239,11 @@ function copyMessagesToClipboard() { $temp.remove(); deactivateMessages(); } + +function cleanMessage() { + $('#message_area').empty(); + $('#action_clip').addClass('hidden'); +} // function wsend(p) { // socket.send(p); // } diff --git a/static/js/autosize.min.js b/static/js/autosize.min.js new file mode 100644 index 0000000..4d9b4e9 --- /dev/null +++ b/static/js/autosize.min.js @@ -0,0 +1,6 @@ +/*! + autosize 4.0.2 + license: MIT + http://www.jacklmoore.com/autosize +*/ +!function(e,t){if("function"==typeof define&&define.amd)define(["module","exports"],t);else if("undefined"!=typeof exports)t(module,exports);else{var n={exports:{}};t(n,n.exports),e.autosize=n.exports}}(this,function(e,t){"use strict";var n,o,p="function"==typeof Map?new Map:(n=[],o=[],{has:function(e){return-1