Skip to content

Commit

Permalink
feat(federation): introduce invitations inbox dialog
Browse files Browse the repository at this point in the history
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
  • Loading branch information
Antreesy committed Feb 14, 2024
1 parent 1268dbc commit 3fe62cf
Show file tree
Hide file tree
Showing 2 changed files with 270 additions and 0 deletions.
211 changes: 211 additions & 0 deletions src/components/LeftSidebar/InvitationHandler.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
<!--
- @copyright Copyright (c) 2024 Maksim Sukharev <antreesy.web@gmail.com>
-
- @author Maksim Sukharev <antreesy.web@gmail.com>
-
- @license AGPL-3.0-or-later
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-->

<template>
<NcModal v-if="modal"
:container="container"
@close="closeModal">
<div class="inbox">
<h2 class="inbox__heading">
{{ t('spreed', 'Pending invitations') }}
</h2>
<p class="inbox__disclaimer">
{{ t('spreed', 'Join conversations from remote Nextcloud servers') }}
</p>
<ul class="inbox__list">
<li v-for="item of invitations"
:key="`invitation_${item.id}`"
class="inbox__item">
<ConversationIcon :item="item" />
<div class="inbox__item-desc">
<span class="inbox__item-desc__name">
{{ item.roomName }}
</span>
<span class="inbox__item-desc__subname">
{{ t('spreed', 'From {user} at {remoteServer}', {
user: item.inviterDisplayName,
remoteServer: item.remoteServerUrl,
}) }}
</span>
</div>
<NcButton type="tertiary"
aria-label="t('spreed', 'Decline invitation')"
title="t('spreed', 'Decline invitation')"
:disabled="item.loading"
@click="rejectShare(item.id)">
<template #icon>
<NcLoadingIcon v-if="item.loading" :size="20" />
<CancelIcon v-else :size="20" />
</template>
</NcButton>
<NcButton type="primary"
aria-label="t('spreed', 'Accept invitation')"
:disabled="item.loading"
@click="acceptShare(item.id)">
<template #icon>
<NcLoadingIcon v-if="item.loading" :size="20" />
<CheckIcon v-else :size="20" />
</template>
{{ t('spreed', 'Accept') }}
</NcButton>
</li>
</ul>
</div>
</NcModal>
</template>

<script>
import CancelIcon from 'vue-material-design-icons/Cancel.vue'
import CheckIcon from 'vue-material-design-icons/Check.vue'

import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'

import ConversationIcon from '../ConversationIcon.vue'

import { CONVERSATION } from '../../constants.js'
import { useFederationStore } from '../../stores/federation.js'

export default {
name: 'InvitationHandler',

components: {
ConversationIcon,
NcButton,
NcLoadingIcon,
NcModal,
// Icons
CancelIcon,
CheckIcon,
},

setup() {
return {
federationStore: useFederationStore(),
}
},

data() {
return {
modal: false,
}
},

computed: {
container() {
return this.$store.getters.getMainContainerSelector()
},

invitations() {
return Object.values(this.federationStore.pendingShares)
.map(item => ({
...item,
type: CONVERSATION.TYPE.GROUP,
}))
},
},

expose: ['showModal'],

methods: {
showModal() {
this.modal = true
},

closeModal() {
this.modal = false
},

async acceptShare(id) {
const conversation = await this.federationStore.acceptShare(id)
if (conversation?.token) {
this.$store.dispatch('addConversation', conversation)
this.$router.push({ name: 'conversation', params: { token: conversation.token } })
this.closeModal()
}
},

async rejectShare(id) {
await this.federationStore.rejectShare(id)
if (this.invitations.length === 0) {
this.closeModal()
}
},
},
}
</script>

<style lang="scss" scoped>
.inbox {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
max-height: 700px;
padding: 20px;

&__heading {
margin-bottom: 4px;
}

&__disclaimer {
margin-bottom: 12px;
color: var(--color-text-maxcontrast)
}

&__list {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
height: 100%;
flex: 0 1 auto;
overflow-y: auto;
}

&__item {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 0;
border-bottom: 1px solid var(--color-border);
&:last-child {
border-bottom: none;
}

&-desc {
display: flex;
flex-direction: column;
margin-right: auto;

&__name {
font-weight: bold;
color: var(--color-main-text);
}

&__subname {
color: var(--color-text-maxcontrast);
}
}
}
}
</style>
59 changes: 59 additions & 0 deletions src/components/LeftSidebar/LeftSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,25 @@

<!-- New phone (SIP dial-out) dialog -->
<CallPhoneDialog ref="callPhoneDialog" />

<!-- New Pending Invitations dialog -->
<InvitationHandler v-if="isFederationEnabled" ref="invitationHandler" />
</div>

<NcAppNavigationItem v-if="pendingInvitationsCount"
class="invitation-button"
:name="t('spreed', 'Pending invitations')"
@click="showInvitationHandler">
<template #icon>
<AccountMultiplePlus :size="20" />
</template>
<template #counter>
<NcCounterBubble type="highlighted">
{{ pendingInvitationsCount }}
</NcCounterBubble>
</template>
</NcAppNavigationItem>

