[#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) {
|
export function initialLoad(resp) {
|
||||||
AppAction.fetchApps();
|
AppAction.fetchApps();
|
||||||
MessageAction.fetchMessages();
|
|
||||||
MessageAction.listenToWebSocket();
|
MessageAction.listenToWebSocket();
|
||||||
ClientAction.fetchClients();
|
ClientAction.fetchClients();
|
||||||
if (resp.data.admin) {
|
if (resp.data.admin) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,9 @@ const styles = () => ({
|
||||||
marginTop: -15,
|
marginTop: -15,
|
||||||
marginRight: -15,
|
marginRight: -15,
|
||||||
},
|
},
|
||||||
|
wrapperPadding: {
|
||||||
|
padding: 12,
|
||||||
|
},
|
||||||
messageContentWrapper: {
|
messageContentWrapper: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
|
|
@ -33,33 +36,34 @@ class Message extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
classes: PropTypes.object.isRequired,
|
classes: PropTypes.object.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
image: PropTypes.string.isRequired,
|
image: PropTypes.string,
|
||||||
date: PropTypes.string.isRequired,
|
date: PropTypes.string.isRequired,
|
||||||
content: PropTypes.string.isRequired,
|
content: PropTypes.string.isRequired,
|
||||||
fDelete: PropTypes.func.isRequired,
|
fDelete: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {fDelete, classes, title, date, content, image} = this.props;
|
const {fDelete, classes, title, date, content, image} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container style={{display: 'flex'}}>
|
<div className={classes.wrapperPadding}>
|
||||||
<div className={classes.imageWrapper}>
|
<Container style={{display: 'flex'}}>
|
||||||
<img src={image} alt="app logo" width="70" height="70" className={classes.image}/>
|
<div className={classes.imageWrapper}>
|
||||||
</div>
|
<img src={image} alt="app logo" width="70" height="70" className={classes.image}/>
|
||||||
<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>
|
</div>
|
||||||
<Typography component="p">{content}</Typography>
|
<div className={classes.messageContentWrapper}>
|
||||||
</div>
|
<div className={classes.header}>
|
||||||
</Container>
|
<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 AppStore from '../stores/AppStore';
|
||||||
import * as MessageAction from '../actions/MessageAction';
|
import * as MessageAction from '../actions/MessageAction';
|
||||||
import DefaultPage from '../component/DefaultPage';
|
import DefaultPage from '../component/DefaultPage';
|
||||||
|
import ReactList from 'react-list';
|
||||||
|
import {CircularProgress} from 'material-ui/Progress';
|
||||||
|
|
||||||
class Messages extends Component {
|
class Messages extends Component {
|
||||||
constructor() {
|
state = {appId: -1, messages: [], name: 'unknown', hasMore: true, list: null};
|
||||||
super();
|
|
||||||
this.state = {appId: -1, messages: [], name: 'unknown'};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
this.updateAllWithProps(nextProps);
|
this.updateAllWithProps(nextProps);
|
||||||
|
|
@ -30,9 +29,10 @@ class Messages extends Component {
|
||||||
|
|
||||||
updateAllWithProps = (props) => {
|
updateAllWithProps = (props) => {
|
||||||
const appId = Messages.appId(props);
|
const appId = Messages.appId(props);
|
||||||
const messages = MessageStore.getForAppId(appId);
|
this.setState({...MessageStore.get(appId), appId, name: AppStore.getName(appId)});
|
||||||
const name = AppStore.getName(appId);
|
if (!MessageStore.exists(appId)) {
|
||||||
this.setState({appId: appId, messages, name});
|
MessageStore.loadNext(appId);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateAll = () => this.updateAllWithProps(this.props);
|
updateAll = () => this.updateAllWithProps(this.props);
|
||||||
|
|
@ -45,24 +45,57 @@ class Messages extends Component {
|
||||||
return match.params.id !== undefined ? parseInt(match.params.id, 10) : -1;
|
return match.params.id !== undefined ? parseInt(match.params.id, 10) : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderMessage = (index, key) => {
|
||||||
const {name, messages, appId} = this.state;
|
this.checkIfLoadMore();
|
||||||
const fDelete = appId === -1 ? MessageAction.deleteMessages : MessageAction.deleteMessagesByApp.bind(this, appId);
|
const message = this.state.messages[index];
|
||||||
|
return (
|
||||||
const noMessages = (
|
<Message key={key}
|
||||||
<Grid item xs={12}><Typography variant="caption" gutterBottom align="center">No messages</Typography></Grid>
|
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 (
|
return (
|
||||||
<DefaultPage title={name} buttonTitle="Delete All" fButton={fDelete} buttonDisabled={messages.length === 0}>
|
<DefaultPage title={name} buttonTitle="Delete All" fButton={deleteMessages} buttonDisabled={!hasMessages}>
|
||||||
{messages.length === 0 ? noMessages : messages.map((message) => {
|
{hasMessages
|
||||||
return (
|
? (
|
||||||
<Grid item xs={12} key={message.id}>
|
<div style={{width: '100%'}}>
|
||||||
<Message fDelete={() => MessageAction.deleteMessage(message.id)} title={message.title}
|
<ReactList ref={(el) => this.list = el}
|
||||||
date={message.date} content={message.message} image={message.image}/>
|
itemRenderer={this.renderMessage}
|
||||||
</Grid>
|
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>
|
</DefaultPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,59 +1,78 @@
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
import dispatcher from './dispatcher';
|
import dispatcher from './dispatcher';
|
||||||
import AppStore from './AppStore';
|
import AppStore from './AppStore';
|
||||||
|
import * as MessageAction from '../actions/MessageAction';
|
||||||
|
|
||||||
class MessageStore extends EventEmitter {
|
class MessageStore extends EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.messages = [];
|
this.appToMessages = {};
|
||||||
this.messagesForApp = [];
|
this.loading = false;
|
||||||
AppStore.on('change', this.updateApps);
|
AppStore.on('change', this.updateApps);
|
||||||
}
|
}
|
||||||
|
|
||||||
get() {
|
loadNext(id) {
|
||||||
return this.messages;
|
if (this.loading || !this.get(id).hasMore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loading = true;
|
||||||
|
MessageAction.fetchMessagesApp(id, this.get(id).nextSince).catch(() => this.loading = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
getForAppId(id) {
|
get(id) {
|
||||||
if (id === -1) {
|
if (this.exists(id)) {
|
||||||
return this.messages;
|
return this.appToMessages[id];
|
||||||
|
} else {
|
||||||
|
return {messages: [], nextSince: 0, hasMore: true};
|
||||||
}
|
}
|
||||||
if (this.messagesForApp[id]) {
|
}
|
||||||
return this.messagesForApp[id];
|
|
||||||
}
|
exists(id) {
|
||||||
return [];
|
return this.appToMessages[id] !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
handle(data) {
|
handle(data) {
|
||||||
if (data.type === 'UPDATE_MESSAGES') {
|
if (data.type === 'UPDATE_MESSAGES') {
|
||||||
this.messages = data.payload;
|
const payload = data.payload;
|
||||||
this.messagesForApp = [];
|
if (this.exists(payload.id)) {
|
||||||
this.messages.forEach(function(message) {
|
payload.messages = this.get(payload.id).messages.concat(payload.messages);
|
||||||
this.createIfNotExist(message.appid);
|
}
|
||||||
this.messagesForApp[message.appid].push(message);
|
this.appToMessages[payload.id] = payload;
|
||||||
}.bind(this));
|
|
||||||
this.updateApps();
|
this.updateApps();
|
||||||
|
this.loading = false;
|
||||||
this.emit('change');
|
this.emit('change');
|
||||||
} else if (data.type === 'ONE_MESSAGE') {
|
} else if (data.type === 'ONE_MESSAGE') {
|
||||||
const {payload} = data;
|
const {payload} = data;
|
||||||
this.createIfNotExist(payload.appid);
|
this.createIfNotExist(payload.appid);
|
||||||
this.messagesForApp[payload.appid].unshift(payload);
|
this.createIfNotExist(-1);
|
||||||
this.messages.unshift(payload);
|
this.appToMessages[payload.appid].messages.unshift(payload);
|
||||||
|
this.appToMessages[-1].messages.unshift(payload);
|
||||||
this.updateApps();
|
this.updateApps();
|
||||||
this.emit('change');
|
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 = () => {
|
updateApps = () => {
|
||||||
const appToUrl = {};
|
const appToUrl = {};
|
||||||
AppStore.get().forEach((app) => appToUrl[app.id] = app.image);
|
AppStore.get().forEach((app) => appToUrl[app.id] = app.image);
|
||||||
this.messages.forEach((msg) => msg.image = appToUrl[msg.appid]);
|
Object.keys(this.appToMessages).forEach((key) => {
|
||||||
this.messagesForApp.forEach((forApp) => forApp.forEach((msg) => msg.image = appToUrl[msg.appid]));
|
const appMessages = this.appToMessages[key];
|
||||||
|
appMessages.messages.forEach((message) => message.image = appToUrl[message.appid]);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
createIfNotExist(id) {
|
createIfNotExist(id) {
|
||||||
if (!(id in this.messagesForApp)) {
|
if (!(id in this.appToMessages)) {
|
||||||
this.messagesForApp[id] = [];
|
this.appToMessages[id] = this.get(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue