Adding loading indicators to save and mark as read. #519

This commit is contained in:
Dessalines 2020-03-19 11:45:23 -04:00
parent ed00fe46e2
commit 47dd8acf54
4 changed files with 95 additions and 63 deletions

View file

@ -56,6 +56,8 @@ interface CommentNodeState {
upvotes: number; upvotes: number;
downvotes: number; downvotes: number;
borderColor: string; borderColor: string;
readLoading: boolean;
saveLoading: boolean;
} }
interface CommentNodeProps { interface CommentNodeProps {
@ -97,6 +99,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
borderColor: this.props.node.comment.depth borderColor: this.props.node.comment.depth
? colorList[this.props.node.comment.depth % colorList.length] ? colorList[this.props.node.comment.depth % colorList.length]
: colorList[0], : colorList[0],
readLoading: false,
saveLoading: false,
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -113,6 +117,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.state.upvotes = nextProps.node.comment.upvotes; this.state.upvotes = nextProps.node.comment.upvotes;
this.state.downvotes = nextProps.node.comment.downvotes; this.state.downvotes = nextProps.node.comment.downvotes;
this.state.score = nextProps.node.comment.score; this.state.score = nextProps.node.comment.score;
this.state.readLoading = false;
this.state.saveLoading = false;
this.setState(this.state); this.setState(this.state);
} }
@ -255,12 +261,16 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
: i18n.t('mark_as_read') : i18n.t('mark_as_read')
} }
> >
<svg {this.state.readLoading ? (
class={`icon icon-inline ${node.comment.read && this.loadingIcon
'text-success'}`} ) : (
> <svg
<use xlinkHref="#icon-check"></use> class={`icon icon-inline ${node.comment.read &&
</svg> 'text-success'}`}
>
<use xlinkHref="#icon-check"></use>
</svg>
)}
</button> </button>
</li> </li>
)} )}
@ -305,6 +315,28 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
</button> </button>
</li> </li>
)} )}
<li className="list-inline-item">
<button
class="btn btn-link btn-sm btn-animate text-muted"
onClick={linkEvent(this, this.handleSaveCommentClick)}
data-tippy-content={
node.comment.saved
? i18n.t('unsave')
: i18n.t('save')
}
>
{this.state.saveLoading ? (
this.loadingIcon
) : (
<svg
class={`icon icon-inline ${node.comment.saved &&
'text-warning'}`}
>
<use xlinkHref="#icon-star"></use>
</svg>
)}
</button>
</li>
<li className="list-inline-item"> <li className="list-inline-item">
<button <button
class="btn btn-link btn-sm btn-animate text-muted" class="btn btn-link btn-sm btn-animate text-muted"
@ -316,17 +348,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
</svg> </svg>
</button> </button>
</li> </li>
<li className="list-inline-item"> {this.props.markable && this.linkBtn}
<Link
className="btn btn-link btn-sm btn-animate text-muted"
to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}
title={i18n.t('link')}
>
<svg class="icon icon-inline">
<use xlinkHref="#icon-link"></use>
</svg>
</Link>
</li>
{!this.state.showAdvanced ? ( {!this.state.showAdvanced ? (
<li className="list-inline-item"> <li className="list-inline-item">
<button <button
@ -354,27 +376,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
</Link> </Link>
</li> </li>
)} )}
<li className="list-inline-item"> {!this.props.markable && this.linkBtn}
<button
class="btn btn-link btn-sm btn-animate text-muted"
onClick={linkEvent(
this,
this.handleSaveCommentClick
)}
data-tippy-content={
node.comment.saved
? i18n.t('unsave')
: i18n.t('save')
}
>
<svg
class={`icon icon-inline ${node.comment.saved &&
'text-warning'}`}
>
<use xlinkHref="#icon-star"></use>
</svg>
</button>
</li>
<li className="list-inline-item"> <li className="list-inline-item">
<button <button
className="btn btn-link btn-sm btn-animate text-muted" className="btn btn-link btn-sm btn-animate text-muted"
@ -756,6 +758,31 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
); );
} }
get linkBtn() {
let node = this.props.node;
return (
<li className="list-inline-item">
<Link
className="btn btn-link btn-sm btn-animate text-muted"
to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}
title={i18n.t('link')}
>
<svg class="icon icon-inline">
<use xlinkHref="#icon-link"></use>
</svg>
</Link>
</li>
);
}
get loadingIcon() {
return (
<svg class="icon icon-spinner spin">
<use xlinkHref="#icon-spinner"></use>
</svg>
);
}
get myComment(): boolean { get myComment(): boolean {
return ( return (
UserService.Instance.user && UserService.Instance.user &&
@ -875,6 +902,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
}; };
WebSocketService.Instance.saveComment(form); WebSocketService.Instance.saveComment(form);
i.state.saveLoading = true;
i.setState(this.state);
} }
handleReplyCancel() { handleReplyCancel() {
@ -987,6 +1017,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
}; };
WebSocketService.Instance.editComment(form); WebSocketService.Instance.editComment(form);
} }
i.state.readLoading = true;
i.setState(this.state);
} }
handleModBanFromCommunityShow(i: CommentNode) { handleModBanFromCommunityShow(i: CommentNode) {

View file

@ -28,8 +28,7 @@ export class PostListings extends Component<PostListingsProps, any> {
post={post} post={post}
showCommunity={this.props.showCommunity} showCommunity={this.props.showCommunity}
/> />
<hr class="d-md-none my-2" /> <hr class="my-2" />
<div class="d-none d-md-block my-2"></div>
</> </>
)) ))
) : ( ) : (

View file

@ -211,7 +211,7 @@ export class Post extends Component<any, PostState> {
sortRadios() { sortRadios() {
return ( return (
<div class="btn-group btn-group-toggle"> <div class="btn-group btn-group-toggle mb-2">
<label <label
className={`btn btn-sm btn-secondary pointer ${this.state className={`btn btn-sm btn-secondary pointer ${this.state
.commentSort === CommentSortType.Hot && 'active'}`} .commentSort === CommentSortType.Hot && 'active'}`}

View file

@ -55,7 +55,7 @@ export class PrivateMessage extends Component<
render() { render() {
let message = this.props.privateMessage; let message = this.props.privateMessage;
return ( return (
<div class="mb-2"> <div class="border-top border-light">
<div> <div>
<ul class="list-inline mb-0 text-muted small"> <ul class="list-inline mb-0 text-muted small">
<li className="list-inline-item"> <li className="list-inline-item">
@ -129,12 +129,12 @@ export class PrivateMessage extends Component<
dangerouslySetInnerHTML={mdToHtml(this.messageUnlessRemoved)} dangerouslySetInnerHTML={mdToHtml(this.messageUnlessRemoved)}
/> />
)} )}
<ul class="list-inline mb-1 text-muted h5 font-weight-bold"> <ul class="list-inline mb-0 text-muted font-weight-bold">
{!this.mine && ( {!this.mine && (
<> <>
<li className="list-inline-item-action"> <li className="list-inline-item">
<span <button
class="pointer" class="btn btn-link btn-sm btn-animate text-muted"
onClick={linkEvent(this, this.handleMarkRead)} onClick={linkEvent(this, this.handleMarkRead)}
data-tippy-content={ data-tippy-content={
message.read message.read
@ -148,37 +148,37 @@ export class PrivateMessage extends Component<
> >
<use xlinkHref="#icon-check"></use> <use xlinkHref="#icon-check"></use>
</svg> </svg>
</span> </button>
</li> </li>
<li className="list-inline-item-action"> <li className="list-inline-item">
<span <button
class="pointer" class="btn btn-link btn-sm btn-animate text-muted"
onClick={linkEvent(this, this.handleReplyClick)} onClick={linkEvent(this, this.handleReplyClick)}
data-tippy-content={i18n.t('reply')} data-tippy-content={i18n.t('reply')}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
<use xlinkHref="#icon-reply1"></use> <use xlinkHref="#icon-reply1"></use>
</svg> </svg>
</span> </button>
</li> </li>
</> </>
)} )}
{this.mine && ( {this.mine && (
<> <>
<li className="list-inline-item-action"> <li className="list-inline-item">
<span <button
class="pointer" class="btn btn-link btn-sm btn-animate text-muted"
onClick={linkEvent(this, this.handleEditClick)} onClick={linkEvent(this, this.handleEditClick)}
data-tippy-content={i18n.t('edit')} data-tippy-content={i18n.t('edit')}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
<use xlinkHref="#icon-edit"></use> <use xlinkHref="#icon-edit"></use>
</svg> </svg>
</span> </button>
</li> </li>
<li className="list-inline-item-action"> <li className="list-inline-item">
<span <button
class="pointer" class="btn btn-link btn-sm btn-animate text-muted"
onClick={linkEvent(this, this.handleDeleteClick)} onClick={linkEvent(this, this.handleDeleteClick)}
data-tippy-content={ data-tippy-content={
!message.deleted !message.deleted
@ -192,13 +192,13 @@ export class PrivateMessage extends Component<
> >
<use xlinkHref="#icon-trash"></use> <use xlinkHref="#icon-trash"></use>
</svg> </svg>
</span> </button>
</li> </li>
</> </>
)} )}
<li className="list-inline-item-action"> <li className="list-inline-item">
<span <button
className="pointer" class="btn btn-link btn-sm btn-animate text-muted"
onClick={linkEvent(this, this.handleViewSource)} onClick={linkEvent(this, this.handleViewSource)}
data-tippy-content={i18n.t('view_source')} data-tippy-content={i18n.t('view_source')}
> >
@ -208,7 +208,7 @@ export class PrivateMessage extends Component<
> >
<use xlinkHref="#icon-file-text"></use> <use xlinkHref="#icon-file-text"></use>
</svg> </svg>
</span> </button>
</li> </li>
</ul> </ul>
</div> </div>