Adding comment and post vote loading indicators. Fixes #449

This commit is contained in:
Dessalines 2020-01-20 10:11:50 -05:00
parent f020e89a8c
commit e31090c9bd
4 changed files with 93 additions and 86 deletions

View file

@ -47,8 +47,8 @@ interface CommentNodeState {
showConfirmAppointAsAdmin: boolean; showConfirmAppointAsAdmin: boolean;
collapsed: boolean; collapsed: boolean;
viewSource: boolean; viewSource: boolean;
my_vote: number; upvoteLoading: boolean;
score: number; downvoteLoading: boolean;
} }
interface CommentNodeProps { interface CommentNodeProps {
@ -78,8 +78,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
showConfirmTransferCommunity: false, showConfirmTransferCommunity: false,
showConfirmAppointAsMod: false, showConfirmAppointAsMod: false,
showConfirmAppointAsAdmin: false, showConfirmAppointAsAdmin: false,
my_vote: this.props.node.comment.my_vote, upvoteLoading: this.props.node.comment.upvoteLoading,
score: this.props.node.comment.score, downvoteLoading: this.props.node.comment.downvoteLoading,
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -87,18 +87,18 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.state = this.emptyState; this.state = this.emptyState;
this.handleReplyCancel = this.handleReplyCancel.bind(this); this.handleReplyCancel = this.handleReplyCancel.bind(this);
this.handleCommentLike = this.handleCommentLike.bind(this); this.handleCommentUpvote = this.handleCommentUpvote.bind(this);
this.handleCommentDisLike = this.handleCommentDisLike.bind(this); this.handleCommentDownvote = this.handleCommentDownvote.bind(this);
} }
componentDidUpdate(prevProps: CommentNodeProps) { componentWillReceiveProps(nextProps: CommentNodeProps) {
if ( if (
prevProps.node.comment.my_vote !== this.props.node.comment.my_vote || nextProps.node.comment.upvoteLoading !== this.state.upvoteLoading ||
this.state.score !== this.props.node.comment.score nextProps.node.comment.downvoteLoading !== this.state.downvoteLoading
) { ) {
this.setState({ this.setState({
my_vote: this.props.node.comment.my_vote, upvoteLoading: false,
score: this.props.node.comment.score, downvoteLoading: false,
}); });
} }
} }
@ -119,26 +119,40 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<button <button
disabled={!UserService.Instance.user} disabled={!UserService.Instance.user}
className={`btn p-0 ${ className={`btn p-0 ${
this.state.my_vote == 1 ? 'text-info' : 'text-muted' node.comment.my_vote == 1 ? 'text-info' : 'text-muted'
}`} }`}
onClick={linkEvent(node, this.handleCommentLike)} onClick={linkEvent(node, this.handleCommentUpvote)}
> >
<svg class="icon upvote"> {this.state.upvoteLoading ? (
<use xlinkHref="#icon-arrow-up"></use> <svg class="icon icon-spinner spin upvote">
</svg> <use xlinkHref="#icon-spinner"></use>
</svg>
) : (
<svg class="icon upvote">
<use xlinkHref="#icon-arrow-up"></use>
</svg>
)}
</button> </button>
<div class={`font-weight-bold text-muted`}>{this.state.score}</div> <div class={`font-weight-bold text-muted`}>
{node.comment.score}
</div>
{WebSocketService.Instance.site.enable_downvotes && ( {WebSocketService.Instance.site.enable_downvotes && (
<button <button
disabled={!UserService.Instance.user} disabled={!UserService.Instance.user}
className={`btn p-0 ${ className={`btn p-0 ${
this.state.my_vote == -1 ? 'text-danger' : 'text-muted' node.comment.my_vote == -1 ? 'text-danger' : 'text-muted'
}`} }`}
onClick={linkEvent(node, this.handleCommentDisLike)} onClick={linkEvent(node, this.handleCommentDownvote)}
> >
<svg class="icon downvote"> {this.state.downvoteLoading ? (
<use xlinkHref="#icon-arrow-down"></use> <svg class="icon icon-spinner spin downvote">
</svg> <use xlinkHref="#icon-spinner"></use>
</svg>
) : (
<svg class="icon downvote">
<use xlinkHref="#icon-arrow-down"></use>
</svg>
)}
</button> </button>
)} )}
</div> </div>
@ -736,41 +750,26 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.setState(this.state); this.setState(this.state);
} }
handleCommentLike(i: CommentNodeI) { handleCommentUpvote(i: CommentNodeI) {
this.state.my_vote = i.comment.my_vote == 1 ? 0 : 1; this.setState({
let add = 1; upvoteLoading: true,
if (i.comment.my_vote == 1) { });
add = -1;
} else if (i.comment.my_vote == -1) {
add = 2;
}
this.state.score = i.comment.score + add;
this.setState(this.state);
let form: CommentLikeForm = { let form: CommentLikeForm = {
comment_id: i.comment.id, comment_id: i.comment.id,
post_id: i.comment.post_id, post_id: i.comment.post_id,
score: this.state.my_vote, score: i.comment.my_vote == 1 ? 0 : 1,
}; };
WebSocketService.Instance.likeComment(form); WebSocketService.Instance.likeComment(form);
} }
handleCommentDisLike(i: CommentNodeI) { handleCommentDownvote(i: CommentNodeI) {
this.state.my_vote = i.comment.my_vote == -1 ? 0 : -1; this.setState({
let add = -1; downvoteLoading: true,
if (i.comment.my_vote == 1) { });
add = -2;
} else if (i.comment.my_vote == -1) {
add = 1;
}
this.state.score = i.comment.score + add;
this.setState(this.state);
let form: CommentLikeForm = { let form: CommentLikeForm = {
comment_id: i.comment.id, comment_id: i.comment.id,
post_id: i.comment.post_id, post_id: i.comment.post_id,
score: this.state.my_vote, score: i.comment.my_vote == -1 ? 0 : -1,
}; };
WebSocketService.Instance.likeComment(form); WebSocketService.Instance.likeComment(form);
} }

