nextcloud-calendar/src/components/AppNavigation/CalendarList/CalendarListItem.vue

491 lines
13 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
- @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com>
- @author Georg Ehrke <oc.list@georgehrke.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>
<AppNavigationItem
v-click-outside="closeShareMenu"
:loading="calendar.loading"
:title="calendar.displayName || $t('calendar', 'Untitled calendar')"
:class="{deleted: !!deleteTimeout, disabled: !calendar.enabled, 'open-sharing': shareMenuOpen}"
@click.prevent.stop="toggleEnabled">
<AppNavigationIconBullet
v-if="calendar.enabled"
slot="icon"
:color="calendar.color"
@click.prevent.stop="toggleEnabled" />
<AppNavigationDisabledCalendarIconBullet
v-if="!calendar.enabled"
slot="icon"
@click.prevent.stop="toggleEnabled" />
<template v-if="!deleteTimeout" slot="counter">
<Actions v-if="showSharingIcon">
<ActionButton :icon="sharingIconClass" @click="toggleShareMenu" />
</Actions>
<Avatar v-if="isSharedWithMe && loadedOwnerPrincipal" :user="ownerUserId" :display-name="ownerDisplayname" />
<div v-if="isSharedWithMe && !loadedOwnerPrincipal" class="icon icon-loading" />
</template>
<template v-if="!deleteTimeout" slot="actions">
<ActionButton
v-if="showRenameLabel"
icon="icon-rename"
@click.prevent.stop="openRenameInput">
{{ $t('calendar', 'Edit name') }}
</ActionButton>
<ActionInput
v-if="showRenameInput"
icon="icon-rename"
:value="calendar.displayName"
@submit.prevent.stop="saveRenameInput" />
<ActionText
v-if="showRenameSaving"
icon="icon-loading-small">
<!-- eslint-disable-next-line no-irregular-whitespace -->
{{ $t('calendar', 'Saving name …') }}
</ActionText>
<ActionButton
v-if="showColorLabel"
icon="icon-rename"
@click.prevent.stop="openColorInput">
{{ $t('calendar', 'Edit color') }}
</ActionButton>
<ActionInput
v-if="showColorInput"
icon="icon-rename"
:value="calendar.color"
type="color"
@submit.prevent.stop="saveColorInput" />
<ActionText
v-if="showColorSaving"
icon="icon-loading-small">
<!-- eslint-disable-next-line no-irregular-whitespace -->
{{ $t('calendar', 'Saving color …') }}
</ActionText>
<ActionButton
icon="icon-clippy"
@click.stop.prevent="copyLink">
{{ $t('calendar', 'Copy private link') }}
</ActionButton>
<ActionLink
icon="icon-download"
target="_blank"
:href="downloadUrl">
{{ $t('calendar', 'Download') }}
</ActionLink>
<ActionButton
v-if="calendar.isSharedWithMe"
icon="icon-delete"
@click.prevent.stop="deleteCalendar">
{{ $t('calendar', 'Unshare from me') }}
</ActionButton>
<ActionButton
v-if="!calendar.isSharedWithMe"
icon="icon-delete"
@click.prevent.stop="deleteCalendar">
{{ $t('calendar', 'Delete') }}
</ActionButton>
</template>
<template v-if="!!deleteTimeout" slot="actions">
<ActionButton
v-if="calendar.isSharedWithMe"
icon="icon-history"
@click.prevent.stop="cancelDeleteCalendar">
{{ $n('calendar', 'Unsharing the calendar in {countdown} second', 'Unsharing the calendar in {countdown} seconds', countdown, { countdown }) }}
</ActionButton>
<ActionButton
v-if="!calendar.isSharedWithMe"
icon="icon-history"
@click.prevent.stop="cancelDeleteCalendar">
{{ $n('calendar', 'Deleting the calendar in {countdown} second', 'Deleting the calendar in {countdown} seconds', countdown, { countdown }) }}
</ActionButton>
</template>
<template v-if="!deleteTimeout">
<div v-show="shareMenuOpen" class="sharing-section">
<CalendarListItemSharingSearch v-if="calendar.canBeShared" :calendar="calendar" />
<CalendarListItemSharingPublishItem v-if="calendar.canBePublished" :calendar="calendar" />
<CalendarListItemSharingShareItem v-for="sharee in calendar.shares"
v-show="shareMenuOpen"
:key="sharee.uri"
:sharee="sharee"
:calendar="calendar" />
</div>
</template>
</AppNavigationItem>
</template>
<script>
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
import Actions from '@nextcloud/vue/dist/Components/Actions'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import ActionInput from '@nextcloud/vue/dist/Components/ActionInput'
import ActionLink from '@nextcloud/vue/dist/Components/ActionLink'
import ActionText from '@nextcloud/vue/dist/Components/ActionText'
import AppNavigationIconBullet from '@nextcloud/vue/dist/Components/AppNavigationIconBullet'
import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem'
import ClickOutside from 'vue-click-outside'
import {
showInfo,
showSuccess,
showError,
} from '@nextcloud/dialogs'
import {
generateRemoteUrl,
} from '@nextcloud/router'
import AppNavigationDisabledCalendarIconBullet from './AppNavigationDisabledCalendarIconBullet.vue'
import CalendarListItemSharingSearch from './CalendarListItemSharingSearch.vue'
import CalendarListItemSharingPublishItem from './CalendarListItemSharingPublishItem.vue'
import CalendarListItemSharingShareItem from './CalendarListItemSharingShareItem.vue'
export default {
name: 'CalendarListItem',
components: {
Avatar,
Actions,
ActionButton,
ActionInput,
ActionLink,
ActionText,
AppNavigationDisabledCalendarIconBullet,
AppNavigationIconBullet,
AppNavigationItem,
CalendarListItemSharingSearch,
CalendarListItemSharingPublishItem,
CalendarListItemSharingShareItem,
},
directives: {
ClickOutside,
},
props: {
calendar: {
type: Object,
required: true,
},
},
data() {
return {
// Rename action
showRenameLabel: true,
showRenameInput: false,
showRenameSaving: false,
// Color action
showColorLabel: true,
showColorInput: false,
showColorSaving: false,
// share menu
shareMenuOpen: false,
// Deleting
deleteInterval: null,
deleteTimeout: null,
countdown: 7,
}
},
computed: {
/**
* Download url of the calendar
*
* @returns {String}
*/
downloadUrl() {
return this.calendar.url + '?export'
},
/**
* Whether or not to display the sharing icon.
* It will only be displayed when the calendar is either sharable or publishable
*
* @returns {Boolean}
*/
showSharingIcon() {
return this.calendar.canBeShared || this.calendar.canBePublished
},
/**
* The sharing icon class.
* This figures out what icon to display.
*
* The anchor icon when the calendar is published
* The sharing icon with high opacity when the calendar is shared
* The sharing icon with low opacity when the calendar is neither shared nor published
*
* @returns {String}
*/
sharingIconClass() {
if (this.isPublished) {
return 'icon-public'
}
if (this.isShared) {
return 'icon-shared'
}
return 'icon-share'
},
/**
* Whether or not the calendar is either shared or published
* This is used to figure out whether or not to display the Shared label
*
* @returns {Boolean}
*/
isSharedOrPublished() {
return this.isShared || this.isPublished
},
/**
* Is the calendar shared?
*
* @returns {Boolean}
*/
isShared() {
return !!this.calendar.shares.length
},
/**
* Is the calendar shared with me?
*
* @returns {Boolean}
*/
isSharedWithMe() {
return this.calendar.isSharedWithMe
},
/**
* Is the calendar published
*
* @returns {Boolean}
*/
isPublished() {
return !!this.calendar.publishURL
},
/**
* TODO: this should use principals and principal.userId
*
* @returns {String}
*/
owner() {
if (this.calendar.owner.indexOf('principal:principals/users/') === '0') {
console.debug(this.calendar.owner.substr(27))
return this.calendar.owner.substr(27)
}
return ''
},
/**
* Whether or not the information about the owner principal was loaded
*
* @returns {Boolean}
*/
loadedOwnerPrincipal() {
return this.$store.getters.getPrincipalByUrl(this.calendar.owner) !== undefined
},
ownerUserId() {
const principal = this.$store.getters.getPrincipalByUrl(this.calendar.owner)
if (principal) {
return principal.userId
}
return ''
},
ownerDisplayname() {
const principal = this.$store.getters.getPrincipalByUrl(this.calendar.owner)
if (principal) {
return principal.displayname
}
return ''
},
},
methods: {
/**
* Toggles the enabled state of this calendar
*/
toggleEnabled() {
this.$store.dispatch('toggleCalendarEnabled', { calendar: this.calendar })
.catch((error) => {
showError(this.$t('calendar', 'An error occurred, unable to change visibility of the calendar.'))
console.error(error)
})
},
/**
* Deletes or unshares the calendar
*/
deleteCalendar() {
this.deleteInterval = setInterval(() => {
this.countdown--
if (this.countdown < 0) {
this.countdown = 0
}
}, 1000)
this.deleteTimeout = setTimeout(async() => {
try {
await this.$store.dispatch('deleteCalendar', { calendar: this.calendar })
} catch (error) {
showError(this.$t('calendar', 'An error occurred, unable to delete the calendar.'))
console.error(error)
} finally {
clearInterval(this.deleteInterval)
this.deleteTimeout = null
this.deleteInterval = null
this.countdown = 7
}
}, 7000)
},
/**
* Cancels the deletion of a calendar
*/
cancelDeleteCalendar() {
clearTimeout(this.deleteTimeout)
clearInterval(this.deleteInterval)
this.deleteTimeout = null
this.deleteInterval = null
this.countdown = 7
},
/**
* Closes the share menu
* This is used with v-click-outside
*
* @param {Event} event The javascript click event
*/
closeShareMenu(event) {
if (!event.isTrusted) {
return
}
if (this.$el.contains(event.target)) {
this.shareMenuOpen = true
return
}
if (event.composedPath && event.composedPath().includes(this.$el)) {
this.shareMenuOpen = true
return
}
this.shareMenuOpen = false
},
/**
* Toggles the visibility of the share menu
*/
toggleShareMenu() {
this.shareMenuOpen = !this.shareMenuOpen
console.debug('toggled share menu')
},
/**
* Copies the private calendar link
* to be used with clients like Thunderbird
*/
async copyLink() {
const rootUrl = generateRemoteUrl('dav')
const url = new URL(this.calendar.url, rootUrl)
// TODO - use menuOpen to keep it open instead of toast
try {
await this.$copyText(url)
showSuccess(this.$t('calendar', 'Calendar link copied to clipboard.'))
} catch (error) {
console.debug(error)
showError(this.$t('calendar', 'Calendar link could not be copied to clipboard.'))
}
},
/**
* Opens the input-field to rename the calendar
*/
openRenameInput() {
// Hide label and show input
this.showRenameLabel = false
this.showRenameInput = true
this.showRenameSaving = false
// Reset color input if necessary
this.showColorLabel = true
this.showColorInput = false
this.showColorSaving = false
},
/**
* Saves the modified name of a calendar
*
* @param {Event} event The submit event
*/
async saveRenameInput(event) {
this.showRenameInput = false
this.showRenameSaving = true
const newName = event.target.querySelector('input[type=text]').value
try {
await this.$store.dispatch('renameCalendar', {
calendar: this.calendar,
newName,
})
this.showRenameLabel = true
this.showRenameInput = false
this.showRenameSaving = false
} catch (error) {
showInfo(this.$t('calendar', 'An error occurred, unable to rename the calendar.'))
console.error(error)
this.showRenameLabel = false
this.showRenameInput = true
this.showRenameSaving = false
}
},
/**
* Opens the color-picker
*/
openColorInput() {
// Hide label and show input
this.showColorLabel = false
this.showColorInput = true
this.showColorSaving = false
// Reset rename input if necessary
this.showRenameLabel = true
this.showRenameInput = false
this.showRenameSaving = false
},
/**
* Saves the modified color of a calendar
*
* @param {Event} event The submit event
*/
async saveColorInput(event) {
this.showColorInput = false
this.showColorSaving = true
const newColor = event.target.querySelector('input[type=color]').value
try {
await this.$store.dispatch('changeCalendarColor', {
calendar: this.calendar,
newColor,
})
this.showColorLabel = true
this.showColorInput = false
this.showColorSaving = false
} catch (error) {
showInfo(this.$t('calendar', 'An error occurred, unable to change the calendar\'s color.'))
console.error(error)
this.showColorLabel = false
this.showColorInput = true
this.showColorSaving = false
}
},
},
}
</script>