From 88249a8e8200ab73f0026dcd262455d57607ad8a Mon Sep 17 00:00:00 2001 From: Jannis Mattheis Date: Thu, 15 Mar 2018 20:32:47 +0100 Subject: [PATCH] Add Messages Component --- ui/src/actions/MessageAction.js | 48 ++++++++++++++++++++++ ui/src/component/Message.js | 54 +++++++++++++++++++++++++ ui/src/pages/Messages.js | 71 +++++++++++++++++++++++++++++++++ ui/src/stores/MessageStore.js | 52 ++++++++++++++++++++++++ 4 files changed, 225 insertions(+) create mode 100644 ui/src/actions/MessageAction.js create mode 100644 ui/src/component/Message.js create mode 100644 ui/src/pages/Messages.js create mode 100644 ui/src/stores/MessageStore.js diff --git a/ui/src/actions/MessageAction.js b/ui/src/actions/MessageAction.js new file mode 100644 index 0000000..7785794 --- /dev/null +++ b/ui/src/actions/MessageAction.js @@ -0,0 +1,48 @@ +import dispatcher from '../stores/dispatcher'; +import config from 'react-global-configuration'; +import axios from 'axios'; +import {getToken} from './defaultAxios'; + +/** Fetches all messages from the current user. */ +export function fetchMessages() { + axios.get(config.get('url') + 'message').then((resp) => { + dispatcher.dispatch({type: 'UPDATE_MESSAGES', payload: resp.data}); + }); +} + +/** Deletes all messages from the current user. */ +export function deleteMessages() { + axios.delete(config.get('url') + 'message').then(fetchMessages); +} + +/** + * Deletes all messages from the current user and an application. + * @param {int} id the application id + */ +export function deleteMessagesByApp(id) { + axios.delete(config.get('url') + 'application/' + id + '/message').then(fetchMessages); +} + +/** + * Deletes a message by id. + * @param {int} id the message id + */ +export function deleteMessage(id) { + axios.delete(config.get('url') + 'message/' + id).then(fetchMessages); +} + +/** + * Starts listening to the stream for new messages. + */ +export function listenToWebSocket() { + const ws = new WebSocket('ws://localhost:80/stream?token=' + getToken()); + + ws.onerror = (e) => { + console.log('WebSocket connection errored; trying again in 60 seconds', e); + setTimeout(listenToWebSocket, 60000); + }; + + ws.onmessage = (data) => { + dispatcher.dispatch({type: 'ONE_MESSAGE', payload: JSON.parse(data.data)}); + }; +} diff --git a/ui/src/component/Message.js b/ui/src/component/Message.js new file mode 100644 index 0000000..521359d --- /dev/null +++ b/ui/src/component/Message.js @@ -0,0 +1,54 @@ +import React, {Component} from 'react'; +import {withStyles} from 'material-ui/styles'; +import Typography from 'material-ui/Typography'; +import IconButton from 'material-ui/IconButton'; +import PropTypes from 'prop-types'; +import Container from './Container'; +import TimeAgo from 'react-timeago'; +import Delete from 'material-ui-icons/Delete'; + +const styles = () => ({ + header: { + display: 'flex', + }, + headerTitle: { + flex: 1, + }, + trash: { + marginTop: -15, + marginRight: -15, + }, +}); + +class Message extends Component { + static propTypes = { + classes: PropTypes.object.isRequired, + title: PropTypes.string.isRequired, + date: PropTypes.string.isRequired, + content: PropTypes.string.isRequired, + fDelete: PropTypes.func.isRequired, + }; + + render() { + const {fDelete, classes, title, date, content} = this.props; + + return ( + +
+ + {title} + + + + + +
+ + {content} + +
+ ); + } +} + +export default withStyles(styles)(Message); diff --git a/ui/src/pages/Messages.js b/ui/src/pages/Messages.js new file mode 100644 index 0000000..7930ba1 --- /dev/null +++ b/ui/src/pages/Messages.js @@ -0,0 +1,71 @@ +import React, {Component} from 'react'; +import Grid from 'material-ui/Grid'; +import Typography from 'material-ui/Typography'; +import Message from '../component/Message'; +import MessageStore from '../stores/MessageStore'; +import AppStore from '../stores/AppStore'; +import * as MessageAction from '../actions/MessageAction'; +import DefaultPage from '../component/DefaultPage'; + +class Messages extends Component { + constructor() { + super(); + this.state = {appId: -1, messages: [], name: 'unknown'}; + } + + componentWillReceiveProps(nextProps) { + this.updateAllWithProps(nextProps); + } + + componentWillMount() { + MessageStore.on('change', this.updateAll); + AppStore.on('change', this.updateAll); + this.updateAll(); + } + + componentWillUnmount() { + MessageStore.removeListener('change', this.updateAll); + AppStore.removeListener('change', this.updateAll); + } + + updateAllWithProps = (props) => { + const appId = Messages.appId(props); + const messages = MessageStore.getForAppId(appId); + const name = AppStore.getName(appId); + this.setState({appId: appId, messages, name}); + }; + + updateAll = () => this.updateAllWithProps(this.props); + + static appId(props) { + if (props === undefined) { + return -1; + } + const {match} = props; + return match.params.id !== undefined ? parseInt(match.params.id) : -1; + } + + render() { + const {name, messages, appId} = this.state; + const fDelete = appId === -1 ? MessageAction.deleteMessages : MessageAction.deleteMessagesByApp.bind(this, appId); + + const noMessages = ( + No messages + ); + + return ( + + {messages.length === 0 ? noMessages : messages.map((message) => { + return ( + + MessageAction.deleteMessage(message.id)} title={message.title} + date={message.date} content={message.message}/> + + ); + })} + + ); + } +} + +export default Messages; diff --git a/ui/src/stores/MessageStore.js b/ui/src/stores/MessageStore.js new file mode 100644 index 0000000..59cdbbb --- /dev/null +++ b/ui/src/stores/MessageStore.js @@ -0,0 +1,52 @@ +import {EventEmitter} from 'events'; +import dispatcher from './dispatcher'; + +class MessageStore extends EventEmitter { + constructor() { + super(); + this.messages = []; + this.messagesForApp = []; + } + + get() { + return this.messages; + } + + getForAppId(id) { + if (id === -1) { + return this.messages; + } + if (this.messagesForApp[id]) { + return this.messagesForApp[id]; + } + return []; + } + + 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)); + 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.emit('change'); + } + } + + createIfNotExist(id) { + if (!(id in this.messagesForApp)) { + this.messagesForApp[id] = []; + } + } +} + +const store = new MessageStore(); +dispatcher.register(store.handle.bind(store)); +export default store;