]> Untitled Git - lemmy.git/commitdiff
Adding instant voting / vote animations. Fixes #526
authorDessalines <tyhou13@gmx.com>
Sun, 9 Feb 2020 20:04:41 +0000 (15:04 -0500)
committerDessalines <tyhou13@gmx.com>
Sun, 9 Feb 2020 20:04:41 +0000 (15:04 -0500)
server/src/websocket/server.rs
ui/assets/css/main.css
ui/src/components/comment-node.tsx
ui/src/components/post-listing.tsx
ui/src/interfaces.ts
ui/src/utils.ts

index 003b886e180bf8d8898bbe79746048db1789f4b5..1cbcb34fb738ebd10e853d65a196fa10246b5e28 100644 (file)
@@ -280,6 +280,9 @@ impl ChatServer {
     self.send_community_room_message(0, &post_sent_str, id);
     self.send_community_room_message(community_id, &post_sent_str, id);
 
+    // Send it to the post room
+    self.send_post_room_message(post_sent.post.id, &post_sent_str, id);
+
     to_json_string(&user_operation, post)
   }
 
index b1ad884a575588b9fd925a12d390478ae2c538dc..df75ec319cfbd0414c5929e66e88aab06d0da045 100644 (file)
@@ -175,3 +175,9 @@ hr {
 .img-expanded {
   max-height: 90vh;
 }
+
+.vote-animate:active {
+  transform: scale(1.2);
+  -webkit-transform: scale(1.2);
+  -ms-transform: scale(1.2);
+}
index 0e51106398d57f9d946f522a494095d3ec4a1ae0..1d0b12cad6b9a5eec1e726a14d6d76c4a80a93d7 100644 (file)
@@ -48,8 +48,10 @@ interface CommentNodeState {
   showConfirmAppointAsAdmin: boolean;
   collapsed: boolean;
   viewSource: boolean;
-  upvoteLoading: boolean;
-  downvoteLoading: boolean;
+  my_vote: number;
+  score: number;
+  upvotes: number;
+  downvotes: number;
 }
 
 interface CommentNodeProps {
@@ -83,8 +85,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
     showConfirmTransferCommunity: false,
     showConfirmAppointAsMod: false,
     showConfirmAppointAsAdmin: false,
-    upvoteLoading: this.props.node.comment.upvoteLoading,
-    downvoteLoading: this.props.node.comment.downvoteLoading,
+    my_vote: this.props.node.comment.my_vote,
+    score: this.props.node.comment.score,
+    upvotes: this.props.node.comment.upvotes,
+    downvotes: this.props.node.comment.downvotes,
   };
 
   constructor(props: any, context: any) {
@@ -97,15 +101,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
   }
 
   componentWillReceiveProps(nextProps: CommentNodeProps) {
-    if (
-      nextProps.node.comment.upvoteLoading !== this.state.upvoteLoading ||
-      nextProps.node.comment.downvoteLoading !== this.state.downvoteLoading
-    ) {
-      this.setState({
-        upvoteLoading: false,
-        downvoteLoading: false,
-      });
-    }
+    this.state.my_vote = nextProps.node.comment.my_vote;
+    this.state.upvotes = nextProps.node.comment.upvotes;
+    this.state.downvotes = nextProps.node.comment.downvotes;
+    this.state.score = nextProps.node.comment.score;
+    this.setState(this.state);
   }
 
   render() {
@@ -122,40 +122,26 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
               .viewOnly && 'no-click'}`}
           >
             <button
-              className={`btn btn-link p-0 ${
-                node.comment.my_vote == 1 ? 'text-info' : 'text-muted'
+              className={`vote-animate btn btn-link p-0 ${
+                this.state.my_vote == 1 ? 'text-info' : 'text-muted'
               }`}
               onClick={linkEvent(node, this.handleCommentUpvote)}
             >
-              {this.state.upvoteLoading ? (
-                <svg class="icon icon-spinner spin upvote">
-                  <use xlinkHref="#icon-spinner"></use>
-                </svg>
-              ) : (
-                <svg class="icon upvote">
-                  <use xlinkHref="#icon-arrow-up"></use>
-                </svg>
-              )}
+              <svg class="icon upvote">
+                <use xlinkHref="#icon-arrow-up"></use>
+              </svg>
             </button>
-            <div class={`font-weight-bold text-muted`}>
-              {node.comment.score}
-            </div>
+            <div class={`font-weight-bold text-muted`}>{this.state.score}</div>
             {WebSocketService.Instance.site.enable_downvotes && (
               <button
-                className={`btn btn-link p-0 ${
-                  node.comment.my_vote == -1 ? 'text-danger' : 'text-muted'
+                className={`vote-animate btn btn-link p-0 ${
+                  this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
                 }`}
                 onClick={linkEvent(node, this.handleCommentDownvote)}
               >
-                {this.state.downvoteLoading ? (
-                  <svg class="icon icon-spinner spin downvote">
-                    <use xlinkHref="#icon-spinner"></use>
-                  </svg>
-                ) : (
-                  <svg class="icon downvote">
-                    <use xlinkHref="#icon-arrow-down"></use>
-                  </svg>
-                )}
+                <svg class="icon downvote">
+                  <use xlinkHref="#icon-arrow-down"></use>
+                </svg>
               </button>
             )}
           </div>
@@ -205,9 +191,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
             )}
             <li className="list-inline-item">
               <span>
-                (<span className="text-info">+{node.comment.upvotes}</span>
+                (<span className="text-info">+{this.state.upvotes}</span>
                 <span> | </span>
-                <span className="text-danger">-{node.comment.downvotes}</span>
+                <span className="text-danger">-{this.state.downvotes}</span>
                 <span>) </span>
               </span>
             </li>
@@ -772,31 +758,57 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
   }
 
   handleCommentUpvote(i: CommentNodeI) {
-    if (UserService.Instance.user) {
-      this.setState({
-        upvoteLoading: true,
-      });
+    let new_vote = this.state.my_vote == 1 ? 0 : 1;
+
+    if (this.state.my_vote == 1) {
+      this.state.score--;
+      this.state.upvotes--;
+    } else if (this.state.my_vote == -1) {
+      this.state.downvotes--;
+      this.state.upvotes++;
+      this.state.score += 2;
+    } else {
+      this.state.upvotes++;
+      this.state.score++;
     }
+
+    this.state.my_vote = new_vote;
+
     let form: CommentLikeForm = {
       comment_id: i.comment.id,
       post_id: i.comment.post_id,
-      score: i.comment.my_vote == 1 ? 0 : 1,
+      score: this.state.my_vote,
     };
+
     WebSocketService.Instance.likeComment(form);
+    this.setState(this.state);
   }
 
   handleCommentDownvote(i: CommentNodeI) {
-    if (UserService.Instance.user) {
-      this.setState({
-        downvoteLoading: true,
-      });
+    let new_vote = this.state.my_vote == -1 ? 0 : -1;
+
+    if (this.state.my_vote == 1) {
+      this.state.score -= 2;
+      this.state.upvotes--;
+      this.state.downvotes++;
+    } else if (this.state.my_vote == -1) {
+      this.state.downvotes--;
+      this.state.score++;
+    } else {
+      this.state.downvotes++;
+      this.state.score--;
     }
+
+    this.state.my_vote = new_vote;
+
     let form: CommentLikeForm = {
       comment_id: i.comment.id,
       post_id: i.comment.post_id,
-      score: i.comment.my_vote == -1 ? 0 : -1,
+      score: this.state.my_vote,
     };
+
     WebSocketService.Instance.likeComment(form);
+    this.setState(this.state);
   }
 
   handleModRemoveShow(i: CommentNode) {
index f11d9e1441439f3c2bcb11e7e796ff7e1ca4e5cc..1e7289013594fe8694e06e8989baef2bbb277d10 100644 (file)
@@ -43,8 +43,10 @@ interface PostListingState {
   showConfirmTransferCommunity: boolean;
   imageExpanded: boolean;
   viewSource: boolean;
-  upvoteLoading: boolean;
-  downvoteLoading: boolean;
+  my_vote: number;
+  score: number;
+  upvotes: number;
+  downvotes: number;
 }
 
 interface PostListingProps {
@@ -68,8 +70,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
     showConfirmTransferCommunity: false,
     imageExpanded: false,
     viewSource: false,
-    upvoteLoading: this.props.post.upvoteLoading,
-    downvoteLoading: this.props.post.downvoteLoading,
+    my_vote: this.props.post.my_vote,
+    score: this.props.post.score,
+    upvotes: this.props.post.upvotes,
+    downvotes: this.props.post.downvotes,
   };
 
   constructor(props: any, context: any) {
@@ -83,15 +87,11 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
   }
 
   componentWillReceiveProps(nextProps: PostListingProps) {
-    if (
-      nextProps.post.upvoteLoading !== this.state.upvoteLoading ||
-      nextProps.post.downvoteLoading !== this.state.downvoteLoading
-    ) {
-      this.setState({
-        upvoteLoading: false,
-        downvoteLoading: false,
-      });
-    }
+    this.state.my_vote = nextProps.post.my_vote;
+    this.state.upvotes = nextProps.post.upvotes;
+    this.state.downvotes = nextProps.post.downvotes;
+    this.state.score = nextProps.post.score;
+    this.setState(this.state);
   }
 
   render() {
@@ -118,38 +118,26 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
       <div class="listing col-12">
         <div className={`vote-bar mr-2 float-left small text-center`}>
           <button
-            className={`btn btn-link p-0 ${
-              post.my_vote == 1 ? 'text-info' : 'text-muted'
+            className={`vote-animate btn btn-link p-0 ${
+              this.state.my_vote == 1 ? 'text-info' : 'text-muted'
             }`}
             onClick={linkEvent(this, this.handlePostLike)}
           >
-            {this.state.upvoteLoading ? (
-              <svg class="icon icon-spinner spin upvote">
-                <use xlinkHref="#icon-spinner"></use>
-              </svg>
-            ) : (
-              <svg class="icon upvote">
-                <use xlinkHref="#icon-arrow-up"></use>
-              </svg>
-            )}
+            <svg class="icon upvote">
+              <use xlinkHref="#icon-arrow-up"></use>
+            </svg>
           </button>
-          <div class={`font-weight-bold text-muted`}>{post.score}</div>
+          <div class={`font-weight-bold text-muted`}>{this.state.score}</div>
           {WebSocketService.Instance.site.enable_downvotes && (
             <button
-              className={`btn btn-link p-0 ${
-                post.my_vote == -1 ? 'text-danger' : 'text-muted'
+              className={`vote-animate btn btn-link p-0 ${
+                this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
               }`}
               onClick={linkEvent(this, this.handlePostDisLike)}
             >
-              {this.state.downvoteLoading ? (
-                <svg class="icon icon-spinner spin downvote">
-                  <use xlinkHref="#icon-spinner"></use>
-                </svg>
-              ) : (
-                <svg class="icon downvote">
-                  <use xlinkHref="#icon-arrow-down"></use>
-                </svg>
-              )}
+              <svg class="icon downvote">
+                <use xlinkHref="#icon-arrow-down"></use>
+              </svg>
             </button>
           )}
         </div>
@@ -315,9 +303,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
             </li>
             <li className="list-inline-item">
               <span>
-                (<span className="text-info">+{post.upvotes}</span>
+                (<span className="text-info">+{this.state.upvotes}</span>
                 <span> | </span>
-                <span className="text-danger">-{post.downvotes}</span>
+                <span className="text-danger">-{this.state.downvotes}</span>
                 <span>) </span>
               </span>
             </li>
@@ -747,28 +735,55 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
   }
 
   handlePostLike(i: PostListing) {
-    if (UserService.Instance.user) {
-      i.setState({ upvoteLoading: true });
+    let new_vote = i.state.my_vote == 1 ? 0 : 1;
+
+    if (i.state.my_vote == 1) {
+      i.state.score--;
+      i.state.upvotes--;
+    } else if (i.state.my_vote == -1) {
+      i.state.downvotes--;
+      i.state.upvotes++;
+      i.state.score += 2;
+    } else {
+      i.state.upvotes++;
+      i.state.score++;
     }
 
+    i.state.my_vote = new_vote;
+
     let form: CreatePostLikeForm = {
       post_id: i.props.post.id,
-      score: i.props.post.my_vote == 1 ? 0 : 1,
+      score: i.state.my_vote,
     };
 
     WebSocketService.Instance.likePost(form);
+    i.setState(i.state);
   }
 
   handlePostDisLike(i: PostListing) {
-    if (UserService.Instance.user) {
-      i.setState({ downvoteLoading: true });
+    let new_vote = i.state.my_vote == -1 ? 0 : -1;
+
+    if (i.state.my_vote == 1) {
+      i.state.score -= 2;
+      i.state.upvotes--;
+      i.state.downvotes++;
+    } else if (i.state.my_vote == -1) {
+      i.state.downvotes--;
+      i.state.score++;
+    } else {
+      i.state.downvotes++;
+      i.state.score--;
     }
 
+    i.state.my_vote = new_vote;
+
     let form: CreatePostLikeForm = {
       post_id: i.props.post.id,
-      score: i.props.post.my_vote == -1 ? 0 : -1,
+      score: i.state.my_vote,
     };
+
     WebSocketService.Instance.likePost(form);
+    i.setState(i.state);
   }
 
   handleEditClick(i: PostListing) {
index 185056f3f121c32079cf90182d79f614416bfaeb..23551b595264d945ab43c54db32a71f1357f0261 100644 (file)
@@ -177,8 +177,6 @@ export interface Post {
   subscribed?: boolean;
   read?: boolean;
   saved?: boolean;
-  upvoteLoading?: boolean;
-  downvoteLoading?: boolean;
   duplicates?: Array<Post>;
 }
 
@@ -209,8 +207,6 @@ export interface Comment {
   saved?: boolean;
   user_mention_id?: number; // For mention type
   recipient_id?: number;
-  upvoteLoading?: boolean;
-  downvoteLoading?: boolean;
 }
 
 export interface Category {
index 929877fc7fa9262ad6e30a5751639cc099c7471f..384d5c1d500ab71d56ee456fc05704f8d6aba6c8 100644 (file)
@@ -601,8 +601,6 @@ export function createCommentLikeRes(
     found.downvotes = data.comment.downvotes;
     if (data.comment.my_vote !== null) {
       found.my_vote = data.comment.my_vote;
-      found.upvoteLoading = false;
-      found.downvoteLoading = false;
     }
   }
 }
@@ -620,8 +618,6 @@ export function createPostLikeRes(data: PostResponse, post: Post) {
   post.downvotes = data.post.downvotes;
   if (data.post.my_vote !== null) {
     post.my_vote = data.post.my_vote;
-    post.upvoteLoading = false;
-    post.downvoteLoading = false;
   }
 }