Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ability to have multiple chats #194

Open
wants to merge 34 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c394c26
Added multiple chats functionality.
eduardsmetanin Sep 5, 2020
f2a49eb
Changed chatList prop to not required.
eduardsmetanin Sep 5, 2020
d28f795
Renamed a prop and reordered a few props.
eduardsmetanin Sep 6, 2020
dbb7cb1
Added multiple chats to demo.
eduardsmetanin Sep 6, 2020
c45b282
Fixed bugs due to v-on='' by re-emitting events on every component.
eduardsmetanin Sep 6, 2020
6e5aa75
Fixed text alignment in ChatList and UserList.
eduardsmetanin Sep 7, 2020
8825474
Added ability to specify user ID in chat json instead of 'me'.
eduardsmetanin Sep 15, 2020
10bd969
Set header image size to be a constant independent of actual image size.
eduardsmetanin Oct 2, 2020
6c7bf40
Added 'box-sizing: border-box' to title image. Added chatListImageUrl…
eduardsmetanin Oct 4, 2020
ee8aa9c
Removed margin-right for 'go back' button.
eduardsmetanin Oct 4, 2020
c08382c
Updated chat list layout.
eduardsmetanin Oct 4, 2020
9b93095
More changes to chat list layout.
eduardsmetanin Oct 4, 2020
c7ed93e
Added code-workspace files to gitignore.
eduardsmetanin Oct 4, 2020
c573bdb
Set border-collapse: collapse.
eduardsmetanin Oct 7, 2020
38cd562
Added cursor: pointer to chat list row. Replaced calls like () => '' …
eduardsmetanin Oct 20, 2020
821ec66
Fixed remaining rebase issues.
eduardsmetanin Oct 21, 2020
fbe2cac
Renamed ChatWindow.vue's var 'state' to 'windowState'.
eduardsmetanin Oct 22, 2020
3d3531a
Ran npm run lint and fixed all errors and warnings.
eduardsmetanin Oct 22, 2020
f70de30
Changed access of object props from using subscripts to period.
eduardsmetanin Oct 22, 2020
158450a
Added events for clicks on headers and icons.
eduardsmetanin Nov 5, 2020
a060084
Ran linter.
eduardsmetanin Nov 5, 2020
931640f
Renamed param to user.
eduardsmetanin Nov 5, 2020
fb59708
Handled case when currently open chat is no longer available.
eduardsmetanin Nov 5, 2020
16aa042
Fixed demo app to show developers how to handle chat room that is no …
eduardsmetanin Nov 5, 2020
968f543
Fixed (again) demo app to show developers how to handle chat room tha…
eduardsmetanin Nov 5, 2020
00454fe
Added props to show header and message icon clickable.
eduardsmetanin Nov 19, 2020
c92b3c1
Add support to toolbox on Emoji and File message type.
silasrm Nov 30, 2020
a6da716
Merge pull request #1 from silasrm/master
eduardsmetanin Dec 1, 2020
c7e3acc
Fix wrong default value for confirmationDeletionMessage param. Fix co…
silasrm Dec 1, 2020
ffe8608
Change button style on sc-message--emoji > sc-message--toolbox
silasrm Dec 3, 2020
28869d5
Merge pull request #2 from silasrm/master
eduardsmetanin Dec 7, 2020
9eacbc4
yarn lint.
eduardsmetanin Dec 7, 2020
92bc47e
Fixed aspect ratio for non-square images in chat list.
eduardsmetanin Dec 22, 2020
1e89a12
No longer showing keyboard on mobile devices when user opens a chat.
eduardsmetanin Jan 14, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ yarn-error.log*
# Editor directories and files
.idea
.vscode
*.code-workspace
*.suo
*.ntvs*
*.njsproj
Expand Down
141 changes: 121 additions & 20 deletions demo/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,22 @@
:confirmationDeletionMessage="'Are you sure? (you can customize this message)'"
:titleImageUrl="titleImageUrl"
:disableUserListToggle="false"
:chatListImageUrl="chatListImageUrl"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This icon appears in the header when the chat is in chat list mode. If it's an empty string, then no icon appears, but it still looks good.

:placeholder="placeholder"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

placeholder was already implemented. I just added it to the demo app.

:multipleChatsEnabled="multipleChatsEnabled"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

multipleChatsEnabled is needed mostly for backward compatibility. We could've gotten this information from chatList prop (for example, if chatList returns just one room, work in single chat room mode), but we can't require this new prop (chatList) to be specified for backward compatibility.

:chatList="chatList"
:chatListTitle="chatListTitle"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Text in the header for chat list screen.

:messageListHeaderTitleClickable="false"
:chatListHeaderTitleClickable="true"
:messageIconClickable="true"
@onType="handleOnType"
@edit="editMessage"
@remove="removeMessage"
@changeCurrentChat="handleChangeCurrentChat"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Developer needs to handle this event to put new chat content into messageList prop.

