Extract NotificationList state (#2185)

* Extract NotificationList state
This commit is contained in:
Alexander Skvortsov 2020-06-18 17:08:06 -04:00 committed by GitHub
parent 5bca4fda9d
commit 65f2d5fb75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 117 additions and 99 deletions

View File

@ -14,6 +14,7 @@ import routes from './routes';
import alertEmailConfirmation from './utils/alertEmailConfirmation';
import Application from '../common/Application';
import Navigation from '../common/components/Navigation';
import NotificationListState from './states/NotificationListState';
export default class ForumApplication extends Application {
/**
@ -63,6 +64,13 @@ export default class ForumApplication extends Application {
*/
history = new History();
/**
* An object which controls the state of the user's notifications.
*
* @type {NotificationListState}
*/
notifications = new NotificationListState(this);
constructor() {
super();

View File

@ -67,7 +67,7 @@ export default class HeaderSecondary extends Component {
}
if (app.session.user) {
items.add('notifications', NotificationsDropdown.component(), 10);
items.add('notifications', NotificationsDropdown.component({ state: app.notifications }), 10);
items.add('session', SessionDropdown.component(), 0);
} else {
if (app.forum.attribute('allowSignUp')) {

View File

@ -10,23 +10,11 @@ import Discussion from '../../common/models/Discussion';
*/
export default class NotificationList extends Component {
init() {
/**
* Whether or not the notifications are loading.
*
* @type {Boolean}
*/
this.loading = false;
/**
* Whether or not there are more results that can be loaded.
*
* @type {Boolean}
*/
this.moreResults = false;
this.state = this.props.state;
}
view() {
const pages = app.cache.notifications || [];
const pages = this.state.getNotificationPages();
return (
<div className="NotificationList">
@ -36,7 +24,7 @@ export default class NotificationList extends Component {
className: 'Button Button--icon Button--link',
icon: 'fas fa-check',
title: app.translator.trans('core.forum.notifications.mark_all_as_read_tooltip'),
onclick: this.markAllAsRead.bind(this),
onclick: this.state.markAllAsRead.bind(this.state),
})}
</div>
@ -97,7 +85,7 @@ export default class NotificationList extends Component {
});
})
: ''}
{this.loading ? (
{this.state.isLoading() ? (
<LoadingIndicator className="LoadingIndicator--block" />
) : pages.length ? (
''
@ -121,8 +109,8 @@ export default class NotificationList extends Component {
const contentTop = $scrollParent === $notifications ? 0 : $notifications.offset().top;
const contentHeight = $notifications[0].scrollHeight;
if (this.moreResults && !this.loading && scrollTop + viewportHeight >= contentTop + contentHeight) {
this.loadMore();
if (this.state.hasMoreResults() && !this.state.isLoading() && scrollTop + viewportHeight >= contentTop + contentHeight) {
this.state.loadMore();
}
};
@ -132,77 +120,4 @@ export default class NotificationList extends Component {
$scrollParent.off('scroll', scrollHandler);
};
}
/**
* Load notifications into the application's cache if they haven't already
* been loaded.
*/
load() {
if (app.session.user.newNotificationCount()) {
delete app.cache.notifications;
}
if (app.cache.notifications) {
return;
}
app.session.user.pushAttributes({ newNotificationCount: 0 });
this.loadMore();
}
/**
* Load the next page of notification results.
*
* @public
*/
loadMore() {
this.loading = true;
m.redraw();
const params = app.cache.notifications ? { page: { offset: app.cache.notifications.length * 10 } } : null;
return app.store
.find('notifications', params)
.then(this.parseResults.bind(this))
.catch(() => {})
.then(() => {
this.loading = false;
m.redraw();
});
}
/**
* Parse results and append them to the notification list.
*
* @param {Notification[]} results
* @return {Notification[]}
*/
parseResults(results) {
app.cache.notifications = app.cache.notifications || [];
if (results.length) app.cache.notifications.push(results);
this.moreResults = !!results.payload.links.next;
return results;
}
/**
* Mark all of the notifications as read.
*/
markAllAsRead() {
if (!app.cache.notifications) return;
app.session.user.pushAttributes({ unreadNotificationCount: 0 });
app.cache.notifications.forEach((notifications) => {
notifications.forEach((notification) => notification.pushAttributes({ isRead: true }));
});
app.request({
url: app.forum.attribute('apiUrl') + '/notifications/read',
method: 'POST',
});
}
}

View File

@ -15,8 +15,6 @@ export default class NotificationsDropdown extends Dropdown {
init() {
super.init();
this.list = new NotificationList();
}
getButton() {
@ -44,7 +42,7 @@ export default class NotificationsDropdown extends Dropdown {
getMenu() {
return (
<div className={'Dropdown-menu ' + this.props.menuClassName} onclick={this.menuClick.bind(this)}>
{this.showing ? this.list.render() : ''}
{this.showing ? NotificationList.component({ state: this.props.state }) : ''}
</div>
);
}
@ -53,7 +51,7 @@ export default class NotificationsDropdown extends Dropdown {
if (app.drawer.isOpen()) {
this.goToRoute();
} else {
this.list.load();
this.props.state.load();
}
}

View File

@ -11,13 +11,16 @@ export default class NotificationsPage extends Page {
app.history.push('notifications');
this.list = new NotificationList();
this.list.load();
app.notifications.load();
this.bodyClass = 'App--notifications';
}
view() {
return <div className="NotificationsPage">{this.list.render()}</div>;
return (
<div className="NotificationsPage">
<NotificationList state={app.notifications}></NotificationList>
</div>
);
}
}

View File

@ -0,0 +1,94 @@
export default class NotificationListState {
constructor(app) {
this.app = app;
this.notificationPages = [];
this.loading = false;
this.moreResults = false;
}
getNotificationPages() {
return this.notificationPages;
}
isLoading() {
return this.loading;
}
hasMoreResults() {
return this.moreResults;
}
/**
* Load notifications into the application's cache if they haven't already
* been loaded.
*/
load() {
if (this.app.session.user.newNotificationCount()) {
this.notificationPages = [];
}
if (this.notificationPages.length > 0) {
return;
}
this.app.session.user.pushAttributes({ newNotificationCount: 0 });
this.loadMore();
}
/**
* Load the next page of notification results.
*
* @public
*/
loadMore() {
this.loading = true;
m.redraw();
const params = this.notificationPages.length > 0 ? { page: { offset: this.notificationPages.length * 10 } } : null;
return this.app.store
.find('notifications', params)
.then(this.parseResults.bind(this))
.catch(() => {})
.then(() => {
this.loading = false;
m.redraw();
});
}
/**
* Parse results and append them to the notification list.
*
* @param {Notification[]} results
* @return {Notification[]}
*/
parseResults(results) {
if (results.length) this.notificationPages.push(results);
this.moreResults = !!results.payload.links.next;
return results;
}
/**
* Mark all of the notifications as read.
*/
markAllAsRead() {
if (this.notificationPages.length === 0) return;
this.app.session.user.pushAttributes({ unreadNotificationCount: 0 });
this.notificationPages.forEach((notifications) => {
notifications.forEach((notification) => notification.pushAttributes({ isRead: true }));
});
this.app.request({
url: this.app.forum.attribute('apiUrl') + '/notifications/read',
method: 'POST',
});
}
}