Skip to content

Commit

Permalink
Change to use UserConversation as SDK suggest
Browse files Browse the repository at this point in the history
refs #16
  • Loading branch information
rickmak committed Apr 21, 2017
1 parent 0b69016 commit 90fabc3
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 100 deletions.
55 changes: 24 additions & 31 deletions src/components/App.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import skygearChat from 'skygear-chat';

import ManagedConversationList from '../utils/ManagedConversationList.jsx';
import ManagedUserConversationList from '../utils/ManagedConversationList.jsx';

import Conversation from './Conversation.jsx';
import ConversationPreview from './ConversationPreview.jsx';
Expand Down Expand Up @@ -30,23 +30,15 @@ export default class App extends React.Component {
this.state = {
unreadCount: 0, // user's total unread message count (int)
currentModal: null, // currently displayed modal dialog name (or null)
currentConversation: null // currently selected Conversion Record (or null)
activeID: null // currently selected UserConversion ID (or null)
};
this.conversationList = new ManagedConversationList();
this.userConversationList = new ManagedUserConversationList();
}

componentDidMount() {
// subscribe conversation change
this.conversationList.subscribe(() => {
const {currentConversation} = this.state;
const {conversationList} = this;
if (currentConversation) {
this.setState({
currentConversation: conversationList.get(currentConversation._id)
});
} else {
this.forceUpdate();
}
this.userConversationList.subscribe(() => {
this.forceUpdate();
});
skygearChat.getUnreadCount().then(result => {
this.setState({unreadCount: result.message});
Expand All @@ -58,10 +50,11 @@ export default class App extends React.Component {
state: {
unreadCount,
currentModal,
currentConversation
activeID
},
conversationList
userConversationList
} = this;
const activeUC = userConversationList.get(activeID);

return (
<div
Expand All @@ -88,36 +81,37 @@ export default class App extends React.Component {
</div>
<div style={Styles.conversationContainer}>
{
conversationList
.map((c) => {
userConversationList
.map((uc) => {
return <ConversationPreview
key={'ConversationPreview-' + c.id + c.updatedAt}
key={'ConversationPreview-' + uc.id + uc.updatedAt}
selected={
c.id === (currentConversation && currentConversation.id)}
conversation={c}
onClick={() => this.setState({currentConversation: c})}/>
uc.id === activeID}
userConversation={uc}
conversation={uc.$transient.conversation}
onClick={() => this.setState({activeID: uc._id})}/>
})
}
</div>
</div>
{currentConversation &&
{activeID &&
<Conversation
key={'Conversation-' + currentConversation.id}
conversation={currentConversation}
key={'Conversation-' + activeID}
conversation={activeUC.$transient.conversation}
showDetails={() => this.setState({currentModal: 'details'})}/>
}
{(modal => {
switch (modal) {
case 'createGroup':
return (
<CreateGroupModal
addConversationDelegate={c => conversationList.add(c)}
addConversationDelegate={c => userConversationList.addConversation(c)}
onClose={() => this.setState({currentModal: null})}/>
);
case 'createChat':
return (
<CreateChatModal
addConversationDelegate={c => conversationList.add(c)}
addConversationDelegate={c => userConversationList.addConversation(c)}
onClose={() => this.setState({currentModal: null})}/>
);
case 'settings':
Expand All @@ -128,11 +122,10 @@ export default class App extends React.Component {
case 'details':
return (
<DetailsModal
key={'DetailsModal-'
+ currentConversation.id + currentConversation.updatedAt}
conversation={currentConversation}
updateConversationDelegate={c => conversationList.update(c)}
removeConversationDelegate={id => conversationList.remove(id)}
key={'DetailsModal-' + activeID.id}
conversation={activeUC.$transient.conversation}
updateConversationDelegate={c => userConversationList.updateConversation(c)}
removeConversationDelegate={c => userConversationList.removeConversation(c)}
onClose={() => this.setState({currentModal: null})}/>
);
default:
Expand Down
34 changes: 14 additions & 20 deletions src/components/ConversationPreview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default class ConversationPreview extends React.Component {
super(props);
const {title} = props.conversation;
this.state = {
title: title || 'loading...', // conversation title (either group name or participant names)
title: title || 'loading...', // conversation title (either group name or participant names)
imageURL: 'img/loading.svg' // conversation image URL
};
}
Expand Down Expand Up @@ -43,23 +43,17 @@ export default class ConversationPreview extends React.Component {
}
render() {
const {
props: {
selected,
onClick,
conversation: {
unread_count,
$transient: {
last_message: {
body: lastMessage
} = {}
}
}
},
state: {
title,
imageURL
}
} = this;
selected,
onClick,
} = this.props;
const {
unread_count
} = this.props.userConversation;
const {
title,
imageURL
} = this.state;
const lastMessage = this.props.conversation.$transient.last_message;

return (
<div
Expand All @@ -78,8 +72,8 @@ export default class ConversationPreview extends React.Component {
{lastMessage &&
<span
style={Styles.lastMessage}>
{ lastMessage.length > 23 ?
lastMessage.substring(0, 20) + '...' : lastMessage }
{ lastMessage.body.length > 23 ?
lastMessage.body.substring(0, 20) + '...' : lastMessage.body }
</span>
}
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/DetailsModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export default class DetailsModal extends React.Component {
).then(() => {
// close modal after leaving
onClose();
removeConversationDelegate(conversation._id);
removeConversationDelegate(conversation);
});
}
discoverAndAddUser(username) {
Expand Down
116 changes: 68 additions & 48 deletions src/utils/ManagedConversationList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ export class ConversationSorting {
}

/**
* A managed list of conversations for the current user.
* Note: You must be logged in to use this.
* A managed list of user conversations for the current user.
* Note: You must be logged in at skygear to use this.
*/
export default class ManagedConversationList {
export default class ManagedUserConversationList {
/**
* @param {object} [options={}]
* @param {boolean} [options.initialFetch=true]
Expand All @@ -73,8 +73,10 @@ export default class ManagedConversationList {
this._compare = sortBy;
// conversation IDs in order (without type prefix)
this._orderedIDs = [];
// map of conversaton ID => conversation object
this._conversations = {};
// map of user conversaton ID => user conversation object
this._userConversations = {};
// map of conversaton ID => user conversation object
this._ucByCId = {};
// map of subscription ID => event handler
this._updateHandlers = {};

Expand All @@ -100,44 +102,45 @@ export default class ManagedConversationList {
console.log('[conversation event]', event);
switch (event.event_type) {
case 'create':
this.add(event.record);
this.addConversation(event.record);
break;
case 'update':
this.update(event.record);
this.updateConversation(event.record);
break;
case 'delete':
this.remove(event.record._id);
this.removeConversation(event.record);
break;
}
}
}
/**
* @private
*/
_conversationsUpdated() {
const {_conversations, _compare, _updateHandlers} = this;
this._orderedIDs = Object.keys(_conversations)
.map(id => _conversations[id])
_userConversationsUpdated() {
const {_userConversations, _compare, _updateHandlers} = this;
this._orderedIDs = Object.keys(_userConversations)
.map(id => _userConversations[id])
.sort(_compare)
.map(conversation => conversation._id);
Object.keys(_updateHandlers)
.map(key => _updateHandlers[key])
.forEach(handler => handler(this));
}
/**
* Fetches list of conversations from the server.
* @return {Promise<ManagedConversationList>}
* Fetches list of user conversations from the server.
* @return {Promise<ManagedUserConversationList>}
* Promise of this object, resolves if the fetch is successful.
*/
fetch() {
return skygearChat
.getConversations()
.getUserConversations()
.then(results => {
console.log('[fetched conversations]', results);
results.forEach(conversation => {
this._conversations[conversation._id] = conversation;
console.log('[fetched user conversations]', results);
results.forEach((uc) => {
this._userConversations[uc._id] = uc;
this._ucByCId[uc.$transient.conversation._id] = uc;
});
this._conversationsUpdated();
this._userConversationsUpdated();
return this;
});
}
Expand All @@ -149,17 +152,17 @@ export default class ManagedConversationList {
return this._orderedIDs.length;
}
/**
* Get a Conversation
* Get a User Conversation
* @param {number|string} indexOrID
* Either the list index (number) or conversation ID (string) without type prefix.
* @return {Conversation}
*/
get(indexOrID) {
const {_conversations, _orderedIDs} = this;
const {_userConversations, _orderedIDs} = this;
if (typeof indexOrID === 'number') {
return _conversations[_orderedIDs[indexOrID]];
return _userConversations[_orderedIDs[indexOrID]];
} else {
return _conversations[indexOrID];
return _userConversations[indexOrID];
}
}
/**
Expand All @@ -169,7 +172,7 @@ export default class ManagedConversationList {
*/
map(mappingFunction) {
return this._orderedIDs
.map(id => this._conversations[id])
.map(id => this._userConversations[id])
.map(mappingFunction);
}
/**
Expand All @@ -179,51 +182,68 @@ export default class ManagedConversationList {
*/
filter(predicate) {
return this._orderedIDs
.map(id => this._conversations[id])
.map(id => this._userConversations[id])
.filter(predicate);
}
/**
* Add a conversation, no-op if the conversation already exists.
* @param {Conversation} conversation
* @return {ManagedConversationList}
* @return {ManagedUserConversationList}
*/
add(conversation) {
const {_conversations} = this;
if (!_conversations.hasOwnProperty(conversation._id)) {
_conversations[conversation._id] = conversation;
this._conversationsUpdated();
addConversation(conversation) {
const {
_userConversations,
_ucByCId
} = this;
if (_ucByCId.hasOwnProperty(conversation._id)) {
return this;
}
skygearChat
.getUserConversation(conversation)
.then((uc) => {
_userConversations[uc._id] = uc;
_ucByCId[conversation._id] = uc;
this._userConversationsUpdated();
});
return this;
}
/**
* Update a conversation, no-op if the conversation updatedAt date is older than existing.
* Conversation will be added if it's not in the list.
* @param {Conversation} conversation
* @return {ManagedConversationList}
* @return {ManagedUserConversationList}
*/
update(conversation) {
const {_conversations} = this;
if (
_conversations.hasOwnProperty(conversation._id) &&
conversation.updatedAt >= _conversations[conversation._id].updatedAt
) {
_conversations[conversation._id] = conversation;
this._conversationsUpdated();
updateConversation(conversation) {
const {_ucByCId} = this;
if (_ucByCId.hasOwnProperty(conversation._id)) {
const uc = _ucByCId[conversation._id];
const c = uc.$transient.conversation;
if (conversation.updatedAt <= c.updatedAt) {
return this
}
uc.$transient.conversation = conversation;
this._userConversationsUpdated();
} else {
this.add(conversation);
this.addConversation(conversation);
}
return this;
}
/**
* Remove a conversation, no-op if the conversation doesn't exist.
* @param {string} conversationID Conversation ID without type prefix.
* @return {ManagedConversationList}
* @param {conversation} conversation Conversation without type prefix.
* @return {ManagedUserConversationList}
*/
remove(conversationID) {
const {_conversations} = this;
if (_conversations.hasOwnProperty(conversationID)) {
delete _conversations[conversationID];
this._conversationsUpdated();
removeConversation(conversation) {
const {
_userConversations,
_ucByCId
} = this;
const conversationID = conversation._id;
if (_ucById.hasOwnProperty(conversationID)) {
const uc = _ucByCId[conversationID]
delete _userConversations[uc._id];
delete _ucById[conversationID];
this._userConversationsUpdated();
}
return this;
}
Expand Down

0 comments on commit 90fabc3

Please sign in to comment.