New comments tree mostly working.
- Fixed some bugs w/ the websocket service
This commit is contained in:
parent
25dcb8f4f4
commit
2bb2b84737
8 changed files with 302 additions and 97 deletions
|
@ -37,6 +37,7 @@ We have a twitter alternative (mastodon), a facebook alternative (friendica), so
|
||||||
- [Sticky Sidebar](https://stackoverflow.com/questions/38382043/how-to-use-css-position-sticky-to-keep-a-sidebar-visible-with-bootstrap-4/49111934)
|
- [Sticky Sidebar](https://stackoverflow.com/questions/38382043/how-to-use-css-position-sticky-to-keep-a-sidebar-visible-with-bootstrap-4/49111934)
|
||||||
- [RXJS websocket](https://stackoverflow.com/questions/44060315/reconnecting-a-websocket-in-angular-and-rxjs/44067972#44067972)
|
- [RXJS websocket](https://stackoverflow.com/questions/44060315/reconnecting-a-websocket-in-angular-and-rxjs/44067972#44067972)
|
||||||
- [Rust JWT](https://github.com/Keats/jsonwebtoken)
|
- [Rust JWT](https://github.com/Keats/jsonwebtoken)
|
||||||
|
- [Hierarchical tree building javascript](https://stackoverflow.com/a/40732240/1655478)
|
||||||
|
|
||||||
## TODOs
|
## TODOs
|
||||||
- Endpoints
|
- Endpoints
|
||||||
|
|
|
@ -83,7 +83,6 @@ impl Handler<WSMessage> for WSSession {
|
||||||
fn handle(&mut self, msg: WSMessage, ctx: &mut Self::Context) {
|
fn handle(&mut self, msg: WSMessage, ctx: &mut Self::Context) {
|
||||||
println!("id: {} msg: {}", self.id, msg.0);
|
println!("id: {} msg: {}", self.id, msg.0);
|
||||||
ctx.text(msg.0);
|
ctx.text(msg.0);
|
||||||
ctx.text("NO");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -190,15 +190,14 @@ pub struct CreateCommentResponse {
|
||||||
/// session. implementation is super primitive
|
/// session. implementation is super primitive
|
||||||
pub struct ChatServer {
|
pub struct ChatServer {
|
||||||
sessions: HashMap<usize, Recipient<WSMessage>>, // A map from generated random ID to session addr
|
sessions: HashMap<usize, Recipient<WSMessage>>, // A map from generated random ID to session addr
|
||||||
rooms: HashMap<String, HashSet<usize>>, // A map from room name to set of connectionIDs
|
rooms: HashMap<i32, HashSet<usize>>, // A map from room / post name to set of connectionIDs
|
||||||
rng: ThreadRng,
|
rng: ThreadRng,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ChatServer {
|
impl Default for ChatServer {
|
||||||
fn default() -> ChatServer {
|
fn default() -> ChatServer {
|
||||||
// default room
|
// default room
|
||||||
let mut rooms = HashMap::new();
|
let rooms = HashMap::new();
|
||||||
rooms.insert("Main".to_owned(), HashSet::new());
|
|
||||||
|
|
||||||
ChatServer {
|
ChatServer {
|
||||||
sessions: HashMap::new(),
|
sessions: HashMap::new(),
|
||||||
|
@ -210,8 +209,8 @@ impl Default for ChatServer {
|
||||||
|
|
||||||
impl ChatServer {
|
impl ChatServer {
|
||||||
/// Send message to all users in the room
|
/// Send message to all users in the room
|
||||||
fn send_room_message(&self, room: &str, message: &str, skip_id: usize) {
|
fn send_room_message(&self, room: i32, message: &str, skip_id: usize) {
|
||||||
if let Some(sessions) = self.rooms.get(room) {
|
if let Some(sessions) = self.rooms.get(&room) {
|
||||||
for id in sessions {
|
for id in sessions {
|
||||||
if *id != skip_id {
|
if *id != skip_id {
|
||||||
if let Some(addr) = self.sessions.get(id) {
|
if let Some(addr) = self.sessions.get(id) {
|
||||||
|
@ -250,14 +249,14 @@ impl Handler<Connect> for ChatServer {
|
||||||
println!("Someone joined");
|
println!("Someone joined");
|
||||||
|
|
||||||
// notify all users in same room
|
// notify all users in same room
|
||||||
self.send_room_message(&"Main".to_owned(), "Someone joined", 0);
|
// self.send_room_message(&"Main".to_owned(), "Someone joined", 0);
|
||||||
|
|
||||||
// register session with random id
|
// register session with random id
|
||||||
let id = self.rng.gen::<usize>();
|
let id = self.rng.gen::<usize>();
|
||||||
self.sessions.insert(id, msg.addr);
|
self.sessions.insert(id, msg.addr);
|
||||||
|
|
||||||
// auto join session to Main room
|
// auto join session to Main room
|
||||||
self.rooms.get_mut(&"Main".to_owned()).unwrap().insert(id);
|
// self.rooms.get_mut(&"Main".to_owned()).unwrap().insert(id);
|
||||||
|
|
||||||
// send id back
|
// send id back
|
||||||
id
|
id
|
||||||
|
@ -271,32 +270,32 @@ impl Handler<Disconnect> for ChatServer {
|
||||||
fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) {
|
fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) {
|
||||||
println!("Someone disconnected");
|
println!("Someone disconnected");
|
||||||
|
|
||||||
let mut rooms: Vec<String> = Vec::new();
|
let mut rooms: Vec<i32> = Vec::new();
|
||||||
|
|
||||||
// remove address
|
// remove address
|
||||||
if self.sessions.remove(&msg.id).is_some() {
|
if self.sessions.remove(&msg.id).is_some() {
|
||||||
// remove session from all rooms
|
// remove session from all rooms
|
||||||
for (name, sessions) in &mut self.rooms {
|
for (id, sessions) in &mut self.rooms {
|
||||||
if sessions.remove(&msg.id) {
|
if sessions.remove(&msg.id) {
|
||||||
rooms.push(name.to_owned());
|
// rooms.push(*id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// send message to other users
|
// send message to other users
|
||||||
for room in rooms {
|
// for room in rooms {
|
||||||
self.send_room_message(&room, "Someone disconnected", 0);
|
// self.send_room_message(room, "Someone disconnected", 0);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handler for Message message.
|
/// Handler for Message message.
|
||||||
impl Handler<ClientMessage> for ChatServer {
|
// impl Handler<ClientMessage> for ChatServer {
|
||||||
type Result = ();
|
// type Result = ();
|
||||||
|
|
||||||
fn handle(&mut self, msg: ClientMessage, _: &mut Context<Self>) {
|
// fn handle(&mut self, msg: ClientMessage, _: &mut Context<Self>) {
|
||||||
self.send_room_message(&msg.room, msg.msg.as_str(), msg.id);
|
// self.send_room_message(&msg.room, msg.msg.as_str(), msg.id);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// Handler for Message message.
|
/// Handler for Message message.
|
||||||
impl Handler<StandardMessage> for ChatServer {
|
impl Handler<StandardMessage> for ChatServer {
|
||||||
|
@ -314,35 +313,35 @@ impl Handler<StandardMessage> for ChatServer {
|
||||||
let res: String = match user_operation {
|
let res: String = match user_operation {
|
||||||
UserOperation::Login => {
|
UserOperation::Login => {
|
||||||
let login: Login = serde_json::from_str(&data.to_string()).unwrap();
|
let login: Login = serde_json::from_str(&data.to_string()).unwrap();
|
||||||
login.perform()
|
login.perform(self, msg.id)
|
||||||
},
|
},
|
||||||
UserOperation::Register => {
|
UserOperation::Register => {
|
||||||
let register: Register = serde_json::from_str(&data.to_string()).unwrap();
|
let register: Register = serde_json::from_str(&data.to_string()).unwrap();
|
||||||
register.perform()
|
register.perform(self, msg.id)
|
||||||
},
|
},
|
||||||
UserOperation::CreateCommunity => {
|
UserOperation::CreateCommunity => {
|
||||||
let create_community: CreateCommunity = serde_json::from_str(&data.to_string()).unwrap();
|
let create_community: CreateCommunity = serde_json::from_str(&data.to_string()).unwrap();
|
||||||
create_community.perform()
|
create_community.perform(self, msg.id)
|
||||||
},
|
},
|
||||||
UserOperation::ListCommunities => {
|
UserOperation::ListCommunities => {
|
||||||
let list_communities: ListCommunities = ListCommunities;
|
let list_communities: ListCommunities = ListCommunities;
|
||||||
list_communities.perform()
|
list_communities.perform(self, msg.id)
|
||||||
},
|
},
|
||||||
UserOperation::CreatePost => {
|
UserOperation::CreatePost => {
|
||||||
let create_post: CreatePost = serde_json::from_str(&data.to_string()).unwrap();
|
let create_post: CreatePost = serde_json::from_str(&data.to_string()).unwrap();
|
||||||
create_post.perform()
|
create_post.perform(self, msg.id)
|
||||||
},
|
},
|
||||||
UserOperation::GetPost => {
|
UserOperation::GetPost => {
|
||||||
let get_post: GetPost = serde_json::from_str(&data.to_string()).unwrap();
|
let get_post: GetPost = serde_json::from_str(&data.to_string()).unwrap();
|
||||||
get_post.perform()
|
get_post.perform(self, msg.id)
|
||||||
},
|
},
|
||||||
UserOperation::GetCommunity => {
|
UserOperation::GetCommunity => {
|
||||||
let get_community: GetCommunity = serde_json::from_str(&data.to_string()).unwrap();
|
let get_community: GetCommunity = serde_json::from_str(&data.to_string()).unwrap();
|
||||||
get_community.perform()
|
get_community.perform(self, msg.id)
|
||||||
},
|
},
|
||||||
UserOperation::CreateComment => {
|
UserOperation::CreateComment => {
|
||||||
let create_comment: CreateComment = serde_json::from_str(&data.to_string()).unwrap();
|
let create_comment: CreateComment = serde_json::from_str(&data.to_string()).unwrap();
|
||||||
create_comment.perform()
|
create_comment.perform(self, msg.id)
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
let e = ErrorMessage {
|
let e = ErrorMessage {
|
||||||
|
@ -351,7 +350,6 @@ impl Handler<StandardMessage> for ChatServer {
|
||||||
};
|
};
|
||||||
serde_json::to_string(&e).unwrap()
|
serde_json::to_string(&e).unwrap()
|
||||||
}
|
}
|
||||||
// _ => "no".to_string()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
MessageResult(res)
|
MessageResult(res)
|
||||||
|
@ -360,7 +358,7 @@ impl Handler<StandardMessage> for ChatServer {
|
||||||
|
|
||||||
|
|
||||||
pub trait Perform {
|
pub trait Perform {
|
||||||
fn perform(&self) -> String;
|
fn perform(&self, chat: &mut ChatServer, addr: usize) -> String;
|
||||||
fn op_type(&self) -> UserOperation;
|
fn op_type(&self) -> UserOperation;
|
||||||
fn error(&self, error_msg: &str) -> String {
|
fn error(&self, error_msg: &str) -> String {
|
||||||
serde_json::to_string(
|
serde_json::to_string(
|
||||||
|
@ -377,7 +375,7 @@ impl Perform for Login {
|
||||||
fn op_type(&self) -> UserOperation {
|
fn op_type(&self) -> UserOperation {
|
||||||
UserOperation::Login
|
UserOperation::Login
|
||||||
}
|
}
|
||||||
fn perform(&self) -> String {
|
fn perform(&self, chat: &mut ChatServer, addr: usize) -> String {
|
||||||
|
|
||||||
let conn = establish_connection();
|
let conn = establish_connection();
|
||||||
|
|
||||||
|
@ -409,7 +407,7 @@ impl Perform for Register {
|
||||||
fn op_type(&self) -> UserOperation {
|
fn op_type(&self) -> UserOperation {
|
||||||
UserOperation::Register
|
UserOperation::Register
|
||||||
}
|
}
|
||||||
fn perform(&self) -> String {
|
fn perform(&self, chat: &mut ChatServer, addr: usize) -> String {
|
||||||
|
|
||||||
let conn = establish_connection();
|
let conn = establish_connection();
|
||||||
|
|
||||||
|
@ -452,7 +450,7 @@ impl Perform for CreateCommunity {
|
||||||
UserOperation::CreateCommunity
|
UserOperation::CreateCommunity
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform(&self) -> String {
|
fn perform(&self, chat: &mut ChatServer, addr: usize) -> String {
|
||||||
|
|
||||||
let conn = establish_connection();
|
let conn = establish_connection();
|
||||||
|
|
||||||
|
@ -505,7 +503,7 @@ impl Perform for ListCommunities {
|
||||||
UserOperation::ListCommunities
|
UserOperation::ListCommunities
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform(&self) -> String {
|
fn perform(&self, chat: &mut ChatServer, addr: usize) -> String {
|
||||||
|
|
||||||
let conn = establish_connection();
|
let conn = establish_connection();
|
||||||
|
|
||||||
|
@ -527,7 +525,7 @@ impl Perform for CreatePost {
|
||||||
UserOperation::CreatePost
|
UserOperation::CreatePost
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform(&self) -> String {
|
fn perform(&self, chat: &mut ChatServer, addr: usize) -> String {
|
||||||
|
|
||||||
let conn = establish_connection();
|
let conn = establish_connection();
|
||||||
|
|
||||||
|
@ -574,7 +572,7 @@ impl Perform for GetPost {
|
||||||
UserOperation::GetPost
|
UserOperation::GetPost
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform(&self) -> String {
|
fn perform(&self, chat: &mut ChatServer, addr: usize) -> String {
|
||||||
|
|
||||||
let conn = establish_connection();
|
let conn = establish_connection();
|
||||||
|
|
||||||
|
@ -585,8 +583,38 @@ impl Perform for GetPost {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// let mut rooms = Vec::new();
|
||||||
|
|
||||||
|
// remove session from all rooms
|
||||||
|
for (n, sessions) in &mut chat.rooms {
|
||||||
|
// if sessions.remove(&addr) {
|
||||||
|
// // rooms.push(*n);
|
||||||
|
// }
|
||||||
|
sessions.remove(&addr);
|
||||||
|
}
|
||||||
|
// // send message to other users
|
||||||
|
// for room in rooms {
|
||||||
|
// self.send_room_message(&room, "Someone disconnected", 0);
|
||||||
|
// }
|
||||||
|
|
||||||
|
if chat.rooms.get_mut(&self.id).is_none() {
|
||||||
|
chat.rooms.insert(self.id, HashSet::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO send a Joined response
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// chat.send_room_message(addr,)
|
||||||
|
// self.send_room_message(&name, "Someone connected", id);
|
||||||
|
chat.rooms.get_mut(&self.id).unwrap().insert(addr);
|
||||||
|
|
||||||
let comments = Comment::from_post(&conn, &post).unwrap();
|
let comments = Comment::from_post(&conn, &post).unwrap();
|
||||||
|
|
||||||
|
println!("{:?}", chat.rooms.keys());
|
||||||
|
println!("{:?}", chat.rooms.get(&5i32).unwrap());
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
serde_json::to_string(
|
serde_json::to_string(
|
||||||
&GetPostResponse {
|
&GetPostResponse {
|
||||||
|
@ -604,7 +632,7 @@ impl Perform for GetCommunity {
|
||||||
UserOperation::GetCommunity
|
UserOperation::GetCommunity
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform(&self) -> String {
|
fn perform(&self, chat: &mut ChatServer, addr: usize) -> String {
|
||||||
|
|
||||||
let conn = establish_connection();
|
let conn = establish_connection();
|
||||||
|
|
||||||
|
@ -631,7 +659,7 @@ impl Perform for CreateComment {
|
||||||
UserOperation::CreateComment
|
UserOperation::CreateComment
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform(&self) -> String {
|
fn perform(&self, chat: &mut ChatServer, addr: usize) -> String {
|
||||||
|
|
||||||
let conn = establish_connection();
|
let conn = establish_connection();
|
||||||
|
|
||||||
|
@ -660,13 +688,20 @@ impl Perform for CreateComment {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
serde_json::to_string(
|
let comment_out = serde_json::to_string(
|
||||||
&CreateCommentResponse {
|
&CreateCommentResponse {
|
||||||
op: self.op_type().to_string(),
|
op: self.op_type().to_string(),
|
||||||
comment: inserted_comment
|
comment: inserted_comment
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap();
|
||||||
|
|
||||||
|
chat.send_room_message(self.post_id, &comment_out, addr);
|
||||||
|
|
||||||
|
println!("{:?}", chat.rooms.keys());
|
||||||
|
println!("{:?}", chat.rooms.get(&5i32).unwrap());
|
||||||
|
|
||||||
|
comment_out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,6 @@ export class CreateCommunity extends Component<any, State> {
|
||||||
|
|
||||||
handleCommunityNameChange(i: CreateCommunity, event) {
|
handleCommunityNameChange(i: CreateCommunity, event) {
|
||||||
i.state.communityForm.name = event.target.value;
|
i.state.communityForm.name = event.target.value;
|
||||||
i.setState(i.state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: any) {
|
||||||
|
|
|
@ -105,22 +105,18 @@ export class CreatePost extends Component<any, State> {
|
||||||
|
|
||||||
handlePostUrlChange(i: CreatePost, event) {
|
handlePostUrlChange(i: CreatePost, event) {
|
||||||
i.state.postForm.url = event.target.value;
|
i.state.postForm.url = event.target.value;
|
||||||
i.setState(i.state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePostNameChange(i: CreatePost, event) {
|
handlePostNameChange(i: CreatePost, event) {
|
||||||
i.state.postForm.name = event.target.value;
|
i.state.postForm.name = event.target.value;
|
||||||
i.setState(i.state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePostBodyChange(i: CreatePost, event) {
|
handlePostBodyChange(i: CreatePost, event) {
|
||||||
i.state.postForm.body = event.target.value;
|
i.state.postForm.body = event.target.value;
|
||||||
i.setState(i.state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePostCommunityChange(i: CreatePost, event) {
|
handlePostCommunityChange(i: CreatePost, event) {
|
||||||
i.state.postForm.community_id = Number(event.target.value);
|
i.state.postForm.community_id = Number(event.target.value);
|
||||||
i.setState(i.state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: any) {
|
||||||
|
@ -132,6 +128,7 @@ export class CreatePost extends Component<any, State> {
|
||||||
} else if (op == UserOperation.ListCommunities) {
|
} else if (op == UserOperation.ListCommunities) {
|
||||||
let res: ListCommunitiesResponse = msg;
|
let res: ListCommunitiesResponse = msg;
|
||||||
this.state.communities = res.communities;
|
this.state.communities = res.communities;
|
||||||
|
this.state.postForm.community_id = res.communities[0].id; // TODO set it to the default community
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreatePost) {
|
} else if (op == UserOperation.CreatePost) {
|
||||||
let res: PostResponse = msg;
|
let res: PostResponse = msg;
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
import { Component, linkEvent } from 'inferno';
|
import { Component, linkEvent } from 'inferno';
|
||||||
import { Subscription } from "rxjs";
|
import { Subscription } from "rxjs";
|
||||||
import { retryWhen, delay, take } from 'rxjs/operators';
|
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||||
import { UserOperation, Community, Post as PostI, PostResponse, Comment, CommentForm } from '../interfaces';
|
import { UserOperation, Community, Post as PostI, PostResponse, Comment, CommentForm as CommentFormI, CommentResponse } from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import { msgOp } from '../utils';
|
import { msgOp } from '../utils';
|
||||||
|
|
||||||
|
interface CommentNodeI {
|
||||||
|
comment: Comment;
|
||||||
|
children?: Array<CommentNodeI>;
|
||||||
|
showReply?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
post: PostI;
|
post: PostI;
|
||||||
commentForm: CommentForm;
|
|
||||||
comments: Array<Comment>;
|
comments: Array<Comment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,21 +27,15 @@ export class Post extends Component<any, State> {
|
||||||
id: null,
|
id: null,
|
||||||
published: null,
|
published: null,
|
||||||
},
|
},
|
||||||
commentForm: {
|
|
||||||
auth: null,
|
|
||||||
content: null,
|
|
||||||
post_id: null
|
|
||||||
},
|
|
||||||
comments: []
|
comments: []
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
let postId = Number(this.props.match.params.id);
|
|
||||||
|
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
this.state.commentForm.post_id = postId;
|
|
||||||
|
this.state.post.id = Number(this.props.match.params.id);
|
||||||
|
|
||||||
this.subscription = WebSocketService.Instance.subject
|
this.subscription = WebSocketService.Instance.subject
|
||||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
|
@ -46,7 +45,7 @@ export class Post extends Component<any, State> {
|
||||||
() => console.log('complete')
|
() => console.log('complete')
|
||||||
);
|
);
|
||||||
|
|
||||||
WebSocketService.Instance.getPost(postId);
|
WebSocketService.Instance.getPost(this.state.post.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -57,58 +56,87 @@ export class Post extends Component<any, State> {
|
||||||
return (
|
return (
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-lg-6 mb-4">
|
<div class="col-12 col-sm-8 col-lg-7 mb-3">
|
||||||
{this.state.post.name}
|
{this.postHeader()}
|
||||||
{this.commentForm()}
|
<CommentForm postId={this.state.post.id} />
|
||||||
{this.comments()}
|
{this.commentsTree()}
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-4 col-lg-3 mb-3">
|
||||||
|
{this.newComments()}
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-12 col-lg-2">
|
||||||
|
{this.sidebar()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
comments() {
|
postHeader() {
|
||||||
|
let title = this.state.post.url
|
||||||
|
? <h5><a href={this.state.post.url}>{this.state.post.name}</a></h5>
|
||||||
|
: <h5>{this.state.post.name}</h5>;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3>Comments</h3>
|
{title}
|
||||||
|
via {this.state.post.attributed_to} X hours ago
|
||||||
|
{this.state.post.body}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
newComments() {
|
||||||
|
return (
|
||||||
|
<div class="sticky-top">
|
||||||
|
<h4>New Comments</h4>
|
||||||
{this.state.comments.map(comment =>
|
{this.state.comments.map(comment =>
|
||||||
<div>{comment.content}</div>
|
<CommentNodes nodes={[{comment: comment}]} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sidebar() {
|
||||||
commentForm() {
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div class="sticky-top">
|
||||||
<form onSubmit={linkEvent(this, this.handleCreateCommentSubmit)}>
|
<h4>Sidebar</h4>
|
||||||
<h3>Create Comment</h3>
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-2 col-form-label">Name</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<textarea class="form-control" value={this.state.commentForm.content} onInput={linkEvent(this, this.handleCommentContentChange)} required minLength={3} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<button type="submit" class="btn btn-secondary">Create</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCreateCommentSubmit(i: Post, event) {
|
// buildCommentsTree(): Array<CommentNodeI> {
|
||||||
event.preventDefault();
|
buildCommentsTree(): any {
|
||||||
WebSocketService.Instance.createComment(i.state.commentForm);
|
let tree: Array<CommentNodeI> = this.createCommentsTree(this.state.comments);
|
||||||
|
console.log(tree); // TODO this is redoing every time and it shouldn't
|
||||||
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCommentContentChange(i: Post, event) {
|
private createCommentsTree(comments: Array<Comment>): Array<CommentNodeI> {
|
||||||
i.state.commentForm.content = event.target.value;
|
let hashTable = {};
|
||||||
i.setState(i.state);
|
for (let comment of comments) {
|
||||||
|
let node: CommentNodeI = {
|
||||||
|
comment: comment
|
||||||
|
};
|
||||||
|
hashTable[comment.id] = { ...node, children : [] };
|
||||||
}
|
}
|
||||||
|
let tree: Array<CommentNodeI> = [];
|
||||||
|
for (let comment of comments) {
|
||||||
|
if( comment.parent_id ) hashTable[comment.parent_id].children.push(hashTable[comment.id]);
|
||||||
|
else tree.push(hashTable[comment.id]);
|
||||||
|
}
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
commentsTree() {
|
||||||
|
let nodes = this.buildCommentsTree();
|
||||||
|
return (
|
||||||
|
<div className="sticky-top">
|
||||||
|
<CommentNodes nodes={nodes} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: any) {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
|
@ -119,8 +147,160 @@ export class Post extends Component<any, State> {
|
||||||
} else if (op == UserOperation.GetPost) {
|
} else if (op == UserOperation.GetPost) {
|
||||||
let res: PostResponse = msg;
|
let res: PostResponse = msg;
|
||||||
this.state.post = res.post;
|
this.state.post = res.post;
|
||||||
this.state.comments = res.comments;
|
this.state.comments = res.comments.reverse();
|
||||||
|
this.setState(this.state);
|
||||||
|
} else if (op == UserOperation.CreateComment) {
|
||||||
|
let res: CommentResponse = msg;
|
||||||
|
this.state.comments.unshift(res.comment);
|
||||||
|
this.setState(this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CommentNodesState {
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CommentNodesProps {
|
||||||
|
nodes: Array<CommentNodeI>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CommentNodes extends Component<CommentNodesProps, CommentNodesState> {
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.handleReplyClick = this.handleReplyClick.bind(this);
|
||||||
|
this.handleReplyCancel = this.handleReplyCancel.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="comments">
|
||||||
|
{this.props.nodes.map(node =>
|
||||||
|
<div className="comment ml-2">
|
||||||
|
<div className="float-left small font-weight-light">
|
||||||
|
<div className="pointer">▲</div>
|
||||||
|
<div className="pointer">▼</div>
|
||||||
|
</div>
|
||||||
|
<div className="details ml-4">
|
||||||
|
<ul class="list-inline mb-0 text-muted small">
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<a href={node.comment.attributed_to}>{node.comment.attributed_to}</a>
|
||||||
|
</li>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<span>(
|
||||||
|
<span className="text-info"> 1300</span>
|
||||||
|
<span> | </span>
|
||||||
|
<span className="text-danger">-29</span>
|
||||||
|
<span> ) points</span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<span>X hours ago</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mb-0">{node.comment.content}</p>
|
||||||
|
<ul class="list-inline mb-1 text-muted small font-weight-bold">
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<span class="pointer" onClick={linkEvent(node, this.handleReplyClick)}>reply</span>
|
||||||
|
</li>
|
||||||
|
<li className="list-inline-item">
|
||||||
|
<a className="text-muted" href="test">link</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{node.showReply && <CommentForm node={node} onReplyCancel={this.handleReplyCancel} />}
|
||||||
|
{node.children && <CommentNodes nodes={node.children}/>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReplyClick(i: CommentNodeI, event) {
|
||||||
|
i.showReply = true;
|
||||||
|
this.setState(this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReplyCancel(i: CommentNodeI): any {
|
||||||
|
i.showReply = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CommentFormProps {
|
||||||
|
postId?: number;
|
||||||
|
node?: CommentNodeI;
|
||||||
|
onReplyCancel?(node: CommentNodeI);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CommentFormState {
|
||||||
|
commentForm: CommentFormI;
|
||||||
|
topReply: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
|
|
||||||
|
private emptyState: CommentFormState = {
|
||||||
|
commentForm: {
|
||||||
|
auth: null,
|
||||||
|
content: null,
|
||||||
|
post_id: null,
|
||||||
|
parent_id: null
|
||||||
|
},
|
||||||
|
topReply: true
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = this.emptyState;
|
||||||
|
if (this.props.node) {
|
||||||
|
this.state.topReply = false;
|
||||||
|
this.state.commentForm.post_id = this.props.node.comment.post_id;
|
||||||
|
this.state.commentForm.parent_id = this.props.node.comment.id;
|
||||||
|
} else {
|
||||||
|
this.state.commentForm.post_id = this.props.postId;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(this.state);
|
||||||
|
|
||||||
|
this.handleReplyCancel = this.handleReplyCancel.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<form onSubmit={linkEvent(this, this.handleCreateCommentSubmit)}>
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<textarea class="form-control" value={this.state.commentForm.content} onInput={linkEvent(this, this.handleCommentContentChange)} placeholder="Comment here" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<button type="submit" class="btn btn-secondary mr-2">Post</button>
|
||||||
|
{!this.state.topReply && <button type="button" class="btn btn-secondary" onClick={this.handleReplyCancel}>Cancel</button>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCreateCommentSubmit(i: CommentForm, event) {
|
||||||
|
WebSocketService.Instance.createComment(i.state.commentForm);
|
||||||
|
i.state.commentForm.content = undefined;
|
||||||
|
i.setState(i.state);
|
||||||
|
event.target.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCommentContentChange(i: CommentForm, event) {
|
||||||
|
// TODO don't use setState, it triggers a re-render
|
||||||
|
i.state.commentForm.content = event.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReplyCancel(event) {
|
||||||
|
this.props.onReplyCancel(this.props.node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
.pointer {
|
.pointer {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,10 +59,6 @@ export class WebSocketService {
|
||||||
this.subject.next(this.wsSendWrapper(UserOperation.CreateComment, commentForm));
|
this.subject.next(this.wsSendWrapper(UserOperation.CreateComment, commentForm));
|
||||||
}
|
}
|
||||||
|
|
||||||
public getComments(postId: number) {
|
|
||||||
this.subject.next(this.wsSendWrapper(UserOperation.GetComments, {post_id: postId}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private wsSendWrapper(op: UserOperation, data: any) {
|
private wsSendWrapper(op: UserOperation, data: any) {
|
||||||
let send = { op: UserOperation[op], data: data };
|
let send = { op: UserOperation[op], data: data };
|
||||||
console.log(send);
|
console.log(send);
|
||||||
|
|
Loading…
Reference in a new issue