<template #list>
<li ref="container" class="left-sidebar__list">
<!-- Conversations List -->
Expand Down Expand Up @@ -268,6 +285,7 @@
import debounce from 'debounce'
import { ref } from 'vue'

import AccountMultiplePlus from 'vue-material-design-icons/AccountMultiplePlus.vue'
import AtIcon from 'vue-material-design-icons/At.vue'
import ChatPlus from 'vue-material-design-icons/ChatPlus.vue'
import FilterIcon from 'vue-material-design-icons/Filter.vue'
Expand All @@ -288,14 +306,17 @@ import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js'
import NcAppNavigationCaption from '@nextcloud/vue/dist/Components/NcAppNavigationCaption.js'
import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcCounterBubble from '@nextcloud/vue/dist/Components/NcCounterBubble.js'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
import NcListItem from '@nextcloud/vue/dist/Components/NcListItem.js'
import { useIsMobile } from '@nextcloud/vue/dist/Composables/useIsMobile.js'

import CallPhoneDialog from './CallPhoneDialog/CallPhoneDialog.vue'
import Conversation from './ConversationsList/Conversation.vue'
import ConversationsListVirtual from './ConversationsListVirtual.vue'
import InvitationHandler from './InvitationHandler.vue'
import OpenConversationsList from './OpenConversationsList/OpenConversationsList.vue'
import SearchBox from './SearchBox/SearchBox.vue'
import ConversationIcon from '../ConversationIcon.vue'
Expand All @@ -314,11 +335,13 @@ import {
} from '../../services/conversationsService.js'
import { EventBus } from '../../services/EventBus.js'
import { talkBroadcastChannel } from '../../services/talkBroadcastChannel.js'
import { useFederationStore } from '../../stores/federation.js'
import { useTalkHashStore } from '../../stores/talkHash.js'
import CancelableRequest from '../../utils/cancelableRequest.js'
import { hasUnreadMentions, filterFunction } from '../../utils/conversation.js'
import { requestTabLeadership } from '../../utils/requestTabLeadership.js'

const isFederationEnabled = loadState('spreed', 'federation_enabled')
const canModerateSipDialOut = getCapabilities()?.spreed?.features?.includes('sip-support-dialout')
&& getCapabilities()?.spreed?.config.call['sip-enabled']
&& getCapabilities()?.spreed?.config.call['sip-dialout-enabled']
Expand All @@ -329,9 +352,12 @@ export default {

components: {
CallPhoneDialog,
InvitationHandler,
NcAppNavigation,
NcAppNavigationCaption,
NcAppNavigationItem,
NcButton,
NcCounterBubble,
Hint,
SearchBox,
NewConversationDialog,
Expand All @@ -344,6 +370,7 @@ export default {
TransitionWrapper,
ConversationsListVirtual,
// Icons
AccountMultiplePlus,
AtIcon,
MessageBadge,
MessageOutline,
Expand All @@ -362,6 +389,7 @@ export default {
const searchBox = ref(null)
const list = ref(null)

const federationStore = useFederationStore()
const talkHashStore = useTalkHashStore()
const { initializeNavigation, resetNavigation } = useArrowNavigation(leftSidebar, searchBox, '.list-item')
const isMobile = useIsMobile()
Expand All @@ -372,9 +400,11 @@ export default {
leftSidebar,
searchBox,
list,
federationStore,
talkHashStore,
isMobile,
canModerateSipDialOut,
isFederationEnabled,
}
},

Expand Down Expand Up @@ -487,6 +517,12 @@ export default {
return this.conversationsList.find(conversation => conversation.type === CONVERSATION.TYPE.NOTE_TO_SELF)
},

pendingInvitationsCount() {
return isFederationEnabled
? Object.keys(this.federationStore.pendingShares).length
: 0
},

sourcesWithoutResults() {
return !this.searchResultsUsers.length
|| !this.searchResultsGroups.length
Expand Down Expand Up @@ -542,6 +578,10 @@ export default {
this.refreshTimer = window.setInterval(() => {
this.fetchConversations()
}, 30000)

if (isFederationEnabled) {
this.federationStore.getShares()
}
})

talkBroadcastChannel.addEventListener('message', (event) => {
Expand Down Expand Up @@ -615,6 +655,10 @@ export default {
this.$refs.callPhoneDialog.showModal()
},

showInvitationHandler() {
this.$refs.invitationHandler.showModal()
},

handleFilter(filter) {
this.isFiltered = filter
// Store the active filter
Expand Down Expand Up @@ -963,6 +1007,21 @@ export default {
}
}

.invitation-button {
padding: 0 6px;
overflow-y: scroll; // align total width with list items
margin-bottom: 4px;

:deep(.app-navigation-entry-link) {
padding-left: 10px;
}

:deep(.app-navigation-entry__name) {
padding-left: 8px;
font-weight: bold;
}
}

// Override vue overflow rules for <ul> elements within app-navigation
.left-sidebar__list {
height: 100% !important;
Expand Down

0 comments on commit 3fe62cf

Please sign in to comment.