You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

254 lines
6.3 KiB

<!--
- SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<div class="chatView"
@dragover.prevent="handleDragOver"
@dragleave.prevent="handleDragLeave"
@drop.prevent="handleDropFiles">
<GuestWelcomeWindow v-if="isGuestWithoutDisplayName" :token="token" />
<div class="messages-list-dragover-wrapper">
<TransitionWrapper name="slide-up" mode="out-in">
<NcEmptyContent v-show="isDraggingOver"
:name="dropHintText"
class="dragover">
<template #icon>
<IconTrayArrowUp v-if="!isGuest && !isReadOnly" />
<IconAccount v-else-if="isGuest" />
<IconAlertOctagon v-else-if="isReadOnly" />
</template>
</NcEmptyContent>
</TransitionWrapper>
<MessagesList role="region"
:aria-label="t('spreed', 'Conversation messages')"
:token="token"
:is-chat-scrolled-to-bottom.sync="isChatScrolledToBottom"
:is-visible="isVisible" />
</div>
<div class="scroll-to-bottom">
<TransitionWrapper name="fade">
<NcButton v-show="!isChatScrolledToBottom && !isLoadingChat"
variant="secondary"
:aria-label="t('spreed', 'Scroll to bottom')"
:title="t('spreed', 'Scroll to bottom')"
class="scroll-to-bottom__button"
@click="scrollToBottom">
<template #icon>
<IconChevronDoubleDown :size="20" />
</template>
</NcButton>
</TransitionWrapper>
</div>
<!-- Input field -->
<NewMessage role="region"
:token="token"
has-typing-indicator
:aria-label="t('spreed', 'Post message')" />
<!-- File upload dialog -->
<NewMessageUploadEditor />
</div>
</template>
<script>
import { t } from '@nextcloud/l10n'
import { provide } from 'vue'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
import IconAccount from 'vue-material-design-icons/Account.vue'
import IconAlertOctagon from 'vue-material-design-icons/AlertOctagon.vue'
import IconChevronDoubleDown from 'vue-material-design-icons/ChevronDoubleDown.vue'
import IconTrayArrowUp from 'vue-material-design-icons/TrayArrowUp.vue'
import GuestWelcomeWindow from './GuestWelcomeWindow.vue'
import MessagesList from './MessagesList/MessagesList.vue'
import NewMessage from './NewMessage/NewMessage.vue'
import NewMessageUploadEditor from './NewMessage/NewMessageUploadEditor.vue'
import TransitionWrapper from './UIShared/TransitionWrapper.vue'
import { useGetToken } from '../composables/useGetToken.ts'
import { CONVERSATION, PARTICIPANT } from '../constants.ts'
import { getTalkConfig } from '../services/CapabilitiesManager.ts'
import { EventBus } from '../services/EventBus.ts'
import { useActorStore } from '../stores/actor.ts'
import { useChatExtrasStore } from '../stores/chatExtras.js'
export default {
name: 'ChatView',
components: {
NcButton,
NcEmptyContent,
MessagesList,
NewMessage,
NewMessageUploadEditor,
TransitionWrapper,
GuestWelcomeWindow,
// icons
IconAccount,
IconAlertOctagon,
IconChevronDoubleDown,
IconTrayArrowUp,
},
props: {
isVisible: {
type: Boolean,
default: true,
},
isSidebar: {
type: Boolean,
default: false,
},
},
setup(props) {
provide('chatView:isSidebar', props.isSidebar)
return {
token: useGetToken(),
chatExtrasStore: useChatExtrasStore(),
actorStore: useActorStore(),
}
},
data() {
return {
isChatScrolledToBottom: true,
isDraggingOver: false,
}
},
computed: {
isGuest() {
return this.actorStore.isActorGuest
},
isGuestWithoutDisplayName() {
return this.isGuest && !this.actorStore.displayName
},
canUploadFiles() {
return getTalkConfig(this.token, 'attachments', 'allowed') && this.actorStore.userId
&& this.$store.getters.getAttachmentFolderFreeSpace() !== 0
&& (this.conversation.permissions & PARTICIPANT.PERMISSIONS.CHAT)
&& !this.conversation.remoteServer // no attachments support in federated conversations
},
isDragAndDropBlocked() {
return this.chatExtrasStore.getMessageIdToEdit(this.token) !== undefined || !this.canUploadFiles
},
dropHintText() {
if (this.isGuest) {
return t('spreed', 'You need to be logged in to upload files')
} else if (this.isReadOnly) {
return t('spreed', 'This conversation is read-only')
} else {
return t('spreed', 'Drop your files to upload')
}
},
isReadOnly() {
if (this.conversation) {
return this.conversation.readOnly === CONVERSATION.STATE.READ_ONLY
} else {
return undefined
}
},
conversation() {
return this.$store.getters.conversation(this.token)
},
isLoadingChat() {
return !this.$store.getters.isMessagesListPopulated(this.token)
},
},
methods: {
t,
handleDragOver(event) {
if (event.dataTransfer.types.includes('Files') && !this.isDragAndDropBlocked) {
this.isDraggingOver = true
}
},
handleDragLeave(event) {
if (!event.currentTarget.contains(event.relatedTarget)) {
this.isDraggingOver = false
}
},
handleDropFiles(event) {
if (!this.isDraggingOver || this.isDragAndDropBlocked) {
return
}
// Restore non dragover state
this.isDraggingOver = false
// Stop the executin if the user is a guest
if (this.isGuest || this.isReadOnly) {
return
}
// Get the files from the event
const files = Object.values(event.dataTransfer.files)
// Create a unique id for the upload operation
const uploadId = new Date().getTime()
// Uploads and shares the files
this.$store.dispatch('initialiseUpload', { files, token: this.token, uploadId })
},
scrollToBottom() {
EventBus.emit('scroll-chat-to-bottom', { smooth: false, force: true })
},
},
}
</script>
<style lang="scss" scoped>
.chatView {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
flex-grow: 1;
min-height: 0;
}
.messages-list-dragover-wrapper {
position: relative;
flex: 1 0;
display: flex;
min-height: 0;
}
.dragover {
position: absolute;
inset: 5%;
background: var(--color-primary-element-light);
z-index: 11;
display: flex;
box-shadow: 0 0 36px var(--color-box-shadow);
border-radius: var(--border-radius);
opacity: 90%;
pointer-events: none;
}
.scroll-to-bottom {
position: relative;
height: 0;
&__button {
position: absolute !important;
bottom: 8px;
inset-inline-end: 24px;
z-index: 2;
}
}
</style>