View file

@ -44,8 +44,8 @@ interface PostListingState {
showConfirmTransferCommunity: boolean; showConfirmTransferCommunity: boolean;
imageExpanded: boolean; imageExpanded: boolean;
viewSource: boolean; viewSource: boolean;
my_vote: number; upvoteLoading: boolean;
score: number; downvoteLoading: boolean;
} }
interface PostListingProps { interface PostListingProps {
@ -70,8 +70,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
showConfirmTransferCommunity: false, showConfirmTransferCommunity: false,
imageExpanded: false, imageExpanded: false,
viewSource: false, viewSource: false,
my_vote: this.props.post.my_vote, upvoteLoading: this.props.post.upvoteLoading,
score: this.props.post.score, downvoteLoading: this.props.post.downvoteLoading,
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -84,11 +84,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this.handleEditCancel = this.handleEditCancel.bind(this); this.handleEditCancel = this.handleEditCancel.bind(this);
} }
componentDidUpdate(prevProps: PostListingProps) { componentWillReceiveProps(nextProps: PostListingProps) {
if (prevProps.post.my_vote !== this.props.post.my_vote) { if (
nextProps.post.upvoteLoading !== this.state.upvoteLoading ||
nextProps.post.downvoteLoading !== this.state.downvoteLoading
) {
this.setState({ this.setState({
my_vote: this.props.post.my_vote, upvoteLoading: false,
score: this.props.post.score, downvoteLoading: false,
}); });
} }
} }
@ -122,26 +125,38 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button <button
disabled={!UserService.Instance.user} disabled={!UserService.Instance.user}
className={`btn p-0 ${ className={`btn p-0 ${
this.state.my_vote == 1 ? 'text-info' : 'text-muted' post.my_vote == 1 ? 'text-info' : 'text-muted'
}`} }`}
onClick={linkEvent(this, this.handlePostLike)} onClick={linkEvent(this, this.handlePostLike)}
> >
<svg class="icon upvote"> {this.state.upvoteLoading ? (
<use xlinkHref="#icon-arrow-up"></use> <svg class="icon icon-spinner spin upvote">
</svg> <use xlinkHref="#icon-spinner"></use>
</svg>
) : (
<svg class="icon upvote">
<use xlinkHref="#icon-arrow-up"></use>
</svg>
)}
</button> </button>
<div class={`font-weight-bold text-muted`}>{this.state.score}</div> <div class={`font-weight-bold text-muted`}>{post.score}</div>
{WebSocketService.Instance.site.enable_downvotes && ( {WebSocketService.Instance.site.enable_downvotes && (
<button <button
disabled={!UserService.Instance.user} disabled={!UserService.Instance.user}
className={`btn p-0 ${ className={`btn p-0 ${
this.state.my_vote == -1 ? 'text-danger' : 'text-muted' post.my_vote == -1 ? 'text-danger' : 'text-muted'
}`} }`}
onClick={linkEvent(this, this.handlePostDisLike)} onClick={linkEvent(this, this.handlePostDisLike)}
> >
<svg class="icon downvote"> {this.state.downvoteLoading ? (
<use xlinkHref="#icon-arrow-down"></use> <svg class="icon icon-spinner spin downvote">
</svg> <use xlinkHref="#icon-spinner"></use>
</svg>
) : (
<svg class="icon downvote">
<use xlinkHref="#icon-arrow-down"></use>
</svg>
)}
</button> </button>
)} )}
</div> </div>
@ -731,38 +746,21 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
handlePostLike(i: PostListing) { handlePostLike(i: PostListing) {
this.state.my_vote = i.props.post.my_vote == 1 ? 0 : 1; i.setState({ upvoteLoading: true });
let add = 1;
if (i.props.post.my_vote == 1) {
add = -1;
} else if (i.props.post.my_vote == -1) {
add = 2;
}
this.state.score = i.props.post.score + add;
this.setState(this.state);
let form: CreatePostLikeForm = { let form: CreatePostLikeForm = {
post_id: i.props.post.id, post_id: i.props.post.id,
score: this.state.my_vote, score: i.props.post.my_vote == 1 ? 0 : 1,
}; };
WebSocketService.Instance.likePost(form); WebSocketService.Instance.likePost(form);
} }
handlePostDisLike(i: PostListing) { handlePostDisLike(i: PostListing) {
this.state.my_vote = i.props.post.my_vote == -1 ? 0 : -1; i.setState({ downvoteLoading: true });
let add = -1;
if (i.props.post.my_vote == 1) {
add = -2;
} else if (i.props.post.my_vote == -1) {
add = 1;
}
this.state.score = i.props.post.score + add;
this.setState(this.state);
let form: CreatePostLikeForm = { let form: CreatePostLikeForm = {
post_id: i.props.post.id, post_id: i.props.post.id,
score: this.state.my_vote, score: i.props.post.my_vote == -1 ? 0 : -1,
}; };
WebSocketService.Instance.likePost(form); WebSocketService.Instance.likePost(form);
} }

