Skip to content

Commit

Permalink
feat(AutoScroll): Implement AutoScroll to Chat Component (#116)
Browse files Browse the repository at this point in the history
* fea(AutoScroll): Use Flexbox to set scrol to bottom when render

* feat(Chat): Add new message per 5 seconds

* feat(AutoScroll): remove autoscroll feature from scroll component

* feat(AutoScroll): Add new scroll component for chat

* fea(AutoScroll): Make Custom Chat Scroll Style same as Vue Custom Scroll Bar

* feat(AutoScroll): show unread messages & init them when click Mark as Read

* feat(AutoScroll): prevent autoscroll when prevent scroll offset is set

* feat(AutoScroll): change labels with i18n text

* feat(AutoScroll): add custom scroll css for firefox (Firefox 64 only support two type of css for custom scroll bar)

* feat(AutoScroll): Update unread message style when scroll element is resized

* feat(AutoScroll): refactor some css values on chat scroll

* feat(AutoScroll): Move action/mutation/state from Media to Ui and Update i18n text

Co-authored-by: Matt Wisniewski <[email protected]>
  • Loading branch information
Eric Lee and Matt Wisniewski authored Sep 6, 2021
1 parent b64409f commit 59bcdd2
Show file tree
Hide file tree
Showing 18 changed files with 283 additions and 87 deletions.
1 change: 1 addition & 0 deletions components/tailored/core/chatbar/Chatbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export default Vue.extend({
this.$store.dispatch('sendMessage', {
value: this.value,
user: this.$mock.user,
isOwner: true,
})
this.value = ''
},
Expand Down
13 changes: 13 additions & 0 deletions components/ui/ChatScroll/ChatScroll.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div :class="classObject" ref="scrollRef" v-on:scroll="onScrolled">
<slot />
<div
v-if="$store.state.ui.unreadMessage>0"
class="new-message-alert"
:style="{ right: newMessageAlertPos.right + 'px',
top: newMessageAlertPos.top + 'px', width: newMessageAlertPos.width + 'px' }"
@click="autoScrollToBottom"
>
{{ $store.state.ui.unreadMessage }} {{ $t('pages.chat.new_message') }}
<span>{{ $t('pages.chat.jump_to_current') }}</span>
</div>
</div>
53 changes: 53 additions & 0 deletions components/ui/ChatScroll/ChatScroll.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
.scroll-area {
margin: auto;
width: 100%;
height: 100%;
white-space: nowrap;
}

.enable-wrap {
white-space: normal !important;
}

.auto-scroll {
display: flex;
flex-direction: column-reverse;
overflow-y: auto;
overflow-x: hidden;

/* Custom Scroll for Firefox */
scrollbar-color: @primary-color transparent;
scrollbar-width: thin;

/* Scroll Width */
&::-webkit-scrollbar {
width: 5px;
&:hover {
width: 11px;
}
}

/* Scroll Track */
&::-webkit-scrollbar-track {
border-radius: @corner-rounding;
}

/* Scroll Handle */
&::-webkit-scrollbar-thumb {
background: @primary-color;
border-radius: @corner-rounding;
}
}

.new-message-alert {
position: fixed;
background: @primary-color;
cursor: pointer;
padding: 2px 20px;
font-size: 14px;
border-radius: 6px;
z-index: 1000;
span {
float: right;
}
}
111 changes: 111 additions & 0 deletions components/ui/ChatScroll/ChatScroll.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<template src="./ChatScroll.html"></template>

<script>
import Vue from 'vue'
export default Vue.extend({
name: 'ChatScroll',
props: {
autoScroll: {
type: Boolean,
default: true,
required: false,
},
preventScrollOffset: {
type: Number,
default: 500,
required: false,
},
enableWrap: {
type: Boolean,
default: false,
required: false,
},
contents: {
/* Content Type could be any value in below array */
type: [Array, Object, String, Number],
default: '',
required: false,
},
},
data() {
return {
loaded: false,
newMessageAlert: false,
newMessageAlertPos: {
top: 0,
right: 30,
width: 0,
},
ro: new ResizeObserver(() => {}),
}
},
computed: {
classObject() {
return {
'enable-wrap': this.enableWrap,
'auto-scroll': this.autoScroll,
dark: this.theme === 'dark',
}
},
},
watch: {
contents: {
deep: true,
handler() {
const lastMsg = this.contents[this.contents.length - 1]
if (lastMsg.from === this.$mock.user.address) {
this.autoScrollToBottom()
} else {
this.newMessageAlert = true
}
},
},
},
mounted() {
this.$nextTick(() => {
this.ro = new ResizeObserver((_) => {
this.calcNewMessageAlertPos()
})
this.ro.observe(this.$el)
this.autoScrollToBottom()
})
},
beforeDestroy() {
this.loaded = false
this.ro.unobserve(this.$el)
},
methods: {
autoScrollToBottom() {
const interval = this.loaded ? 100 : 1000
if (this.$el && this.autoScroll) {
setTimeout(() => {
this.$nextTick(() => {
this.$el.scrollTop = 0
this.loaded = true
this.$store.dispatch('setIsScrollOver', false)
})
}, interval)
}
},
calcNewMessageAlertPos() {
if (this.$el) {
this.newMessageAlertPos.top = this.$el.offsetTop + 15
this.newMessageAlertPos.width = this.$el.offsetWidth - 60
}
},
onScrolled() {
if (this.$el) {
if (Math.abs(this.$el.scrollTop) > this.preventScrollOffset) {
this.$store.dispatch('setIsScrollOver', true)
} else {
this.$store.dispatch('setIsScrollOver', false)
}
}
},
},
})
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less" src="./ChatScroll.less"></style>
2 changes: 1 addition & 1 deletion components/ui/Scroll/Scroll.less
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@

.enable-wrap {
white-space: normal!important;
}
}
38 changes: 1 addition & 37 deletions components/ui/Scroll/Scroll.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,11 @@ export default Vue.extend({
default: false,
required: false,
},
autoScroll: {
type: Boolean,
default: false,
required: false,
},
enableWrap: {
type: Boolean,
default: false,
required: false,
},
contents: {
/* Content Type could be any value in below array */
type: [Array, Object, String, Number],
default: '',
required: false,
},
},
data() {
return {
Expand All @@ -60,7 +49,7 @@ export default Vue.extend({
suppressScrollX: !this.horizontalScroll,
wheelPropagation: false,
},
loaded: false,
newMessageAlert: false,
}
},
computed: {
Expand All @@ -75,31 +64,6 @@ export default Vue.extend({
}
},
},
watch: {
contents: {
deep: true,
handler() {
this.autoScrollToBottom()
},
},
},
beforeDestroy() {
this.loaded = false
},
methods: {
autoScrollToBottom() {
const interval = this.loaded ? 100 : 1000
const scrollRef = this.$refs.scrollRef
if (scrollRef && this.autoScroll) {
setTimeout(() => {
this.$nextTick(() => {
scrollRef.$el.scrollTop = scrollRef.$el.scrollHeight
this.loaded = true
})
}, interval)
}
},
},
})
</script>

