diff --git a/ui/src/actions/GlobalAction.js b/ui/src/actions/GlobalAction.js index a0c04f5..3d8da98 100644 --- a/ui/src/actions/GlobalAction.js +++ b/ui/src/actions/GlobalAction.js @@ -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) { diff --git a/ui/src/component/Message.js b/ui/src/component/Message.js index fce95e1..9d1192a 100644 --- a/ui/src/component/Message.js +++ b/ui/src/component/Message.js @@ -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 ( - -
- app logo -
-
-
- - {title} - - - - - +
+ +
+ app logo
- {content} -
- +
+
+ + {title} + + + + + +
+ {content} +
+ +
); } } diff --git a/ui/src/pages/Messages.js b/ui/src/pages/Messages.js index 484c933..b400785 100644 --- a/ui/src/pages/Messages.js +++ b/ui/src/pages/Messages.js @@ -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 = ( - No messages + renderMessage = (index, key) => { + this.checkIfLoadMore(); + const message = this.state.messages[index]; + return ( + 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) => ( + {text} + ); + + render() { + const {name, messages, hasMore, appId} = this.state; + const hasMessages = messages.length !== 0; + const deleteMessages = () => MessageAction.deleteMessagesByApp(appId); return ( - - {messages.length === 0 ? noMessages : messages.map((message) => { - return ( - - MessageAction.deleteMessage(message.id)} title={message.title} - date={message.date} content={message.message} image={message.image}/> - - ); - })} + + {hasMessages + ? ( +
+ this.list = el} + itemRenderer={this.renderMessage} + length={messages.length} + threshold={1000} + pageSize={30} + type='variable' + /> + {hasMore + ? + : this.label('You\'ve reached the end')} +
+ ) + : this.label('No messages') + }
); } diff --git a/ui/src/stores/MessageStore.js b/ui/src/stores/MessageStore.js index e6a75bb..c118187 100644 --- a/ui/src/stores/MessageStore.js +++ b/ui/src/stores/MessageStore.js @@ -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); } } }