[#34] Add react-list & lazy-loading to Messages & Adjust store to make requests

This commit is contained in:
Jannis Mattheis 2018-04-08 17:45:25 +02:00 committed by Jannis Mattheis
parent 9ed6228013
commit ca5a832baf
4 changed files with 119 additions and 64 deletions

View File

@ -6,7 +6,6 @@ import dispatcher from '../stores/dispatcher';
export function initialLoad(resp) {
AppAction.fetchApps();
MessageAction.fetchMessages();
MessageAction.listenToWebSocket();
ClientAction.fetchClients();
if (resp.data.admin) {

View File

@ -18,6 +18,9 @@ const styles = () => ({
marginTop: -15,
marginRight: -15,
},
wrapperPadding: {
padding: 12,
},
messageContentWrapper: {
width: '100%',
},
@ -33,33 +36,34 @@ class Message extends Component {
static propTypes = {
classes: PropTypes.object.isRequired,
title: PropTypes.string.isRequired,
image: PropTypes.string.isRequired,
image: PropTypes.string,
date: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
fDelete: PropTypes.func.isRequired,
};
render() {
const {fDelete, classes, title, date, content, image} = this.props;
return (
<Container style={{display: 'flex'}}>
<div className={classes.imageWrapper}>
<img src={image} alt="app logo" width="70" height="70" className={classes.image}/>
</div>
<div className={classes.messageContentWrapper}>
<div className={classes.header}>
<Typography className={classes.headerTitle} variant="headline">
{title}
</Typography>
<Typography variant="body1">
<TimeAgo date={date}/>
</Typography>
<IconButton onClick={fDelete} className={classes.trash}><Delete/></IconButton>
<div className={classes.wrapperPadding}>
<Container style={{display: 'flex'}}>
<div className={classes.imageWrapper}>
<img src={image} alt="app logo" width="70" height="70" className={classes.image}/>
</div>
<Typography component="p">{content}</Typography>
</div>
</Container>
<div className={classes.messageContentWrapper}>
<div className={classes.header}>
<Typography className={classes.headerTitle} variant="headline">
{title}
</Typography>
<Typography variant="body1">
<TimeAgo date={date}/>
</Typography>
<IconButton onClick={fDelete} className={classes.trash}><Delete/></IconButton>
</div>
<Typography component="p">{content}</Typography>
</div>
</Container>
</div>
);
}
}

View File

@ -6,12 +6,11 @@ import MessageStore from '../stores/MessageStore';
import AppStore from '../stores/AppStore';
import * as MessageAction from '../actions/MessageAction';
import DefaultPage from '../component/DefaultPage';
import ReactList from 'react-list';
import {CircularProgress} from 'material-ui/Progress';
class Messages extends Component {
constructor() {
super();
this.state = {appId: -1, messages: [], name: 'unknown'};
}
state = {appId: -1, messages: [], name: 'unknown', hasMore: true, list: null};
componentWillReceiveProps(nextProps) {
this.updateAllWithProps(nextProps);
@ -30,9 +29,10 @@ class Messages extends Component {
updateAllWithProps = (props) => {
const appId = Messages.appId(props);
const messages = MessageStore.getForAppId(appId);
const name = AppStore.getName(appId);
this.setState({appId: appId, messages, name});
this.setState({...MessageStore.get(appId), appId, name: AppStore.getName(appId)});
if (!MessageStore.exists(appId)) {
MessageStore.loadNext(appId);
}
};
updateAll = () => this.updateAllWithProps(this.props);
@ -45,24 +45,57 @@ class Messages extends Component {
return match.params.id !== undefined ? parseInt(match.params.id, 10) : -1;
}
render() {
const {name, messages, appId} = this.state;
const fDelete = appId === -1 ? MessageAction.deleteMessages : MessageAction.deleteMessagesByApp.bind(this, appId);
const noMessages = (
<Grid item xs={12}><Typography variant="caption" gutterBottom align="center">No messages</Typography></Grid>
renderMessage = (index, key) => {
this.checkIfLoadMore();
const message = this.state.messages[index];
return (
<Message key={key}
fDelete={() => MessageAction.deleteMessage(message)}
title={message.title}
date={message.date}
content={message.message}
image={message.image}/>
);
};
checkIfLoadMore() {
const {hasMore, messages, appId} = this.state;
if (hasMore) {
const [, maxRenderedIndex] = (this.list && this.list.getVisibleRange()) || [0, 0];
if (maxRenderedIndex > (messages.length - 30)) {
MessageStore.loadNext(appId);
}
}
}
label = (text) => (
<Grid item xs={12}><Typography variant="caption" gutterBottom align="center">{text}</Typography></Grid>
);
render() {
const {name, messages, hasMore, appId} = this.state;
const hasMessages = messages.length !== 0;
const deleteMessages = () => MessageAction.deleteMessagesByApp(appId);
return (
<DefaultPage title={name} buttonTitle="Delete All" fButton={fDelete} buttonDisabled={messages.length === 0}>
{messages.length === 0 ? noMessages : messages.map((message) => {
return (
<Grid item xs={12} key={message.id}>
<Message fDelete={() => MessageAction.deleteMessage(message.id)} title={message.title}
date={message.date} content={message.message} image={message.image}/>
</Grid>
);
})}
<DefaultPage title={name} buttonTitle="Delete All" fButton={deleteMessages} buttonDisabled={!hasMessages}>
{hasMessages
? (
<div style={{width: '100%'}}>
<ReactList ref={(el) => this.list = el}
itemRenderer={this.renderMessage}
length={messages.length}
threshold={1000}
pageSize={30}
type='variable'
/>
{hasMore
? <Grid item xs={12} style={{textAlign: 'center'}}><CircularProgress size={100}/></Grid>
: this.label('You\'ve reached the end')}
</div>
)
: this.label('No messages')
}
</DefaultPage>
);
}

View File

@ -1,59 +1,78 @@
import {EventEmitter} from 'events';
import dispatcher from './dispatcher';
import AppStore from './AppStore';
import * as MessageAction from '../actions/MessageAction';
class MessageStore extends EventEmitter {
constructor() {
super();
this.messages = [];
this.messagesForApp = [];
this.appToMessages = {};
this.loading = false;
AppStore.on('change', this.updateApps);
}
get() {
return this.messages;
loadNext(id) {
if (this.loading || !this.get(id).hasMore) {
return;
}
this.loading = true;
MessageAction.fetchMessagesApp(id, this.get(id).nextSince).catch(() => this.loading = false);
}
getForAppId(id) {
if (id === -1) {
return this.messages;
get(id) {
if (this.exists(id)) {
return this.appToMessages[id];
} else {
return {messages: [], nextSince: 0, hasMore: true};
}
if (this.messagesForApp[id]) {
return this.messagesForApp[id];
}
return [];
}
exists(id) {
return this.appToMessages[id] !== undefined;
}
handle(data) {
if (data.type === 'UPDATE_MESSAGES') {
this.messages = data.payload;
this.messagesForApp = [];
this.messages.forEach(function(message) {
this.createIfNotExist(message.appid);
this.messagesForApp[message.appid].push(message);
}.bind(this));
const payload = data.payload;
if (this.exists(payload.id)) {
payload.messages = this.get(payload.id).messages.concat(payload.messages);
}
this.appToMessages[payload.id] = payload;
this.updateApps();
this.loading = false;
this.emit('change');
} else if (data.type === 'ONE_MESSAGE') {
const {payload} = data;
this.createIfNotExist(payload.appid);
this.messagesForApp[payload.appid].unshift(payload);
this.messages.unshift(payload);
this.createIfNotExist(-1);
this.appToMessages[payload.appid].messages.unshift(payload);
this.appToMessages[-1].messages.unshift(payload);
this.updateApps();
this.emit('change');
} else if (data.type === 'DELETE_MESSAGE') {
Object.keys(this.appToMessages).forEach((key) => {
const appMessages = this.appToMessages[key];
const index = appMessages.messages.indexOf(data.payload);
if (index !== -1) {
appMessages.messages.splice(index, 1);
}
});
this.emit('change');
}
}
updateApps = () => {
const appToUrl = {};
AppStore.get().forEach((app) => appToUrl[app.id] = app.image);
this.messages.forEach((msg) => msg.image = appToUrl[msg.appid]);
this.messagesForApp.forEach((forApp) => forApp.forEach((msg) => msg.image = appToUrl[msg.appid]));
Object.keys(this.appToMessages).forEach((key) => {
const appMessages = this.appToMessages[key];
appMessages.messages.forEach((message) => message.image = appToUrl[message.appid]);
});
};
createIfNotExist(id) {
if (!(id in this.messagesForApp)) {
this.messagesForApp[id] = [];
if (!(id in this.appToMessages)) {
this.appToMessages[id] = this.get(id);
}
}
}