Expand Down
4 changes: 2 additions & 2 deletions layouts/chat/Chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
<TailoredCoreStatusbar id="statusbar" :user="$mock.users[0]" />
<TailoredCoreMedia :fullscreen="$store.state.ui.fullscreen" :users="$mock.callUsers" :maxViewableUsers="10"
:fullscreenMaxViewableUsers="20" />
<UiScroll ref="chat" verticalScroll scrollbarVisibility="scroll" :contents="$store.state.media.messages" autoScroll enableWrap :class="$store.state.media.activeCall ? 'media-open' : ''">
<UiChatScroll ref="chat" :contents="$store.state.ui.messages" preventScrollOffset="500" enableWrap :class="$store.state.media.activeCall ? 'media-open' : ''">
<Nuxt />
</UiScroll>
</UiChatScroll>
<Nuxt ref="chat" id="chat" />
<TailoredMessagingEnhancers />
<TailoredCommandsPreview :message="$store.state.ui.chatbarContent" />
Expand Down
4 changes: 4 additions & 0 deletions locales/en-US.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ export default {
},
},
},
chat: {
new_message: 'new messages',
jump_to_current: 'Jump To Current',
},
},
servers: {
create: {
Expand Down
7 changes: 5 additions & 2 deletions pages/chat/direct/Direct.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<div id="direct">
<UiTailoredEncrypted />
<TailoredMessagingConversation :messages="$store.state.media.messages" :loading="loading" />
</div>
<TailoredMessagingConversation
:messages="$store.state.ui.messages"
:loading="loading"
/>
</div>
21 changes: 21 additions & 0 deletions pages/chat/direct/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,36 @@ export default Vue.extend({
data() {
return {
loading: true,
updateInterval: null,
}
},
mounted() {
setTimeout(() => {
this.$data.loading = false
this.$store.dispatch('setMessages', this.$mock.messages)
/* Add new message per 5 seconds temporarily */
this.$data.updateInterval = setInterval(
this.sendMessageAutomatically,
5000
)
}, 3000)
this.$store.dispatch('fetchFriends')
},
beforeDestroy() {
if (this.$data.updateInterval) {
clearInterval(this.$data.updateInterval)
this.updateInterval = null
}
},
methods: {
sendMessageAutomatically() {
this.$store.dispatch('sendMessage', {
value: 'Test Message',
user: this.$mock.friend,
isOwner: false,
})
},
},
})
</script>

Expand Down
2 changes: 1 addition & 1 deletion pages/server/chat/Chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
&nbsp;
{{$t('conversation.encrypted')}}
</div>
<TailoredMessagingConversation :messages="$store.state.media.messages" :loading="loading" />
<TailoredMessagingConversation :messages="$store.state.ui.messages" :loading="loading" />
</div>
2 changes: 2 additions & 0 deletions store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import MediaActions from './media/actions'
import MediaMutations from './media/mutations'
import SettingsMutations from './settings/mutations'
import UIMuatations from './ui/mutations'
import UIActions from './ui/actions'
import SearchMutations from './search/mutations'

export const mutations = {
Expand All @@ -30,6 +31,7 @@ export const actions = {
...FriendActions,
...MediaActions,
...AccountsActions,
...UIActions,
}

export const getters = {
Expand Down
11 changes: 3 additions & 8 deletions store/media/actions.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// eslint-disable-next-line import/named
import { Commit } from 'vuex'

interface FetchCallsArguments {
Expand All @@ -7,16 +8,10 @@ interface FetchCallsArguments {

export default {
handler: () => {},
async acceptCall({ commit }: FetchCallsArguments) {
acceptCall({ commit }: FetchCallsArguments) {
commit('toggleIncomingCall', '')
},
async denyCall({ commit }: FetchCallsArguments) {
denyCall({ commit }: FetchCallsArguments) {
commit('toggleIncomingCall', '')
},
setMessages({ commit }: FetchCallsArguments, messages: any[]) {
commit('setMessages', messages)
},
sendMessage({ commit }: FetchCallsArguments, message: any) {
commit('sendMessage', message)
},
}
Loading

0 comments on commit 59bcdd2

Please sign in to comment.