nextcloud-contacts/src/components/MembersList/MembersListItem.vue

284 lines
7.1 KiB
Vue

<!--
- @copyright Copyright (c) 2021 John Molakvoæ <skjnldsv@protonmail.com>
-
- @author John Molakvoæ <skjnldsv@protonmail.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>
<ListItemIcon
:id="source.singleId"
:key="source.singleId"
:avatar-size="44"
:is-no-user="!source.isUser"
:subtitle="levelName"
:title="source.displayName"
:user="source.userId"
class="members-list__item">
<Actions @close="onMenuClose">
<template v-if="loading">
<ActionText icon="icon-loading-small">
{{ t('contacts', 'Loading …') }}
</ActionText>
</template>
<!-- Normal menu -->
<template v-else>
<!-- Level picker -->
<template v-if="canChangeLevel">
<ActionText>
{{ t('contacts', 'Manage level') }}
<ShieldCheck slot="icon"
:size="16"
decorative />
</ActionText>
<ActionButton
v-for="level in availableLevelsChange"
:key="level"
icon=""
@click="changeLevel(level)">
{{ levelChangeLabel(level) }}
</ActionButton>
<ActionSeparator />
</template>
<!-- Leave or delete member from circle -->
<ActionButton v-if="isCurrentUser && !circle.isOwner" @click="deleteMember">
{{ t('contacts', 'Leave circle') }}
<ExitToApp slot="icon"
:size="16"
decorative />
</ActionButton>
<ActionButton v-else-if="canDelete" icon="icon-delete" @click="deleteMember">
{{ t('contacts', 'Remove member') }}
</ActionButton>
</template>
</Actions>
</ListItemIcon>
</template>
<script>
import { CIRCLES_MEMBER_LEVELS, MemberLevels } from '../../models/constants.ts'
import Actions from '@nextcloud/vue/dist/Components/Actions'
import ListItemIcon from '@nextcloud/vue/dist/Components/ListItemIcon'
import ActionSeparator from '@nextcloud/vue/dist/Components/ActionSeparator'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import ActionText from '@nextcloud/vue/dist/Components/ActionText'
import ExitToApp from 'vue-material-design-icons/ExitToApp'
import ShieldCheck from 'vue-material-design-icons/ShieldCheck'
import { changeMemberLevel } from '../../services/circles.ts'
import { showError } from '@nextcloud/dialogs'
import Member from '../../models/member.ts'
import RouterMixin from '../../mixins/RouterMixin'
export default {
name: 'MembersListItem',
components: {
Actions,
ActionButton,
ActionSeparator,
ActionText,
ExitToApp,
ListItemIcon,
ShieldCheck,
},
mixins: [RouterMixin],
props: {
source: {
type: Member,
required: true,
},
},
data() {
return {
CIRCLES_MEMBER_LEVELS,
loading: false,
}
},
computed: {
/**
* Return the current circle
* @returns {Circle}
*/
circle() {
return this.$store.getters.getCircle(this.selectedCircle)
},
avatarUrl() {
if (this.contact.url) {
return `${this.contact.url}?photo`
}
return undefined
},
/**
* Current member level translated name
* @returns {string}
*/
levelName() {
return CIRCLES_MEMBER_LEVELS[this.source.level]
|| CIRCLES_MEMBER_LEVELS[MemberLevels.MEMBER]
},
/**
* Current user member level
* @returns {number}
*/
currentUserLevel() {
return this.circle?.initiator?.level || MemberLevels.MEMBER
},
/**
* Current user member level
* @returns {string}
*/
currentUserId() {
return this.circle?.initiator?.singleId
},
/**
* Available levels change to the current user
* @returns {Array}
*/
availableLevelsChange() {
return Object.keys(CIRCLES_MEMBER_LEVELS)
// Object.keys returns those as string
.map(level => parseInt(level, 10))
// we cannot set to a level higher than the current user's level
.filter(level => level < this.currentUserLevel)
// we cannot set to the level this member is already
.filter(level => level !== this.source.level)
},
/**
* Is the current member the current user?
* @returns {boolean}
*/
isCurrentUser() {
return this.currentUserId === this.source.singleId
},
/**
* Can the current user change the level of others?
* @returns {boolean}
*/
canChangeLevel() {
// we can change if the member is at the same
// or lower level as the current user
// BUT not an owner as there can/must always be one
return this.availableLevelsChange.length > 0
&& this.currentUserLevel >= this.source.level
&& this.circle.canManageMembers
&& !(this.circle.isOwner && this.isCurrentUser)
},
/**
* Can the current user delete members or?
* @returns {boolean}
*/
canDelete() {
return this.currentUserLevel > MemberLevels.MEMBER
&& this.source.level <= this.currentUserLevel
&& !this.isCurrentUser
},
},
methods: {
/**
* Return the promote/demote member action label
* @param {MemberLevel} level the member level
* @returns {string}
*/
levelChangeLabel(level) {
if (this.source.level < level) {
return t('contacts', 'Promote to {level}', { level: CIRCLES_MEMBER_LEVELS[level] })
}
return t('contacts', 'Demote to {level}', { level: CIRCLES_MEMBER_LEVELS[level] })
},
/**
* Delete the current member
*/
async deleteMember() {
this.loading = true
try {
await this.$store.dispatch('deleteMemberFromCircle', {
member: this.source,
leave: this.isCurrentUser,
})
} catch (error) {
if (error.response.status === 404) {
console.debug('Member is not in circle')
return
}
console.error('Could not delete the member', this.source, error)
showError(t('contacts', 'Could not delete the member {displayName}', this.source))
} finally {
this.loading = false
}
},
async changeLevel(level) {
this.loading = true
try {
await changeMemberLevel(this.circle.id, this.source.id, level)
this.showLevelMenu = false
// this.source is a class. We're modifying the class setter, not the prop itself
// eslint-disable-next-line vue/no-mutating-props
this.source.level = level
} catch (error) {
console.error('Could not change the member level to', CIRCLES_MEMBER_LEVELS[level])
showError(t('contacts', 'Could not change the member level to {level}', {
level: CIRCLES_MEMBER_LEVELS[level],
}))
} finally {
this.loading = false
}
},
/**
* Reset menu on close
*/
onMenuClose() {
this.showLevelMenu = false
},
},
}
</script>
<style lang="scss">
.members-list__item {
padding: 8px;
&:focus,
&:hover {
background-color: var(--color-background-hover);
}
}
</style>