Skip to content

Commit d548126

Browse files
authored
Merge pull request #705 from Adamant-im/fix/full-view-startup-message
fix: full view startup message
2 parents f7516af + 8dd6012 commit d548126

File tree

1 file changed

+149
-113
lines changed

1 file changed

+149
-113
lines changed

src/components/AChat/AChat.vue

Lines changed: 149 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<template>
2-
<div class="a-chat">
3-
<div class="a-chat__content">
2+
<div :class="classes.root">
3+
<div :class="classes.content">
44
<slot name="header" />
55

66
<v-divider />
77

8-
<div class="a-chat__body">
8+
<div :class="classes.body">
99
<div class="text-center py-2">
1010
<v-progress-circular
1111
v-show="loading"
@@ -16,7 +16,7 @@
1616
/>
1717
</div>
1818

19-
<div ref="messages" class="a-chat__body-messages">
19+
<div ref="messagesRef" :class="classes.bodyMessages">
2020
<template v-for="message in messages" :key="message.id">
2121
<slot
2222
name="message"
@@ -28,45 +28,55 @@
2828
</template>
2929
</div>
3030

31-
<div class="a-chat__fab">
31+
<div :class="classes.fab">
3232
<slot name="fab" />
3333
</div>
3434
</div>
3535

3636
<slot name="form" />
3737
</div>
3838

39-
<div v-if="$slots.overlay" class="a-chat__overlay">
39+
<div v-if="$slots.overlay" :class="classes.overlay">
4040
<slot name="overlay" />
4141
</div>
4242
</div>
4343
</template>
4444