View file

@ -400,7 +400,11 @@ export class Post extends Component<any, PostState> {
found.score = res.comment.score; found.score = res.comment.score;
found.upvotes = res.comment.upvotes; found.upvotes = res.comment.upvotes;
found.downvotes = res.comment.downvotes; found.downvotes = res.comment.downvotes;
if (res.comment.my_vote !== null) found.my_vote = res.comment.my_vote; if (res.comment.my_vote !== null) {
found.my_vote = res.comment.my_vote;
found.upvoteLoading = false;
found.downvoteLoading = false;
}
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.CreatePostLike) { } else if (op == UserOperation.CreatePostLike) {
let res: CreatePostLikeResponse = msg; let res: CreatePostLikeResponse = msg;
@ -408,6 +412,8 @@ export class Post extends Component<any, PostState> {
this.state.post.score = res.post.score; this.state.post.score = res.post.score;
this.state.post.upvotes = res.post.upvotes; this.state.post.upvotes = res.post.upvotes;
this.state.post.downvotes = res.post.downvotes; this.state.post.downvotes = res.post.downvotes;
this.state.post.upvoteLoading = false;
this.state.post.downvoteLoading = false;
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.EditPost) { } else if (op == UserOperation.EditPost) {
let res: PostResponse = msg; let res: PostResponse = msg;

View file

@ -164,6 +164,8 @@ export interface Post {
subscribed?: boolean; subscribed?: boolean;
read?: boolean; read?: boolean;
saved?: boolean; saved?: boolean;
upvoteLoading?: boolean;
downvoteLoading?: boolean;
} }
export interface Comment { export interface Comment {
@ -190,6 +192,8 @@ export interface Comment {
saved?: boolean; saved?: boolean;
user_mention_id?: number; // For mention type user_mention_id?: number; // For mention type
recipient_id?: number; recipient_id?: number;
upvoteLoading?: boolean;
downvoteLoading?: boolean;
} }
export interface Category { export interface Category {