446333e616
Vue CLI 5 Cypress 10
164 lines
3.4 KiB
Vue
164 lines
3.4 KiB
Vue
<script>
|
|
import MessageItem from './MessageItem.vue'
|
|
import MessageForm from './MessageForm.vue'
|
|
|
|
export default {
|
|
name: 'ChannelView',
|
|
|
|
components: {
|
|
MessageItem,
|
|
MessageForm,
|
|
},
|
|
|
|
props: {
|
|
id: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
},
|
|
|
|
watch: {
|
|
id: {
|
|
handler () {
|
|
this.$_init = false
|
|
},
|
|
immediate: true,
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
onMessageChanged (previousResult, { subscriptionData }) {
|
|
const { type, message } = subscriptionData.data.messageChanged
|
|
|
|
// No list change
|
|
if (type === 'updated') return previousResult
|
|
|
|
const messages = previousResult.channel.messages.slice()
|
|
// Add or remove item
|
|
if (type === 'added') {
|
|
messages.push(message)
|
|
} else if (type === 'removed') {
|
|
const index = messages.findIndex(m => m.id === message.id)
|
|
if (index !== -1) messages.splice(index, 1)
|
|
}
|
|
|
|
// New query result
|
|
return {
|
|
channel: {
|
|
...previousResult.channel,
|
|
messages,
|
|
},
|
|
}
|
|
},
|
|
|
|
async scrollToBottom (force = false) {
|
|
const el = this.$refs.body
|
|
|
|
// No body element yet
|
|
if (!el) {
|
|
setTimeout(() => this.scrollToBottom(force), 100)
|
|
return
|
|
}
|
|
// User is scrolling up => no auto scroll
|
|
if (!force && el.scrollTop + el.clientHeight < el.scrollHeight - 100) return
|
|
|
|
// Scroll to bottom
|
|
await this.$nextTick()
|
|
el.scrollTop = el.scrollHeight
|
|
},
|
|
|
|
onResult (result) {
|
|
// The first time we load a channel, we force scroll to bottom
|
|
this.scrollToBottom(!this.$_init)
|
|
this.$_init = true
|
|
},
|
|
},
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="channel-view">
|
|
<ApolloQuery
|
|
:query="require('../graphql/channel.gql')"
|
|
:variables="{
|
|
id
|
|
}"
|
|
@result="onResult"
|
|
>
|
|
<template #default="{ result: { data, loading } }">
|
|
<div
|
|
v-if="!data && loading"
|
|
class="loading"
|
|
>
|
|
Loading...
|
|
</div>
|
|
|
|
<div v-else-if="data?.channel">
|
|
<!-- Websockets -->
|
|
<ApolloSubscribeToMore
|
|
:document="require('../graphql/messageChanged.gql')"
|
|
:variables="{
|
|
channelId: id,
|
|
}"
|
|
:update-query="onMessageChanged"
|
|
/>
|
|
|
|
<div class="wrapper">
|
|
<div class="header">
|
|
<div class="id">
|
|
#{{ data.channel.id }}
|
|
</div>
|
|
<div class="name">
|
|
{{ data.channel.name }}
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
ref="body"
|
|
class="body"
|
|
>
|
|
<MessageItem
|
|
v-for="message in data.channel.messages"
|
|
:key="message.id"
|
|
:message="message"
|
|
/>
|
|
</div>
|
|
|
|
<div class="footer">
|
|
<MessageForm :channel-id="id" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</ApolloQuery>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="stylus" scoped>
|
|
@import '~@/style/imports'
|
|
|
|
.wrapper
|
|
height 100vh
|
|
display grid
|
|
grid-template-columns 1fr
|
|
grid-template-rows auto 1fr auto
|
|
|
|
.header
|
|
padding 12px
|
|
border-bottom $border
|
|
|
|
.id
|
|
font-family monospace
|
|
margin-bottom 4px
|
|
|
|
.name
|
|
color #555
|
|
|
|
.body
|
|
overflow-x hidden
|
|
overflow-y auto
|
|
|
|
.footer
|
|
border-top $border
|
|
</style>
|