45-
<script>
45+
<script lang="ts">
46+
import { ref, onMounted, onBeforeUnmount, computed, defineComponent, PropType } from 'vue'
4647
import throttle from 'lodash/throttle'
4748
import scrollIntoView from 'scroll-into-view-if-needed'
4849
import Styler from 'stylefire'
4950
import { animate } from 'popmotion'
50-
5151
import { SCROLL_TO_REPLIED_MESSAGE_ANIMATION_DURATION } from '@/lib/constants'
5252
import { isStringEqualCI } from '@/lib/textHelpers'
53+
import { isWelcomeChat as checkIsWelcomeChat } from '@/lib/chat/meta/utils/isWelcomeChat'
54+
import { NormalizedChatMessageTransaction } from '@/lib/chat/helpers'
55+
import { User } from '@/components/AChat/types'
56+
57+
const className = 'a-chat'
58+
const classes = {
59+
root: className,
60+
content: `${className}__content`,
61+
body: `${className}__body`,
62+
bodyMessages: `${className}__body-messages`,
63+
fab: `${className}__fab`,
64+
overlay: `${className}__overlay`
65+
}
5366
54-
const emitScroll = throttle(function () {
55-
this.$emit('scroll', this.currentScrollTop, this.isScrolledToBottom())
56-
}, 200)
57-
58-
export default {
67+
export default defineComponent({
5968
props: {
6069
messages: {
61-
type: Array,
70+
type: Array as PropType<NormalizedChatMessageTransaction[]>,
6271
default: () => []
6372
},
6473
partners: {
65-
type: Array,
74+
type: Array as PropType<User[]>,
6675
default: () => []
6776
},
6877
userId: {
69-
type: String
78+
type: String,
79+
required: true
7080
},
7181
loading: {
7282
type: Boolean,
@@ -78,128 +88,129 @@ export default {
7888
}
7989
},
8090
emits: ['scroll', 'scroll:bottom', 'scroll:top'],
81-
data: () => ({
82-
currentScrollHeight: 0,
83-
currentScrollTop: 0,
84-
currentClientHeight: 0
85-
}),
86-
mounted() {
87-
this.attachScrollListener()
88-
89-
this.currentClientHeight = this.$refs.messages.clientHeight
91+
setup(props, { emit }) {
92+
const messagesRef = ref<HTMLDivElement | null>(null)
93+
const currentScrollHeight = ref(0)
94+
const currentScrollTop = ref(0)
95+
const currentClientHeight = ref(0)
96+
9097
const resizeHandler = () => {
91-
const clientHeightDelta = this.currentClientHeight - this.$refs.messages.clientHeight
98+
if (!messagesRef.value) return
99+
100+
const clientHeightDelta = currentClientHeight.value - messagesRef.value.clientHeight
92101
93102
const nonVisibleClientHeight =
94-
this.$refs.messages.scrollHeight -
95-
this.$refs.messages.clientHeight -
96-
Math.ceil(this.$refs.messages.scrollTop)
103+
messagesRef.value.scrollHeight -
104+
messagesRef.value.clientHeight -
105+
Math.ceil(messagesRef.value.scrollTop)
97106
const scrolledToBottom = nonVisibleClientHeight === 0
98107
99-
if (scrolledToBottom) {
100-
// Browser updates Element.scrollTop by itself
101-
} else {
102-
this.$refs.messages.scrollTop += clientHeightDelta
108+
if (!scrolledToBottom) {
109+
messagesRef.value.scrollTop += clientHeightDelta
103110
}
104111
105-
this.currentClientHeight = this.$refs.messages.clientHeight
112+
currentClientHeight.value = messagesRef.value.clientHeight
106113
}
107114
108-
this.resizeObserver = new ResizeObserver(resizeHandler)
109-
this.resizeObserver.observe(this.$refs.messages)
110-
},
111-
beforeUnmount() {
112-
this.destroyScrollListener()
113-
this.resizeObserver?.unobserve(this.$refs.messages)
114-
},
115-
methods: {
116-
attachScrollListener() {
117-
this.$refs.messages.addEventListener('scroll', this.onScroll)
118-
},
115+
const resizeObserver = ref(new ResizeObserver(resizeHandler))
119116
120-
destroyScrollListener() {
121-
this.$refs.messages.removeEventListener('scroll', this.onScroll)
122-
},
117+
const isWelcomeChat = computed(() => {
118+
return props.partners
119+
.map((item) => item.id)
120+
.map(checkIsWelcomeChat)
121+
.reduce((previous, current) => previous || current, false)
122+
})
123123
124-
onScroll() {
125-
const scrollHeight = this.$refs.messages.scrollHeight
126-
const scrollTop = Math.ceil(this.$refs.messages.scrollTop)
127-
const clientHeight = this.$refs.messages.clientHeight
124+
const emitScroll = throttle(
125+
() => emit('scroll', currentScrollTop.value, isScrolledToBottom()),
126+
200
127+
)
128+
129+
const attachScrollListener = () => {
130+
messagesRef.value?.addEventListener('scroll', onScroll)
131+
}
132+
133+
const destroyScrollListener = () => {
134+
messagesRef.value?.removeEventListener('scroll', onScroll)
135+
}
136+
137+
const onScroll = () => {
138+
if (!messagesRef.value) return
139+
140+
const scrollHeight = messagesRef.value.scrollHeight
141+
const scrollTop = Math.ceil(messagesRef.value.scrollTop)
142+
const clientHeight = messagesRef.value.clientHeight
128143
129-
// Scrolled to Bottom
130144
if (scrollHeight - scrollTop === clientHeight) {
131-
this.$emit('scroll:bottom')
145+
emit('scroll:bottom')
132146
} else if (scrollTop === 0) {
133-
// Scrolled to Top
134-
// Save current `scrollHeight` to maintain scroll
135-
// position when unshift new messages
136-
this.currentScrollHeight = scrollHeight
137-
this.$emit('scroll:top')
147+
currentScrollHeight.value = scrollHeight
148+
emit('scroll:top')
138149
}
139150
140-
// Save previous values of `scrollTop` and `scrollHeight`
141-
// Needed for keeping the same scroll position when prepending
142-
// new messages to the chat
143-
this.currentScrollTop = scrollTop
144-
this.currentScrollHeight = scrollHeight
151+
currentScrollTop.value = scrollTop
152+
currentScrollHeight.value = scrollHeight
145153
146-
emitScroll.call(this)
147-
},
154+
emitScroll()
155+
}
148156
149-
// Fix scroll position after unshift new messages.
150-
// Called from parent component.
151-
maintainScrollPosition() {
152-
this.$refs.messages.scrollTop =
153-
this.$refs.messages.scrollHeight - this.currentScrollHeight + this.currentScrollTop
154-
},
157+
const maintainScrollPosition = () => {
158+
if (!messagesRef.value) return
155159
156-
// Scroll to Bottom when new message.
157-
// Called from parent component.
158-
scrollToBottom() {
159-
this.$refs.messages.scrollTop = this.$refs.messages.scrollHeight
160-
},
160+
if (isWelcomeChat.value) {
161+
messagesRef.value.scrollTop = 0
162+
return
163+
}
161164
162-
scrollTo(position) {
163-
this.$refs.messages.scrollTop = position
164-
},
165+
messagesRef.value.scrollTop =
166+
messagesRef.value.scrollHeight - currentScrollHeight.value + currentScrollTop.value
167+
}
168+
169+
const scrollToBottom = () => {
170+
if (messagesRef.value) {
171+
messagesRef.value.scrollTop = messagesRef.value.scrollHeight
172+
}
173+
}
174+
175+
const scrollTo = (position: number) => {
176+
if (messagesRef.value) {
177+
messagesRef.value.scrollTop = position
178+
}
179+
}
180+
181+
const scrollToMessage = (index: number) => {
182+
if (!messagesRef.value) return
165183
166-
/**
167-
* Scroll to message by index, starting with the last.
168-
*/
169-
scrollToMessage(index) {
170-
const elements = this.$refs.messages.children
184+
const elements = messagesRef.value.children
171185
172186
if (!elements) return
173187
174-
const element = elements[elements.length - 1 - index]
188+
const element = elements[elements.length - 1 - index] as HTMLElement
175189
176190
if (element) {
177-
this.$refs.messages.scrollTop = element.offsetTop - 16
191+
messagesRef.value.scrollTop = element.offsetTop - 16
178192
} else {
179-
this.scrollToBottom()
193+
scrollToBottom()
180194
}
181-
},
195+
}
196+
197+
const scrollToMessageEasy = async (index: number): Promise<boolean> => {
198+
if (!messagesRef.value) return false
182199
183-
/**
184-
* Smooth scroll to message by index (starting with the last).
185-
* @returns Promise<boolean> If `true` then scrolling has been applied.
186-
*/
187-
scrollToMessageEasy(index) {
188-
const elements = this.$refs.messages.children
200+
const elements = messagesRef.value.children
189201
190-
if (!elements) return Promise.resolve(false)
202+
if (!elements) return false
191203
192-
const element = elements[elements.length - 1 - index]
204+
const element = elements[elements.length - 1 - index] as HTMLElement
193205
194-
if (!element) return Promise.resolve(false)
206+
if (!element) return false
195207
196208
return new Promise((resolve) => {
197209
scrollIntoView(element, {
198210
behavior: (instructions) => {
199211
const [{ el, top }] = instructions
200212
const styler = Styler(el)
201213
202-
// do nothing if the element is already scrolled at target position
203214
if (el.scrollTop === top) {
204215
resolve(false)
205216
return
@@ -216,25 +227,50 @@ export default {
216227
block: 'center'
217228
})
218229
})
219-
},
230+
}
231+
232+
const isScrolledToBottom = () => {
233+
if (!messagesRef.value) return false
220234
221-
isScrolledToBottom() {
222235
const scrollOffset =
223-
this.$refs.messages.scrollHeight -
224-
Math.ceil(this.$refs.messages.scrollTop) -
225-
this.$refs.messages.clientHeight
236+
messagesRef.value.scrollHeight -
237+
Math.ceil(messagesRef.value.scrollTop) -
238+
messagesRef.value.clientHeight
226239
227240
return scrollOffset <= 60
228-
},
241+
}
229242
230-
/**
231-
* Returns sender address and name.
232-
* @param {string} senderId Sender address
233-
* @returns {{ id: string, name: string }}
234-
*/
235-
getSenderMeta(senderId) {
236-
return this.partners.find((partner) => isStringEqualCI(partner.id, senderId))
243+
const getSenderMeta = (senderId: string) => {
244+
return props.partners.find((partner) => isStringEqualCI(partner.id, senderId))
245+
}
246+
247+
onMounted(() => {
248+
attachScrollListener()
249+
250+
if (messagesRef.value) {
251+
currentClientHeight.value = messagesRef.value.clientHeight
252+
resizeObserver.value.observe(messagesRef.value)
253+
}
254+
})
255+
256+
onBeforeUnmount(() => {
257+
destroyScrollListener()
258+
if (messagesRef.value) {
259+
resizeObserver.value?.unobserve(messagesRef.value)
260+
}
261+
})
262+
263+
return {
264+
classes,
265+
messagesRef,
266+
maintainScrollPosition,
267+
scrollToBottom,
268+
scrollTo,
269+
scrollToMessage,
270+
scrollToMessageEasy,
271+
getSenderMeta,
272+
isScrolledToBottom
237273
}
238274
}
239-
}
275+
})
240276
</script>

0 commit comments

Comments
 (0)