@messageListMountedUpdated="handleMessageListMountedUpdated"
@messageListHeaderTitleClicked="messageListHeaderTitleClicked"
@chatListHeaderTitleClicked="chatListHeaderTitleClicked"
@messageIconClicked="messageIconClicked"
>
<template v-slot:text-message-toolbox="scopedProps">
<button v-if="!scopedProps.me && scopedProps.message.type==='text'" @click.prevent="like(scopedProps.message.id)">
Expand Down Expand Up @@ -103,6 +116,9 @@
:messageStyling="messageStyling"
:onMessage="sendMessage"
:onTyping="handleTyping"
:multipleChatsEnabled="multipleChatsEnabled"
:chatList="chatList"
@multipleChatsEnabledChange="handleMultipleChatsEnabledChange"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This event is mostly needed for the demo app. I don't expect this to be used in real applications. I think developers should just choose they either use single chat or multi chat mode once and keep it a constant.

/>
<Footer
:chosenColor="chosenColor"
Expand All @@ -112,8 +128,7 @@
</template>

<script>
import messageHistory from './messageHistory'
import chatParticipants from './chatProfiles'
import chatHistory from './messageHistory'
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chat content format has changed in the demo app to include chat room info.

import Header from './Header.vue'
import Footer from './Footer.vue'
import TestArea from './TestArea.vue'
Expand All @@ -128,50 +143,53 @@ export default {
},
data() {
return {
participants: chatParticipants,
titleImageUrl:
'https://a.slack-edge.com/66f9/img/avatars-teams/ava_0001-34.png',
messageList: messageHistory,
newMessagesCount: 0,
titleImageUrl: "https://a.slack-edge.com/66f9/img/avatars-teams/ava_0001-34.png",
chatListImageUrl: "https://a.slack-edge.com/66f9/img/avatars-teams/ava_0001-34.png",
chatHistory: chatHistory,
isChatOpen: false,
showTypingIndicator: '',
colors: null,
availableColors,
chosenColor: null,
alwaysScrollToBottom: true,
messageStyling: true,
userIsTyping: false
userIsTyping: false,
placeholder: 'Write something...',
multipleChatsEnabled: true,
currentChatID: Object.keys(chatHistory)[0],
chatListTitle: 'My chats'
}
},
created() {
this.setColor('blue')
},
methods: {
sendMessage(text) {
sendMessage(text, chatID) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now need to know which chat room the message goes to, so I added chatID.

if (text.length > 0) {
this.newMessagesCount = this.isChatOpen
? this.newMessagesCount
: this.newMessagesCount + 1
this.onMessageWasSent({
author: 'support',
type: 'text',
id: Math.random(),
data: { text }
})
}, chatID)
}
},
handleTyping(text) {
handleTyping(text, chatID) {
this.showTypingIndicator =
text.length > 0
text.length > 0 && (chatID === undefined || this.currentChatID == chatID)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I remember right chatID is undefined in single chat mode.

? this.participants[this.participants.length - 1].id
: ''
},
onMessageWasSent(message) {
this.messageList = [...this.messageList, Object.assign({}, message, {id: Math.random()})]
onMessageWasSent(message, chatID) {
const msg = Object.assign({}, message, {id: Math.random(), read: (message.author === this.myId)})
if (chatID !== undefined) {
this.chatMessageList(chatID).push(msg)
} else {
this.messageList.push(msg)
}
},
openChat() {
this.isChatOpen = true
this.newMessagesCount = 0
},
closeChat() {
this.isChatOpen = false
Expand Down Expand Up @@ -204,12 +222,52 @@ export default {
m.type = 'system';
m.data.text = 'This message has been removed';
},
handleChangeCurrentChat(newCurrentChatID) {
this.showTypingIndicator = ''
this.currentChatID = newCurrentChatID
const chat = this.chatHistory[this.currentChatID]
this.titleImageUrl = chat.imageUrl
this.title = chat.name
},
handleMultipleChatsEnabledChange(newMultipleChatsEnabled) {
this.multipleChatsEnabled = newMultipleChatsEnabled
this.currentChatID = Object.keys(this.chatHistory)[0]
},
handleMessageListMountedUpdated() {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Experiment showed the 2 events, Mounted and Updated need to be listened to to mark messages as read.

if (this.isChatOpen) {
this.markCurrentChatRead()
}
},
messageListHeaderTitleClicked() {
console.log("messageListHeaderTitleClicked")
},
chatListHeaderTitleClicked() {
console.log("chatListHeaderTitleClicked")
},
messageIconClicked(user) {
console.log("messageIconClicked user:")
console.log(user)
},
like(id){
const m = this.messageList.findIndex(m => m.id === id);
var msg = this.messageList[m];
msg.liked = !msg.liked;
this.$set(this.messageList, m, msg);
}
},
markCurrentChatRead() {
this.messageList.forEach((msg) => {
if (!msg.read) {
msg.read = true
}
})
},
chatMessageList(chatID) {
const chat = this.chatHistory[chatID];
if (chat === undefined) {
return undefined;
}
return chat.messages;
},
},
computed: {
linkColor() {
Expand All @@ -219,10 +277,53 @@ export default {
},
backgroundColor() {
return this.chosenColor === 'dark' ? this.colors.messageList.bg : '#fff'
},
messageList() {
const chat = this.chatHistory[this.currentChatID];
if (chat === undefined) {
return undefined;
}
return chat.messages;
},
participants() {
const chat = this.chatHistory[this.currentChatID];
if (chat === undefined) {
return [];
}
return chat.participants;
},
chatList() {
var chats = []
Object.entries(this.chatHistory).forEach(([chatKey, chat]) => {
var unreadCount = 0
chat.messages.forEach((msg) => {
if (!msg.read) {
unreadCount++
}
})
chats.push(
{
id: chatKey,
name: chat.name,
imageUrl: chat.imageUrl,
unreadCount: unreadCount
}
)
})
return chats
},
newMessagesCount() {
var totalUnreadCount = 0
this.chatList.forEach((chat) => {
totalUnreadCount += chat.unreadCount
})
return totalUnreadCount
}
},
mounted(){
this.messageList.forEach(x=>x.liked = false);
Object.entries(this.chatHistory).forEach(([_, chat]) => {
chat.messages.forEach(msg => msg.liked = false)
})
}
}
</script>
Expand Down
31 changes: 27 additions & 4 deletions demo/src/TestArea.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@
<form class="demo-test-area" @submit.prevent="_handleSubmit" @keyup="_handleTyping">
<div class="demo-test-area--preamble">
<p>Test the chat window by sending a message:</p>
<p class="text-center messageStyling">
Multiple chat groups?
<input :checked="multipleChatsEnabled" type="checkbox" @change.prevent="_multipleChatsEnabledChanged">
</p>
<p v-if="multipleChatsEnabled">
To:
<select name="chat" id="id" v-model="chatID">
<option v-for="chat in chatList" :key="chat.id" :value="chat.id">{{ chat.name }}</option>
</select>
</p>
<p v-if="userIsTyping">User is typing...</p>
</div>
<textarea ref="textArea" class="demo-test-area--text" placeholder="Write a test message...." :style="textareaStyle" />
Expand Down Expand Up @@ -46,21 +56,34 @@ export default {
messageStyling: {
type: Boolean,
required: true
},
chatList: {
type: Array,
required: true
},
multipleChatsEnabled: {
type: Boolean,
required: true
Comment on lines +64 to +66
Copy link
Collaborator

@a-kriya a-kriya Oct 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a prop? Is there any difference when this is off? I would expect a single instance of the component to work just as it has, and multiple instances to also work out-of-the-box. I can't see why it needs to be configured.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In multi-room mode, when chat window is opened, I show list of chat rooms (as opposite to chat right away). Also, in the header for chat window I show go-back-to-chat-list button. I use multipleChatsEnabled prop to determine if those 2 things need to be done. I don't see any other good way to do it. The main reason for that is the demo app is controlling the chat content and there is only one property (messageList) with chat content it passes launcher. So, the demo app puts correct chat content when chat room changes. I considered to use chatList prop instead of multipleChatsEnabled (if it's empty or undefined than assume single chat mode), but it's possible to have 0 chat rooms in multi-chat mode.

}
},
data() {
return {
userIsTyping: false
userIsTyping: false,
chatID: this.chatList[0].id
}
},
methods: {
_handleSubmit() {
this.onMessage(this.$refs.textArea.value)
this.onMessage(this.$refs.textArea.value, this.chatID)
this.$refs.textArea.value = ''
this.onTyping('')
this.onTyping('', this.chatID)
},
_handleTyping() {
this.onTyping(this.$refs.textArea.value)
this.onTyping(this.$refs.textArea.value, this.chatID)
},
_multipleChatsEnabledChanged(e) {
this.chatID = this.chatList[0].id
this.$emit("multipleChatsEnabledChange", e.srcElement.checked)
}
},
computed: {
Expand Down
12 changes: 0 additions & 12 deletions demo/src/chatProfiles.js

This file was deleted.

36 changes: 34 additions & 2 deletions demo/src/colors.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ export default {
userList: {
bg: '#fff',
text: '#212121'
},
chatList: {
bg: '#fff',
text: '#212121'
},
chatListBadge: {
bg: '#D32F2F',
text: '#fff'
}
},
green: {
Expand Down Expand Up @@ -53,7 +61,15 @@ export default {
userList: {
bg: '#fff',
text: '#212121'
}
},
chatList: {
bg: '#fff',
text: '#212121'
},
chatListBadge: {
bg: '#388E3C',
text: '#fff'
},
},
blue: {
header: {
Expand Down Expand Up @@ -81,7 +97,15 @@ export default {
userList: {
bg: '#fff',
text: '#212121'
}
},
chatList: {
bg: '#fff',
text: '#212121'
},
chatListBadge: {
bg: '#4e8cff',
text: '#ffffff'
},
},
dark: {
header: {
Expand Down Expand Up @@ -109,6 +133,14 @@ export default {
userList: {
bg: '#2c3e50',
text: '#ecf0f1'
},
chatList: {
bg: '#2c3e50',
text: '#ecf0f1'
},
chatListBadge: {
bg: '#95a5a6',
text: '#ecf0f1'
}
}
}
Expand Down
Loading