[#34] Add react-list & lazy-loading to Messages & Adjust store to make requests
This commit is contained in:
parent
9ed6228013
commit
ca5a832baf
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue