491 lines
13 KiB
Vue
491 lines
13 KiB
Vue
<!--
|
||
- @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>
|