diff --git a/ui/src/component/FixedReactList.js b/ui/src/component/FixedReactList.js new file mode 100644 index 0000000..303cf23 --- /dev/null +++ b/ui/src/component/FixedReactList.js @@ -0,0 +1,40 @@ +import ReactList from 'react-list'; + +// See also https://github.com/coderiety/react-list/blob/master/react-list.es6 +class FixedReactList extends ReactList { + // deleting a messages or adding a message (per shift) requires invalidating the cache, react-list sucks as it does + // not provide such functionality, therefore we need to hack it inside there :( + ignoreNextCacheUpdate = false; + + cacheSizes() { + if (this.ignoreNextCacheUpdate) { + this.ignoreNextCacheUpdate = false; + return; + } + super.cacheSizes(); + } + + clearCacheFromIndex(startIndex) { + this.ignoreNextCacheUpdate = true; + + if (startIndex === 0) { + this.cache = {}; + } else { + Object.keys(this.cache).filter((index) => index >= startIndex).forEach((index) => { + delete this.cache[index]; + }); + } + }; + + componentDidUpdate() { + const hasCacheForLastRenderedItem = Object.keys(this.cache).length && this.cache[this.getVisibleRange()[1]]; + super.componentDidUpdate(); + if (!hasCacheForLastRenderedItem) { + // when there is no cache for the last rendered item, then its a new item, react-list doesn't know it size + // and cant correctly calculate the height of the list, we force a rerender where react-list knows the size. + this.forceUpdate(); + } + } +} + +export default FixedReactList; diff --git a/ui/src/pages/Messages.js b/ui/src/pages/Messages.js index ee0a2d7..24fa09d 100644 --- a/ui/src/pages/Messages.js +++ b/ui/src/pages/Messages.js @@ -6,7 +6,7 @@ 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 ReactList from '../component/FixedReactList'; import {CircularProgress} from 'material-ui/Progress'; class Messages extends Component { @@ -29,6 +29,12 @@ class Messages extends Component { updateAllWithProps = (props) => { const appId = Messages.appId(props); + + const reset = MessageStore.shouldReset(appId); + if (reset !== false && this.list) { + this.list.clearCacheFromIndex(reset); + } + this.setState({...MessageStore.get(appId), appId, name: AppStore.getName(appId)}); if (!MessageStore.exists(appId)) { MessageStore.loadNext(appId); diff --git a/ui/src/stores/MessageStore.js b/ui/src/stores/MessageStore.js index 9cc9ad1..32bc912 100644 --- a/ui/src/stores/MessageStore.js +++ b/ui/src/stores/MessageStore.js @@ -7,10 +7,21 @@ class MessageStore extends EventEmitter { constructor() { super(); this.appToMessages = {}; + this.reset = false; + this.resetOnAll = false; this.loading = false; AppStore.on('change', this.updateApps); } + shouldReset(appId) { + let reset = appId === -1 ? this.resetOnAll : this.reset; + if (reset !== false) { + this.reset = false; + this.resetOnAll = false; + } + return reset; + } + loadNext(id) { if (this.loading || !this.get(id).hasMore) { return; @@ -32,8 +43,8 @@ class MessageStore extends EventEmitter { } handle(data) { + const {payload} = data; if (data.type === 'UPDATE_MESSAGES') { - const payload = data.payload; if (this.exists(payload.id)) { payload.messages = this.get(payload.id).messages.concat(payload.messages); } @@ -42,34 +53,42 @@ class MessageStore extends EventEmitter { this.loading = false; this.emit('change'); } else if (data.type === 'ONE_MESSAGE') { - const {payload} = data; this.createIfNotExist(payload.appid); this.createIfNotExist(-1); this.appToMessages[payload.appid].messages.unshift(payload); this.appToMessages[-1].messages.unshift(payload); + this.reset = 0; + this.resetOnAll = 0; 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.resetOnAll = this.removeFromList(this.appToMessages[-1], payload); + this.reset = this.removeFromList(this.appToMessages[payload.appid], payload); this.emit('change'); } else if (data.type === 'DELETE_MESSAGES') { - const id = data.payload; + const id = payload; if (id === -1) { this.appToMessages = {}; } else { delete this.appToMessages[-1]; delete this.appToMessages[id]; } + this.reset = 0; this.emit('change'); } } + removeFromList(messages, messageToDelete) { + if (messages) { + const index = messages.messages.findIndex((message) => message.id === messageToDelete.id); + if (index !== -1) { + messages.messages.splice(index, 1); + return index; + } + } + return false; + } + updateApps = () => { const appToUrl = {}; AppStore.get().forEach((app) => appToUrl[app.id] = app.image);