Skip to content

Commit

Permalink
Merge pull request #731 from coralproject/button-states
Browse files Browse the repository at this point in the history
Support post, reply, edit, load and view more loading states via global css classnames
  • Loading branch information
kgardnr authored Jun 30, 2017
2 parents 0188385 + 7858ccc commit e3ead31
Show file tree
Hide file tree
Showing 13 changed files with 311 additions and 177 deletions.
39 changes: 20 additions & 19 deletions client/coral-embed-stream/src/components/Comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import CommentContent from './CommentContent';
import Slot from 'coral-framework/components/Slot';
import IgnoredCommentTombstone from './IgnoredCommentTombstone';
import {EditableCommentContent} from './EditableCommentContent';
import {getActionSummary, iPerformedThisAction} from 'coral-framework/utils';
import {getActionSummary, iPerformedThisAction, forEachError} from 'coral-framework/utils';
import t from 'coral-framework/services/i18n';

const isStaff = (tags) => !tags.every((t) => t.tag.name !== 'STAFF');
Expand Down Expand Up @@ -75,7 +75,6 @@ const ActionButton = ({children}) => {
};

export default class Comment extends React.Component {
isLoadingReplies = false;

constructor(props) {
super(props);
Expand All @@ -90,6 +89,7 @@ export default class Comment extends React.Component {
isEditing: false,
replyBoxVisible: false,
animateEnter: false,
loadingState: '',
...resetCursors({}, props),
};
}
Expand Down Expand Up @@ -220,24 +220,23 @@ export default class Comment extends React.Component {
}

loadNewReplies = () => {
if (!this.isLoadingReplies) {
this.isLoadingReplies = true;
const {replies, replyCount, id} = this.props.comment;
if (replyCount > replies.nodes.length) {
this.props.loadMore(id)
.then(() => {
this.setState(resetCursors(this.state, this.props));
this.isLoadingReplies = false;
})
.catch((e) => {
this.isLoadingReplies = false;
throw e;
const {replies, replyCount, id} = this.props.comment;
if (replyCount > replies.nodes.length) {
this.setState({loadingState: 'loading'});
this.props.loadMore(id)
.then(() => {
this.setState({
...resetCursors(this.state, this.props),
loadingState: 'success',
});
return;
}
this.setState(resetCursors);
this.isLoadingReplies = false;
})
.catch((error) => {
this.setState({loadingState: 'error'});
forEachError(error, ({msg}) => {this.props.addNotification('error', msg);});
});
return;
}
this.setState(resetCursors);
};

showReplyBox = () => {
Expand Down Expand Up @@ -331,6 +330,7 @@ export default class Comment extends React.Component {
} = this.props;

const view = this.getVisibileReplies();
const {loadingState} = this.state;

const hasMoreComments = comment.replies && (comment.replies.hasNextPage || comment.replies.nodes.length > view.length);
const replyCount = this.hasIgnoredReplies() ? '' : comment.replyCount;
Expand Down Expand Up @@ -595,12 +595,13 @@ export default class Comment extends React.Component {
/>;
})}
</TransitionGroup>
<div className="coral-load-more-replies">
<div className="talk-load-more-replies">
<LoadMore
topLevel={false}
replyCount={replyCount}
moreComments={hasMoreComments}
loadMore={this.loadNewReplies}
loadingState={loadingState}
/>
</div>
</div>
Expand Down
91 changes: 49 additions & 42 deletions client/coral-embed-stream/src/components/EditableCommentContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import styles from './Comment.css';
import {CountdownSeconds} from './CountdownSeconds';
import {getEditableUntilDate} from './util';
import {can} from 'coral-framework/services/perms';
import {forEachError} from 'coral-framework/utils';

import {Icon} from 'coral-ui';
import t from 'coral-framework/services/i18n';
Expand Down Expand Up @@ -46,9 +47,14 @@ export class EditableCommentContent extends React.Component {
// called when editing should be stopped
stopEditing: React.PropTypes.func,
}

constructor(props) {
super(props);
this.editWindowExpiryTimeout = null;
this.state = {
body: props.comment.body,
loadingState: '',
};
}
componentDidMount() {
const editableUntil = getEditableUntilDate(this.props.comment);
Expand All @@ -65,74 +71,75 @@ export class EditableCommentContent extends React.Component {
this.editWindowExpiryTimeout = clearTimeout(this.editWindowExpiryTimeout);
}
}
editComment = async (edit) => {

handleBodyChange = (body) => {
this.setState({body});
}

handleSubmit = async () => {
if (!can(this.props.currentUser, 'INTERACT_WITH_COMMUNITY')) {
this.props.addNotification('error', t('error.NOT_AUTHORIZED'));
return;
}

this.setState({loadingState: 'loading'});

const {editComment, addNotification, stopEditing} = this.props;
if (typeof editComment !== 'function') {return;}
let response;
let successfullyEdited = false;
try {
response = await editComment(edit);
const errors = (response && response.data && response.data.editComment)
? response.data.editComment.errors
: null;
if (errors && (errors.length === 1)) {
throw errors[0];
}
successfullyEdited = true;
} catch (error) {
const errors = error.errors || [error];
errors.forEach((e) => {
if (e.translation_key) {
addNotification('error', t(`error.${e.translation_key}`));
} else if (error.networkError) {
addNotification('error', t('error.network_error'));
} else {
addNotification('error', t('edit_comment.unexpected_error'));
console.error(e);
}
});
}
if (successfullyEdited) {
response = await editComment({body: this.state.body});
this.setState({loadingState: 'success'});
const status = response.data.editComment.comment.status;
notifyForNewCommentStatus(this.props.addNotification, status);
}
if (successfullyEdited && typeof stopEditing === 'function') {
stopEditing();
if (typeof stopEditing === 'function') {
stopEditing();
}
} catch (error) {
this.setState({loadingState: 'error'});
forEachError(error, ({msg}) => addNotification('error', msg));
}
}

getEditableUntil = (props = this.props) => {
return getEditableUntilDate(props.comment);
}

isEditWindowExpired = (props = this.props) => {
return (this.getEditableUntil(props) - new Date()) < 0;
}

isSubmitEnabled = (comment) => {

// should be disabled if user hasn't actually changed their
// original comment
return (comment.body !== this.props.comment.body) && !this.isEditWindowExpired();
}

render() {
const originalBody = this.props.comment.body;
const editableUntil = getEditableUntilDate(this.props.comment);
const editWindowExpired = (editableUntil - new Date()) < 0;
return (
<div className={styles.editCommentForm}>
<CommentForm
defaultValue={this.props.comment.body}
charCountEnable={this.props.asset.settings.charCountEnable}
maxCharCount={this.props.maxCharCount}
saveCommentEnabled={(comment) => {

// should be disabled if user hasn't actually changed their
// original comment
return (comment.body !== originalBody) && !editWindowExpired;
}}
saveComment={this.editComment}
submitEnabled={this.isSubmitEnabled}
body={this.state.body}
onBodyChange={this.handleBodyChange}
onSubmit={this.handleSubmit}
bodyLabel={t('edit_comment.body_input_label')}
bodyPlaceholder=""
submitText={<span>{t('edit_comment.save_button')}</span>}
saveButtonCStyle="green"
cancelButtonClicked={this.props.stopEditing}
buttonClass={styles.button}
submitButtonCStyle="green"
onCancel={this.props.stopEditing}
submitButtonClassName={styles.button}
cancelButtonClassName={styles.button}
loadingState={this.state.loadingState}
buttonContainerStart={
<div className={styles.buttonContainerLeft}>
<span className={styles.editWindowRemaining}>
{
editWindowExpired
this.isEditWindowExpired()
? <span>
{t('edit_comment.edit_window_expired')}
{
Expand All @@ -144,7 +151,7 @@ export class EditableCommentContent extends React.Component {
: <span>
<Icon name="timer"/> {t('edit_comment.edit_window_timer_prefix')}
<CountdownSeconds
until={editableUntil}
until={this.getEditableUntil()}
classNameForMsRemaining={(remainingMs) => (remainingMs <= 10 * 1000) ? styles.editWindowAlmostOver : '' }
/>
</span>
Expand Down
30 changes: 18 additions & 12 deletions client/coral-embed-stream/src/components/LoadMore.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import React, {PropTypes} from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import {Button} from 'coral-ui';
import t from 'coral-framework/services/i18n';
import cn from 'classnames';

class LoadMore extends React.Component {

componentDidMount () {
this.initialState = true;
}
initialState = true;

replyCountFormat = (count) => {
if (!count) {
Expand All @@ -23,17 +22,22 @@ class LoadMore extends React.Component {
}
}

loadMore = () => {
this.initialState = false;
this.props.loadMore();
componentWillReceiveProps(nextProps) {
if (['success', 'error'].indexOf(nextProps.loadingState) >= 0) {
this.initialState = false;
}
}

render () {
const {topLevel, moreComments, replyCount} = this.props;
const {topLevel, moreComments, replyCount, loadingState, loadMore} = this.props;
const disabled = loadingState === 'loading';
return moreComments
? <div className='coral-load-more'>
? <div className='talk-load-more'>
<Button
onClick={this.loadMore}>
onClick={loadMore}
className={cn('talk-load-more-button', {[`talk-load-more-button-${loadingState}`]: loadingState})}
disabled={disabled}
>
{topLevel ? t('framework.view_more_comments') : this.replyCountFormat(replyCount)}
</Button>
</div>
Expand All @@ -44,7 +48,9 @@ class LoadMore extends React.Component {
LoadMore.propTypes = {
replyCount: PropTypes.number,
topLevel: PropTypes.bool.isRequired,
loadMore: PropTypes.func.isRequired
loadMore: PropTypes.func.isRequired,
moreComments: PropTypes.bool,
loadingState: PropTypes.oneOf(['', 'loading', 'success', 'error']),
};

export default LoadMore;
30 changes: 15 additions & 15 deletions client/coral-embed-stream/src/components/Stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import QuestionBox from 'coral-plugin-questionbox/QuestionBox';
import IgnoredCommentTombstone from './IgnoredCommentTombstone';
import NewCount from './NewCount';
import {TransitionGroup} from 'react-transition-group';
import {forEachError} from 'coral-framework/utils';

const hasComment = (nodes, id) => nodes.some((node) => node.id === id);

Expand Down Expand Up @@ -54,13 +55,12 @@ function invalidateCursor(invalidated, state, props) {

class Stream extends React.Component {

isLoadingMore = false;

constructor(props) {
super(props);
this.state = {
...resetCursors(this.state, props),
keepCommentBox: false,
loadingState: '',
};
}

Expand Down Expand Up @@ -108,15 +108,15 @@ class Stream extends React.Component {
};

loadMoreComments = () => {
if (!this.isLoadingMore) {
this.isLoadingMore = true;
this.props.loadMoreComments()
.then(() => this.isLoadingMore = false)
.catch((e) => {
this.isLoadingMore = false;
throw e;
});
}
this.setState({loadingState: 'loading'});
this.props.loadMoreComments()
.then(() => {
this.setState({loadingState: 'success'});
})
.catch((error) => {
this.setState({loadingState: 'error'});
forEachError(error, ({msg}) => {this.props.addNotification('error', msg);});
});
}

// getVisibileComments returns a list containing comments
Expand Down Expand Up @@ -164,8 +164,7 @@ class Stream extends React.Component {
pluginProps,
editName
} = this.props;

const {keepCommentBox} = this.state;
const {keepCommentBox, loadingState} = this.state;
const view = this.getVisibleComments();
const open = asset.closedAt === null;

Expand All @@ -190,8 +189,8 @@ class Stream extends React.Component {

const showCommentBox = loggedIn && ((!banned && !temporarilySuspended && !highlightedComment) || keepCommentBox);

if (!comment && !comments) {
console.error('Talk: No comments came back from the graph given that query. Please, check the query params.');
if (!comment && !comments) {
console.error('Talk: No comments came back from the graph given that query. Please, check the query params.');
return <StreamError />;
}

Expand Down Expand Up @@ -325,6 +324,7 @@ class Stream extends React.Component {
topLevel={true}
moreComments={asset.comments.hasNextPage}
loadMore={this.loadMoreComments}
loadingState={loadingState}
/>
</div>}
</div>
Expand Down
Loading

0 comments on commit e3ead31

Please sign in to comment.