зеркало из https://github.com/nextcloud/spreed.git
Merge pull request #2407 from nextcloud/feature/vuejs/participants-list
Participants list in the sidebar
This commit is contained in:
Коммит
041f2499a1
|
@ -22,8 +22,7 @@
|
||||||
<template>
|
<template>
|
||||||
<AppContentListItem
|
<AppContentListItem
|
||||||
:title="item.displayName"
|
:title="item.displayName"
|
||||||
:to="{ name: 'conversation', params: { token: item.token }}"
|
:to="{ name: 'conversation', params: { token: item.token }}">
|
||||||
@click.prevent.exact="joinConversation">
|
|
||||||
<ConversationIcon
|
<ConversationIcon
|
||||||
slot="icon"
|
slot="icon"
|
||||||
:item="item"
|
:item="item"
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
import Conversation from './Conversation'
|
import Conversation from './Conversation'
|
||||||
import Hint from '../Hint/Hint'
|
import Hint from '../Hint/Hint'
|
||||||
import { fetchConversations } from '../../../services/conversationsService'
|
import { fetchConversations } from '../../../services/conversationsService'
|
||||||
|
import { joinConversation, leaveConversation } from '../../../services/participantsService'
|
||||||
import { EventBus } from '../../../services/EventBus'
|
import { EventBus } from '../../../services/EventBus'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -68,6 +69,15 @@ export default {
|
||||||
window.setInterval(() => {
|
window.setInterval(() => {
|
||||||
this.fetchConversations()
|
this.fetchConversations()
|
||||||
}, 30000)
|
}, 30000)
|
||||||
|
|
||||||
|
EventBus.$on('routeChange', ({ from, to }) => {
|
||||||
|
if (from.name === 'conversation') {
|
||||||
|
leaveConversation(from.params.token)
|
||||||
|
}
|
||||||
|
if (to.name === 'conversation') {
|
||||||
|
joinConversation(to.params.token)
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
sortConversations(conversation1, conversation2) {
|
sortConversations(conversation1, conversation2) {
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AppNavigation class="vue navigation">
|
<AppNavigation class="vue navigation">
|
||||||
<AppNavigationSearch
|
<SearchBox
|
||||||
v-model="searchText"
|
v-model="searchText"
|
||||||
@input="debounceFetchSearchResults" />
|
@input="debounceFetchSearchResults" />
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -57,7 +57,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
|
import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
|
||||||
import AppNavigationSearch from './AppNavigationSearch/AppNavigationSearch'
|
|
||||||
import Caption from './Caption/Caption'
|
import Caption from './Caption/Caption'
|
||||||
import ContactsList from './ContactsList/ContactsList'
|
import ContactsList from './ContactsList/ContactsList'
|
||||||
import ConversationsList from './ConversationsList/ConversationsList'
|
import ConversationsList from './ConversationsList/ConversationsList'
|
||||||
|
@ -65,6 +64,7 @@ import GroupsList from './GroupsList/GroupsList'
|
||||||
import Hint from './Hint/Hint'
|
import Hint from './Hint/Hint'
|
||||||
import NewPrivateConversation from './NewConversation/NewPrivateConversation'
|
import NewPrivateConversation from './NewConversation/NewPrivateConversation'
|
||||||
import NewPublicConversation from './NewConversation/NewPublicConversation'
|
import NewPublicConversation from './NewConversation/NewPublicConversation'
|
||||||
|
import SearchBox from '../SearchBox/SearchBox'
|
||||||
import debounce from 'debounce'
|
import debounce from 'debounce'
|
||||||
import { EventBus } from '../../services/EventBus'
|
import { EventBus } from '../../services/EventBus'
|
||||||
import { searchPossibleConversations } from '../../services/conversationsService'
|
import { searchPossibleConversations } from '../../services/conversationsService'
|
||||||
|
@ -77,7 +77,6 @@ export default {
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
AppNavigation,
|
AppNavigation,
|
||||||
AppNavigationSearch,
|
|
||||||
Caption,
|
Caption,
|
||||||
ContactsList,
|
ContactsList,
|
||||||
ConversationsList,
|
ConversationsList,
|
||||||
|
@ -85,6 +84,7 @@ export default {
|
||||||
Hint,
|
Hint,
|
||||||
NewPrivateConversation,
|
NewPrivateConversation,
|
||||||
NewPublicConversation,
|
NewPublicConversation,
|
||||||
|
SearchBox,
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -27,8 +27,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import AppNavigationNew from 'nextcloud-vue/dist/Components/AppNavigationNew'
|
import AppNavigationNew from '@nextcloud/vue/dist/Components/AppNavigationNew'
|
||||||
import Multiselect from 'nextcloud-vue/dist/Components/Multiselect'
|
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NewConversationForm',
|
name: 'NewConversationForm',
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AppNavigationSearch',
|
name: 'SearchBox',
|
||||||
props: {
|
props: {
|
||||||
/**
|
/**
|
||||||
* Refers to the focused state of the input search box when loading the page.
|
* Refers to the focused state of the input search box when loading the page.
|
||||||
|
@ -87,7 +87,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../../assets/variables.scss';
|
@import '../../assets/variables.scss';
|
||||||
|
|
||||||
.app-navigation-search {
|
.app-navigation-search {
|
||||||
height: $top-bar-height !important;
|
height: $top-bar-height !important;
|
|
@ -0,0 +1,216 @@
|
||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
|
||||||
|
-
|
||||||
|
- @author Joas Schilling <coding@schilljs.com>
|
||||||
|
-
|
||||||
|
- @license GNU AGPL version 3 or any later version
|
||||||
|
-
|
||||||
|
- 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>
|
||||||
|
<li class="participant-row"
|
||||||
|
:class="{ offline: isOffline, currentUser: isSelf, guestUser: isGuest }">
|
||||||
|
<div class="participant-row__avatar-wrapper">
|
||||||
|
<Avatar
|
||||||
|
:user="userId"
|
||||||
|
:display-name="displayName" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="participant-row__user-name">{{ displayName }}</span>
|
||||||
|
<span v-if="isModerator" class="participant-row__moderator-indicator">({{ t('spreed', 'moderator') }})</span>
|
||||||
|
|
||||||
|
<template v-if="canModerate">
|
||||||
|
<Actions>
|
||||||
|
<ActionButton v-if="canBeDemoted"
|
||||||
|
icon="icon-rename"
|
||||||
|
@click.prevent.exact="demoteFromModerator">
|
||||||
|
{{ t('spreed', 'Demote from moderator') }}
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton v-if="canBePromoted"
|
||||||
|
icon="icon-rename"
|
||||||
|
@click.prevent.exact="promoteToModerator">
|
||||||
|
{{ t('spreed', 'Promote to moderator') }}
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
icon="icon-delete"
|
||||||
|
@click.prevent.exact="removeParticipant">
|
||||||
|
{{ t('spreed', 'Remove participant') }}
|
||||||
|
</ActionButton>
|
||||||
|
</Actions>
|
||||||
|
</template>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
||||||
|
import Actions from '@nextcloud/vue/dist/Components/Actions'
|
||||||
|
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
|
||||||
|
import { PARTICIPANT } from '../../../constants'
|
||||||
|
import { getCurrentUser } from '@nextcloud/auth'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Participant',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
Actions,
|
||||||
|
ActionButton,
|
||||||
|
Avatar,
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
userId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
displayName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
participantType: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
lastPing: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
sessionId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
token() {
|
||||||
|
return this.$route.params.token
|
||||||
|
},
|
||||||
|
currentParticipant() {
|
||||||
|
return this.$store.getters.conversations[this.token]
|
||||||
|
},
|
||||||
|
|
||||||
|
isSelf() {
|
||||||
|
// User
|
||||||
|
if (this.userId) {
|
||||||
|
return getCurrentUser().uid === this.userId
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guest
|
||||||
|
return this.sessionId !== '0' && this.sessionId === this.currentParticipant.sessionId
|
||||||
|
},
|
||||||
|
selfIsModerator() {
|
||||||
|
return this.participantTypeIsModerator(this.currentParticipant.participantType)
|
||||||
|
},
|
||||||
|
|
||||||
|
isOffline() {
|
||||||
|
return this.sessionId === '0'
|
||||||
|
},
|
||||||
|
isGuest() {
|
||||||
|
return [PARTICIPANT.TYPE.GUEST, PARTICIPANT.TYPE.GUEST_MODERATOR].indexOf(this.participantType) !== -1
|
||||||
|
},
|
||||||
|
isModerator() {
|
||||||
|
return this.participantTypeIsModerator(this.participantType)
|
||||||
|
},
|
||||||
|
canModerate() {
|
||||||
|
return this.participantType !== PARTICIPANT.TYPE.OWNER && !this.isSelf && this.selfIsModerator
|
||||||
|
},
|
||||||
|
canBeDemoted() {
|
||||||
|
return this.canModerate
|
||||||
|
&& [PARTICIPANT.TYPE.MODERATOR, PARTICIPANT.TYPE.GUEST_MODERATOR].indexOf(this.participantType) !== -1
|
||||||
|
},
|
||||||
|
canBePromoted() {
|
||||||
|
return this.canModerate && !this.isModerator
|
||||||
|
},
|
||||||
|
|
||||||
|
participantIdentifier() {
|
||||||
|
let data = {}
|
||||||
|
|
||||||
|
if (this.isGuest) {
|
||||||
|
data = {
|
||||||
|
sessionId: this.sessionId,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data = {
|
||||||
|
participant: this.userId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
participantTypeIsModerator(participantType) {
|
||||||
|
return [PARTICIPANT.TYPE.OWNER, PARTICIPANT.TYPE.MODERATOR, PARTICIPANT.TYPE.GUEST_MODERATOR].indexOf(participantType) !== -1
|
||||||
|
},
|
||||||
|
|
||||||
|
async promoteToModerator() {
|
||||||
|
await this.$store.dispatch('promoteToModerator', {
|
||||||
|
token: this.token,
|
||||||
|
participantIdentifier: this.participantIdentifier,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async demoteFromModerator() {
|
||||||
|
await this.$store.dispatch('demoteFromModerator', {
|
||||||
|
token: this.token,
|
||||||
|
participantIdentifier: this.participantIdentifier,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async removeParticipant() {
|
||||||
|
await this.$store.dispatch('removeParticipant', {
|
||||||
|
token: this.token,
|
||||||
|
participantIdentifier: this.participantIdentifier,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.participant-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
&__avatar-wrapper {
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
&__user-name {
|
||||||
|
margin-left: 6px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
&__moderator-indicator {
|
||||||
|
color: var(--color-text-maxcontrast);
|
||||||
|
font-weight: 300;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
&__icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline {
|
||||||
|
& > .participant-row__avatar-wrapper {
|
||||||
|
opacity: .4;
|
||||||
|
}
|
||||||
|
& > span {
|
||||||
|
color: var(--color-text-maxcontrast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,144 @@
|
||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
|
||||||
|
-
|
||||||
|
- @author Joas Schilling <coding@schilljs.com>
|
||||||
|
-
|
||||||
|
- @license GNU AGPL version 3 or any later version
|
||||||
|
-
|
||||||
|
- 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>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
<Participant
|
||||||
|
v-for="participant in participantsList"
|
||||||
|
:key="participant.userId"
|
||||||
|
:user-id="participant.userId"
|
||||||
|
:display-name="participant.displayName"
|
||||||
|
:participant-type="participant.participantType"
|
||||||
|
:last-ping="participant.lastPing"
|
||||||
|
:session-id="participant.sessionId" />
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import Participant from './Participant'
|
||||||
|
import { fetchParticipants } from '../../../services/participantsService'
|
||||||
|
import { EventBus } from '../../../services/EventBus'
|
||||||
|
import { PARTICIPANT } from '../../../constants'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ParticipantsTab',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
Participant,
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
token() {
|
||||||
|
return this.$route.params.token
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the participants array.
|
||||||
|
*
|
||||||
|
* @returns {array}
|
||||||
|
*/
|
||||||
|
participantsList() {
|
||||||
|
const participants = this.$store.getters.participantsList(this.token)
|
||||||
|
|
||||||
|
return participants.slice().sort(this.sortParticipants)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the messages when the MessageList created. The router mounts this
|
||||||
|
* component only if the token is passed in so there's no need to check the
|
||||||
|
* token prop.
|
||||||
|
*/
|
||||||
|
created() {
|
||||||
|
this.onRouteChange()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener for routeChange event emitted by the App.vue component.
|
||||||
|
* Call the onRouteChange method function whenever the route changes.
|
||||||
|
*/
|
||||||
|
EventBus.$on('routeChange', () => {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.onRouteChange()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onRouteChange() {
|
||||||
|
this.getParticipants()
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort two participants by:
|
||||||
|
* - type (moderators before normal participants)
|
||||||
|
* - online status
|
||||||
|
* - display name
|
||||||
|
*
|
||||||
|
* @param {object} participant1 First participant
|
||||||
|
* @param {int} participant1.participantType First participant type
|
||||||
|
* @param {string} participant1.sessionId First participant session
|
||||||
|
* @param {string} participant1.displayName First participant display name
|
||||||
|
* @param {object} participant2 Second participant
|
||||||
|
* @param {int} participant2.participantType Second participant type
|
||||||
|
* @param {string} participant2.sessionId Second participant session
|
||||||
|
* @param {string} participant2.displayName Second participant display name
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
sortParticipants(participant1, participant2) {
|
||||||
|
const moderatorTypes = [PARTICIPANT.TYPE.OWNER, PARTICIPANT.TYPE.MODERATOR, PARTICIPANT.TYPE.GUEST_MODERATOR]
|
||||||
|
const moderator1 = moderatorTypes.indexOf(participant1.participantType) !== -1
|
||||||
|
const moderator2 = moderatorTypes.indexOf(participant2.participantType) !== -1
|
||||||
|
|
||||||
|
if (moderator1 !== moderator2) {
|
||||||
|
return moderator1 ? -1 : 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (participant1.sessionId === '0') {
|
||||||
|
if (participant2.sessionId !== '0') {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
} else if (participant2.sessionId === '0') {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return participant2.displayName - participant1.displayName
|
||||||
|
},
|
||||||
|
|
||||||
|
async getParticipants() {
|
||||||
|
const participants = await fetchParticipants(this.token)
|
||||||
|
this.$store.dispatch('purgeParticipantsStore', this.token)
|
||||||
|
participants.data.ocs.data.forEach(participant => {
|
||||||
|
this.$store.dispatch('addParticipant', {
|
||||||
|
token: this.token,
|
||||||
|
participant: participant,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -26,7 +26,7 @@
|
||||||
:starred.sync="conversation.isFavorite"
|
:starred.sync="conversation.isFavorite"
|
||||||
@close="handleClose">
|
@close="handleClose">
|
||||||
<AppSidebarTab :name="t('spreed', 'Participants')" icon="icon-contacts-dark">
|
<AppSidebarTab :name="t('spreed', 'Participants')" icon="icon-contacts-dark">
|
||||||
Participants
|
<ParticipantsTab />
|
||||||
</AppSidebarTab>
|
</AppSidebarTab>
|
||||||
<AppSidebarTab :name="t('spreed', 'Projects')" icon="icon-projects">
|
<AppSidebarTab :name="t('spreed', 'Projects')" icon="icon-projects">
|
||||||
<CollectionList v-if="conversation.token"
|
<CollectionList v-if="conversation.token"
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
<script>
|
<script>
|
||||||
import AppSidebar from '@nextcloud/vue/dist/Components/AppSidebar'
|
import AppSidebar from '@nextcloud/vue/dist/Components/AppSidebar'
|
||||||
import AppSidebarTab from '@nextcloud/vue/dist/Components/AppSidebarTab'
|
import AppSidebarTab from '@nextcloud/vue/dist/Components/AppSidebarTab'
|
||||||
// import ParticipantsTab from './ParticipantsTab/ParticipantsTab'
|
import ParticipantsTab from './ParticipantsTab/ParticipantsTab'
|
||||||
import { CollectionList } from 'nextcloud-vue-collections'
|
import { CollectionList } from 'nextcloud-vue-collections'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -49,7 +49,7 @@ export default {
|
||||||
AppSidebar,
|
AppSidebar,
|
||||||
AppSidebarTab,
|
AppSidebarTab,
|
||||||
CollectionList,
|
CollectionList,
|
||||||
// ParticipantsTab,
|
ParticipantsTab,
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
@ -29,9 +29,9 @@ import { generateOcsUrl } from '@nextcloud/router'
|
||||||
*
|
*
|
||||||
* @param {string} token The conversation token;
|
* @param {string} token The conversation token;
|
||||||
*/
|
*/
|
||||||
const joinConversation = async function(token) {
|
const joinConversation = async(token) => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(generateOcsUrl(`room/${token}/participants/active`))
|
const response = await axios.post(generateOcsUrl('apps/spreed/api/v1', 2) + `room/${token}/participants/active`)
|
||||||
return response
|
return response
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.debug(error)
|
console.debug(error)
|
||||||
|
@ -66,8 +66,56 @@ const removeCurrentUserFromConversation = async function(token) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const removeUserFromConversation = async function(token, userId) {
|
||||||
|
try {
|
||||||
|
const response = await axios.delete(generateOcsUrl('apps/spreed/api/v1', 2) + `room/${token}/participants`, {
|
||||||
|
params: {
|
||||||
|
participant: userId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return response
|
||||||
|
} catch (error) {
|
||||||
|
console.debug(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeGuestFromConversation = async function(token, sessionId) {
|
||||||
|
try {
|
||||||
|
const response = await axios.delete(generateOcsUrl('apps/spreed/api/v1', 2) + `room/${token}/participants/guests`, {
|
||||||
|
params: {
|
||||||
|
participant: sessionId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return response
|
||||||
|
} catch (error) {
|
||||||
|
console.debug(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const promoteToModerator = async(token, options) => {
|
||||||
|
const response = await axios.post(generateOcsUrl('apps/spreed/api/v1/room', 2) + token + '/moderators', options)
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
const demoteFromModerator = async(token, options) => {
|
||||||
|
const response = await axios.delete(generateOcsUrl('apps/spreed/api/v1/room', 2) + token + '/moderators', {
|
||||||
|
params: options,
|
||||||
|
})
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchParticipants = async(token) => {
|
||||||
|
const response = await axios.get(generateOcsUrl('apps/spreed/api/v1/room', 2) + token + '/participants')
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
joinConversation,
|
joinConversation,
|
||||||
leaveConversation,
|
leaveConversation,
|
||||||
removeCurrentUserFromConversation,
|
removeCurrentUserFromConversation,
|
||||||
|
removeUserFromConversation,
|
||||||
|
removeGuestFromConversation,
|
||||||
|
promoteToModerator,
|
||||||
|
demoteFromModerator,
|
||||||
|
fetchParticipants,
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import Vue from 'vue'
|
||||||
import Vuex, { Store } from 'vuex'
|
import Vuex, { Store } from 'vuex'
|
||||||
import conversationsStore from './conversationsStore'
|
import conversationsStore from './conversationsStore'
|
||||||
import messagesStore from './messagesStore'
|
import messagesStore from './messagesStore'
|
||||||
|
import participantsStore from './participantsStore'
|
||||||
import quoteReplyStore from './quoteReplyStore'
|
import quoteReplyStore from './quoteReplyStore'
|
||||||
import sidebarStore from './sidebarStore'
|
import sidebarStore from './sidebarStore'
|
||||||
|
|
||||||
|
@ -35,6 +36,7 @@ export default new Store({
|
||||||
modules: {
|
modules: {
|
||||||
conversationsStore,
|
conversationsStore,
|
||||||
messagesStore,
|
messagesStore,
|
||||||
|
participantsStore,
|
||||||
quoteReplyStore,
|
quoteReplyStore,
|
||||||
sidebarStore,
|
sidebarStore,
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
|
||||||
|
*
|
||||||
|
* @author Joas Schilling <coding@schilljs.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import Vue from 'vue'
|
||||||
|
import {
|
||||||
|
promoteToModerator,
|
||||||
|
demoteFromModerator,
|
||||||
|
removeUserFromConversation,
|
||||||
|
removeGuestFromConversation,
|
||||||
|
} from '../services/participantsService'
|
||||||
|
import { PARTICIPANT } from '../constants'
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
participants: {
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const getters = {
|
||||||
|
/**
|
||||||
|
* Gets the participants array
|
||||||
|
* @param {object} state the state object.
|
||||||
|
* @returns {array} the participants array (if there are participants in the store)
|
||||||
|
*/
|
||||||
|
participantsList: (state) => (token) => {
|
||||||
|
if (state.participants[token]) {
|
||||||
|
return state.participants[token]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
getParticipant: (state) => (token, index) => {
|
||||||
|
if (state.participants[token] && state.participants[token][index]) {
|
||||||
|
return state.participants[token][index]
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
getParticipantIndex: (state) => (token, participantIdentifier) => {
|
||||||
|
let index
|
||||||
|
|
||||||
|
if (participantIdentifier.hasOwnProperty('participant')) {
|
||||||
|
index = state.participants[token].findIndex(participant => participant.userId === participantIdentifier.participant)
|
||||||
|
} else {
|
||||||
|
index = state.participants[token].findIndex(participant => participant.sessionId === participantIdentifier.sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return index
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
/**
|
||||||
|
* Adds a message to the store.
|
||||||
|
* @param {object} state current store state;
|
||||||
|
* @param {object} token the token of the conversation;
|
||||||
|
* @param {object} participant the participant;
|
||||||
|
*/
|
||||||
|
addParticipant(state, { token, participant }) {
|
||||||
|
if (!state.participants[token]) {
|
||||||
|
Vue.set(state.participants, token, [])
|
||||||
|
}
|
||||||
|
state.participants[token].push(participant)
|
||||||
|
},
|
||||||
|
updateParticipant(state, { token, index, updatedData }) {
|
||||||
|
if (state.participants[token] && state.participants[token][index]) {
|
||||||
|
state.participants[token][index] = Object.assign(state.participants[token][index], updatedData)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteParticipant(state, { token, index }) {
|
||||||
|
if (state.participants[token] && state.participants[token][index]) {
|
||||||
|
Vue.delete(state.participants[token], index)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Resets the store to it's original state
|
||||||
|
* @param {object} state current store state;
|
||||||
|
* @param {string} token the conversation to purge;
|
||||||
|
*/
|
||||||
|
purgeParticipantsStore(state, token) {
|
||||||
|
Vue.delete(state.participants, token)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds participant to the store.
|
||||||
|
*
|
||||||
|
* @param {object} context default store context;
|
||||||
|
* @param {string} token the conversation to purge;
|
||||||
|
* @param {object} participant the participant;
|
||||||
|
*/
|
||||||
|
addParticipant({ commit }, { token, participant }) {
|
||||||
|
commit('addParticipant', { token, participant })
|
||||||
|
},
|
||||||
|
async promoteToModerator({ commit, getters }, { token, participantIdentifier }) {
|
||||||
|
const index = getters.getParticipantIndex(token, participantIdentifier)
|
||||||
|
if (index === -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await promoteToModerator(token, participantIdentifier)
|
||||||
|
|
||||||
|
const participant = getters.getParticipant(token, index)
|
||||||
|
const updatedData = {
|
||||||
|
participantType: participant.participantType === PARTICIPANT.TYPE.GUEST ? PARTICIPANT.TYPE.GUEST_MODERATOR : PARTICIPANT.TYPE.MODERATOR,
|
||||||
|
}
|
||||||
|
commit('updateParticipant', { token, index, updatedData })
|
||||||
|
},
|
||||||
|
async demoteFromModerator({ commit, getters }, { token, participantIdentifier }) {
|
||||||
|
const index = getters.getParticipantIndex(token, participantIdentifier)
|
||||||
|
if (index === -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await demoteFromModerator(token, participantIdentifier)
|
||||||
|
|
||||||
|
const participant = getters.getParticipant(token, index)
|
||||||
|
const updatedData = {
|
||||||
|
participantType: participant.participantType === PARTICIPANT.TYPE.GUEST_MODERATOR ? PARTICIPANT.TYPE.GUEST : PARTICIPANT.TYPE.USER,
|
||||||
|
}
|
||||||
|
commit('updateParticipant', { token, index, updatedData })
|
||||||
|
},
|
||||||
|
async removeParticipant({ commit, getters }, { token, participantIdentifier }) {
|
||||||
|
const index = getters.getParticipantIndex(token, participantIdentifier)
|
||||||
|
if (index === -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const participant = getters.getParticipant(token, index)
|
||||||
|
if (participant.userId) {
|
||||||
|
await removeUserFromConversation(token, participant.userId)
|
||||||
|
} else {
|
||||||
|
await removeGuestFromConversation(token, participant.sessionId)
|
||||||
|
}
|
||||||
|
commit('deleteParticipant', { token, index })
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Resets the store to it's original state.
|
||||||
|
* @param {object} context default store context;
|
||||||
|
* @param {string} token the conversation to purge;
|
||||||
|
*/
|
||||||
|
purgeParticipantsStore({ commit }, token) {
|
||||||
|
commit('purgeParticipantsStore', token)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { state, mutations, getters, actions }
|
Загрузка…
Ссылка в новой задаче