Skip to content

Instantly share code, notes, and snippets.

@dphase
Created April 26, 2017 16:26
Show Gist options
  • Save dphase/79102ba81b5e79fc6fae168a6416b2e1 to your computer and use it in GitHub Desktop.
Save dphase/79102ba81b5e79fc6fae168a6416b2e1 to your computer and use it in GitHub Desktop.
import _ from 'lodash'
import alt from '../alt'
import MessageActions from '../actions/Messages'
import SocketActions from '../actions/Socket'
class MessageStore {
constructor() {
this.messages = []
this.bindListeners({
handleUpdateMessages: [ MessageActions.UPDATE_MESSAGES, SocketActions.UPDATE_MESSAGES ],
handleFetchMessages: [ MessageActions.FETCH_MESSAGES ]
})
}
handleUpdateMessages(message) {
// only save the message to the store if it hasn't been previously saved
// ----------------------------------------------------------------------------
if (!_.find(this.messages, m => m.id === message.id)) {
this.messages.push(message)
}
}
handleFetchMessages() {
this.messages = []
}
}
export default alt.createStore(MessageStore, 'MessageStore')
import React from 'react'
import ReactDOM from 'react-dom'
import Block from 'react-blocks'
import moment from 'moment'
import request from 'superagent'
import _ from 'lodash'
import config from 'config'
import SocketStore from '../stores/SocketStore'
import SocketActions from '../actions/Socket'
import MessageStore from '../stores/MessageStore'
import MessageActions from '../actions/Messages'
import MessageBubble from './MessageBubble'
import MessageInput from './MessageInput'
import ContactFinder from './ContactLookup'
import Styles from '../styles/Styles'
class MessageView extends React.Component {
constructor(props) {
super(props)
this.state = MessageStore.getState()
this.state.conversation = null
}
// Component lifecycle
// ------------------------------------------------------------------------------
componentDidMount() {
MessageStore.listen(::this.onChange)
SocketActions.fetchMessages()
if (this.props.params.conversation) {
MessageActions.fetchMessages(this.props.params.conversation)
this.findConversationAJAX()
}
}
componentWillUnmount() {
this.ignoreLastFetch = true
MessageStore.unlisten(::this.onChange)
}
// FIXME: this is awful and bad
// ------------------------------------------------------------------------------
componentWillReceiveProps(nextProps) {
let oldID = this.props.params.conversation
let newID = nextProps.params.conversation
if (newID && (newID !== oldID)) {
this.findConversationAJAX()
}
}
componentDidUpdate(prevProps, prevState) {
let oldID = prevProps.params.conversation
let newID = this.props.params.conversation
if (newID && (newID !== oldID)) {
MessageActions.fetchMessages(this.props.params.conversation)
}
}
onChange(state) {
this.setState(state)
}
// Find contact_id for conversation
// ------------------------------------------------------------------------------
findConversationAJAX() {
return request
.get(`${config.apiServer}/commo/conversations`)
.set('x-ss-api-key', config.apiKey)
.set('x-ss-token', config.userToken)
.end((err, res) => {
let contact = (_.find(
JSON.parse(res.text),
m => m.id === this.props.params.conversation
))
this.setState({messages: this.state.messages, conversation: contact})
})
}
// Sorted messages
// ------------------------------------------------------------------------------
sortedMessages() {
return _.sortBy(this.state.messages, m => m.created_at)
}
// UI rendering
// ------------------------------------------------------------------------------
renderMessage(message) {
if (message.conversation_id === this.props.params.conversation) {
return (
<MessageBubble
key = {message.id}
data = {message}
/>
)
}
}
render() {
let { sms, foreman } = Styles
let inputBox = <br />
let contactBox = <ContactFinder {...this.props} />
let messageContainer
if (this.props.params.conversation) {
if (this.state.conversation) {
inputBox = <MessageInput {...this.props} contact={this.state.conversation.contact} />
contactBox = <ContactFinder
{...this.props}
contact={this.state.conversation.contact}
/>
messageContainer = <MessageItemsContainer
conversation = {this.props.params.conversation}
messages = {this.sortedMessages()}
/>
}
} else {
messageContainer = <div style={sms.helpBox}>Choose a conversation from your inbox on the left or use Student Search<br/>to create a new conversation with a student's contact</div>
}
return (
<Block style={sms.messageViewContainer} layout vertical flex>
<Block>
{contactBox}
</Block>
{messageContainer}
<Block end>
{inputBox}
</Block>
</Block>
)
}
}
class MessageItemsContainer extends React.Component {
constructor(props) {
super(props)
}
// Lifecyle events to manage funky scrolling
// ------------------------------------------------------------------------------
componentDidMount() {
// FIXME: possibly add scrollPosition to state
this.scrollPosition = 0
this.domNode().addEventListener('scroll', ::this.handleScroll)
}
componentWillUpdate() {
let node = this.domNode()
this.shouldScrollDown = this.scrollPosition + node.clientHeight === node.scrollHeight
}
componentDidUpdate() {
let node = this.domNode()
node.scrollTop = this.shouldScrollDown ? node.scrollHeight : this.scrollPosition
this.scrollPosition = node.scrollTop
}
// DOM helpers for scrolling
// ------------------------------------------------------------------------------
domNode() {
return ReactDOM.findDOMNode(this)
}
handleScroll() {
this.scrollPosition = this.domNode().scrollTop
}
// Rendering
// ------------------------------------------------------------------------------
renderMessage(message) {
if (message.conversation_id === this.props.conversation) {
return (
<MessageBubble
key = {message.id}
data = {message}
/>
)
}
}
render() {
let { sms, foreman } = Styles
return (
<Block style={sms.bubbleContainer} flex>
{this.props.messages.map((m) => {
return ::this.renderMessage(m)
})}
</Block>
)
}
}
export default MessageView
import alt from '../alt'
import Faye from 'faye/browser/faye-browser'
import config from 'config'
class SocketActions {
constructor() {
let faye_url = `${config.apiServer}/rt`
this.faye = new Faye.Client(faye_url, {
timeout: 120
})
}
updateMessages(messages) {
return messages
}
fetchMessages() {
this.faye.subscribe(`/user/${config.userID}`, (msg) => {
this.updateMessages(msg)
})
}
}
export default alt.createActions(SocketActions)
import alt from '../alt'
import Actions from '../actions/Socket'
class SocketStore {
constructor() {
this.messages = []
this.bindListeners({
handleUpdateMessages: Actions.UPDATE_MESSAGES,
handlleFetchMessages: Actions.FETCH_MESSAGES
})
}
handleUpdateMessages(message) {
this.messages.push(message)
}
handlleFetchMessages() {
this.messages = []
}
}
export default alt.createStore(SocketStore, 'SocketStore')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment