From 59bcdd2759d09b4f91eeb69e4d71adf1c2f0ea77 Mon Sep 17 00:00:00 2001 From: Eric Lee Date: Tue, 7 Sep 2021 02:51:02 +0700 Subject: [PATCH] feat(AutoScroll): Implement AutoScroll to Chat Component (#116) * 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 --- components/tailored/core/chatbar/Chatbar.vue | 1 + components/ui/ChatScroll/ChatScroll.html | 13 +++ components/ui/ChatScroll/ChatScroll.less | 53 +++++++++ components/ui/ChatScroll/ChatScroll.vue | 111 +++++++++++++++++++ components/ui/Scroll/Scroll.less | 2 +- components/ui/Scroll/Scroll.vue | 38 +------ layouts/chat/Chat.html | 4 +- locales/en-US.js | 4 + pages/chat/direct/Direct.html | 7 +- pages/chat/direct/index.vue | 21 ++++ pages/server/chat/Chat.html | 2 +- store/index.ts | 2 + store/media/actions.ts | 11 +- store/media/mutations.ts | 34 ------ store/media/state.ts | 2 - store/ui/actions.ts | 20 ++++ store/ui/mutations.ts | 39 +++++++ store/ui/state.ts | 6 + 18 files changed, 283 insertions(+), 87 deletions(-) create mode 100644 components/ui/ChatScroll/ChatScroll.html create mode 100644 components/ui/ChatScroll/ChatScroll.less create mode 100644 components/ui/ChatScroll/ChatScroll.vue create mode 100644 store/ui/actions.ts diff --git a/components/tailored/core/chatbar/Chatbar.vue b/components/tailored/core/chatbar/Chatbar.vue index e72a792077..0a72124600 100644 --- a/components/tailored/core/chatbar/Chatbar.vue +++ b/components/tailored/core/chatbar/Chatbar.vue @@ -110,6 +110,7 @@ export default Vue.extend({ this.$store.dispatch('sendMessage', { value: this.value, user: this.$mock.user, + isOwner: true, }) this.value = '' }, diff --git a/components/ui/ChatScroll/ChatScroll.html b/components/ui/ChatScroll/ChatScroll.html new file mode 100644 index 0000000000..f1cbdc6257 --- /dev/null +++ b/components/ui/ChatScroll/ChatScroll.html @@ -0,0 +1,13 @@ +
+ +
+ {{ $store.state.ui.unreadMessage }} {{ $t('pages.chat.new_message') }} + {{ $t('pages.chat.jump_to_current') }} +
+
diff --git a/components/ui/ChatScroll/ChatScroll.less b/components/ui/ChatScroll/ChatScroll.less new file mode 100644 index 0000000000..79e6eb5de3 --- /dev/null +++ b/components/ui/ChatScroll/ChatScroll.less @@ -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; + } +} diff --git a/components/ui/ChatScroll/ChatScroll.vue b/components/ui/ChatScroll/ChatScroll.vue new file mode 100644 index 0000000000..c24364f0d1 --- /dev/null +++ b/components/ui/ChatScroll/ChatScroll.vue @@ -0,0 +1,111 @@ + + + + + + diff --git a/components/ui/Scroll/Scroll.less b/components/ui/Scroll/Scroll.less index 7c1481c0dc..38235022af 100644 --- a/components/ui/Scroll/Scroll.less +++ b/components/ui/Scroll/Scroll.less @@ -11,4 +11,4 @@ .enable-wrap { white-space: normal!important; -} +} \ No newline at end of file diff --git a/components/ui/Scroll/Scroll.vue b/components/ui/Scroll/Scroll.vue index 51049a1645..05dc930833 100644 --- a/components/ui/Scroll/Scroll.vue +++ b/components/ui/Scroll/Scroll.vue @@ -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 { @@ -60,7 +49,7 @@ export default Vue.extend({ suppressScrollX: !this.horizontalScroll, wheelPropagation: false, }, - loaded: false, + newMessageAlert: false, } }, computed: { @@ -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) - } - }, - }, }) diff --git a/layouts/chat/Chat.html b/layouts/chat/Chat.html index 94a8d385c1..b3fc80c38a 100644 --- a/layouts/chat/Chat.html +++ b/layouts/chat/Chat.html @@ -23,9 +23,9 @@ - + - + diff --git a/locales/en-US.js b/locales/en-US.js index d6a2b16c6f..07a1a10cb4 100644 --- a/locales/en-US.js +++ b/locales/en-US.js @@ -211,6 +211,10 @@ export default { }, }, }, + chat: { + new_message: 'new messages', + jump_to_current: 'Jump To Current', + }, }, servers: { create: { diff --git a/pages/chat/direct/Direct.html b/pages/chat/direct/Direct.html index 4cbdc2c179..00668f5b48 100644 --- a/pages/chat/direct/Direct.html +++ b/pages/chat/direct/Direct.html @@ -1,4 +1,7 @@
- -
\ No newline at end of file + + diff --git a/pages/chat/direct/index.vue b/pages/chat/direct/index.vue index 34ac560b2b..f2110cedf4 100644 --- a/pages/chat/direct/index.vue +++ b/pages/chat/direct/index.vue @@ -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, + }) + }, + }, }) diff --git a/pages/server/chat/Chat.html b/pages/server/chat/Chat.html index 503120c902..38694937cc 100644 --- a/pages/server/chat/Chat.html +++ b/pages/server/chat/Chat.html @@ -5,5 +5,5 @@   {{$t('conversation.encrypted')}} - + \ No newline at end of file diff --git a/store/index.ts b/store/index.ts index e3c4dc04e9..2e46e3fd59 100644 --- a/store/index.ts +++ b/store/index.ts @@ -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 = { @@ -30,6 +31,7 @@ export const actions = { ...FriendActions, ...MediaActions, ...AccountsActions, + ...UIActions, } export const getters = { diff --git a/store/media/actions.ts b/store/media/actions.ts index 5c9196e2de..5554bc85b6 100644 --- a/store/media/actions.ts +++ b/store/media/actions.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line import/named import { Commit } from 'vuex' interface FetchCallsArguments { @@ -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) - }, } diff --git a/store/media/mutations.ts b/store/media/mutations.ts index d65710feb0..85b1b2577b 100644 --- a/store/media/mutations.ts +++ b/store/media/mutations.ts @@ -4,38 +4,4 @@ export default { toggleIncomingCall(state: NuxtState, id: String) { state.media.incomingCall = id }, - setMessages(state: NuxtState, messages: any[]) { - state.media.messages = messages - }, - sendMessage(state: NuxtState, message: any) { - const messages: any[] = [...state.media.messages] - const lastIndex = messages.length - 1 - const lastMessage = messages[lastIndex] - if (lastMessage) { - const messageContent = { - id: Date.now(), - at: Date.now(), - type: 'text', - payload: message.value, - } - if (lastMessage.from === message.user.address) { - state.media.messages[lastIndex].messages.push({ - ...messageContent, - }) - } else { - state.media.messages.push({ - id: Date.now(), - at: Date.now(), - type: 'group', - from: message.user.address, - to: '0x07ee55aa48bb72dcc6e9d78256648910de513eca', - messages: [ - { - ...messageContent, - }, - ], - }) - } - } - }, } diff --git a/store/media/state.ts b/store/media/state.ts index 1f28229314..226df14832 100644 --- a/store/media/state.ts +++ b/store/media/state.ts @@ -1,12 +1,10 @@ interface MediaState { incomingCall: String - messages: any[] activeCall: String } const InitialMediaState = (): MediaState => ({ incomingCall: 'Phoenix Kalindi', - messages: [], activeCall: '0x0', }) diff --git a/store/ui/actions.ts b/store/ui/actions.ts new file mode 100644 index 0000000000..66a0318f70 --- /dev/null +++ b/store/ui/actions.ts @@ -0,0 +1,20 @@ +// eslint-disable-next-line import/named +import { Commit, Dispatch } from 'vuex' + +interface ActionsArguments { + commit: Commit + state: any + dispatch: Dispatch +} + +export default { + setMessages({ commit }: ActionsArguments, messages: any[]) { + commit('setMessages', messages) + }, + sendMessage({ commit }: ActionsArguments, message: any) { + commit('sendMessage', message) + }, + setIsScrollOver({ commit }: ActionsArguments, status: boolean) { + commit('setIsScrollOver', status) + }, +} diff --git a/store/ui/mutations.ts b/store/ui/mutations.ts index 98f390523f..c265625b5c 100644 --- a/store/ui/mutations.ts +++ b/store/ui/mutations.ts @@ -70,6 +70,45 @@ export default { showSearchResult: enabled, } }, + setMessages(state: NuxtState, messages: any[]) { + state.ui.messages = messages + }, + setIsScrollOver(state: NuxtState, status: boolean) { + state.ui.isScrollOver = status + if (!status) state.ui.unreadMessage = 0 + }, + sendMessage(state: NuxtState, message: any, isOwner: boolean) { + const messages: any[] = [...state.ui.messages] + const lastIndex = messages.length - 1 + const lastMessage = messages[lastIndex] + if (lastMessage) { + const messageContent = { + id: Date.now(), + at: Date.now(), + type: 'text', + payload: message.value, + } + if (lastMessage.from === message.user.address) { + state.ui.messages[lastIndex].messages.push({ + ...messageContent, + }) + } else { + state.ui.messages.push({ + id: Date.now(), + at: Date.now(), + type: 'group', + from: message.user.address, + to: '0x07ee55aa48bb72dcc6e9d78256648910de513eca', + messages: [ + { + ...messageContent, + }, + ], + }) + } + if (!isOwner && state.ui.isScrollOver) state.ui.unreadMessage++ + } + } setTypingUser(state: NuxtState, user: Object | Boolean) { state.ui = { ...state.ui, diff --git a/store/ui/state.ts b/store/ui/state.ts index f25e7b4e05..ded47b3db2 100644 --- a/store/ui/state.ts +++ b/store/ui/state.ts @@ -11,6 +11,9 @@ interface UIState { chatbarContent: String fullscreen: Boolean showEnhancers: Boolean + messages: any[] + unreadMessage: number + isScrollOver: boolean isTyping: Object | Boolean } @@ -30,6 +33,9 @@ const InitalUIState = (): UIState => ({ chatbarContent: '', fullscreen: false, showEnhancers: false, + messages: [], + unreadMessage: 0, + isScrollOver: false, isTyping: false, })