diff options
author | Richard Steinmetz <richard@steinmetz.cloud> | 2022-08-02 13:22:32 +0300 |
---|---|---|
committer | Richard Steinmetz <richard@steinmetz.cloud> | 2022-11-04 18:29:19 +0300 |
commit | 1632e8edbbd5fd7d8a6cd35a4d9ddc374343d088 (patch) | |
tree | 4b48b731e20729ec83cf0fdf8a84853b5fbe4fee | |
parent | 871b3ef04576067bd6c0a307858ccbb0a79f83f5 (diff) |
Redesign calendar sharingenh/4124/redesign-calendar-sharing
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
-rw-r--r-- | src/components/AppNavigation/CalendarList/CalendarListItem.vue | 314 | ||||
-rw-r--r-- | src/components/AppNavigation/EditCalendarModal.vue | 301 | ||||
-rw-r--r-- | src/components/AppNavigation/EditCalendarModal/PublishCalendar.vue (renamed from src/components/AppNavigation/CalendarList/CalendarListItemSharingPublishItem.vue) | 262 | ||||
-rw-r--r-- | src/components/AppNavigation/EditCalendarModal/ShareItem.vue (renamed from src/components/AppNavigation/CalendarList/CalendarListItemSharingShareItem.vue) | 88 | ||||
-rw-r--r-- | src/components/AppNavigation/EditCalendarModal/SharingSearch.vue (renamed from src/components/AppNavigation/CalendarList/CalendarListItemSharingSearch.vue) | 25 | ||||
-rw-r--r-- | src/store/calendars.js | 13 | ||||
-rw-r--r-- | src/views/Calendar.vue | 3 |
7 files changed, 599 insertions, 407 deletions
diff --git a/src/components/AppNavigation/CalendarList/CalendarListItem.vue b/src/components/AppNavigation/CalendarList/CalendarListItem.vue index 06c66246..178cf297 100644 --- a/src/components/AppNavigation/CalendarList/CalendarListItem.vue +++ b/src/components/AppNavigation/CalendarList/CalendarListItem.vue @@ -1,6 +1,8 @@ <!-- - @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com> + - - @author Georg Ehrke <oc.list@georgehrke.com> + - @author Richard Steinmetz <richard@steinmetz.cloud> - - @license AGPL-3.0-or-later - @@ -20,13 +22,12 @@ --> <template> - <AppNavigationItem v-click-outside="closeShareMenu" - :loading="calendar.loading" + <AppNavigationItem :loading="calendar.loading" :aria-description="descriptionAppNavigationItem" :title="calendar.displayName || $t('calendar', 'Untitled calendar')" - :class="{deleted: !!deleteTimeout, disabled: !calendar.enabled, 'open-sharing': shareMenuOpen}" + :class="{deleted: !!deleteTimeout, disabled: !calendar.enabled}" @click.prevent.stop="toggleEnabled"> - <template slot="icon"> + <template #icon> <CheckboxBlankCircle v-if="calendar.enabled" :size="20" :fill-color="calendar.color" /> @@ -35,133 +36,74 @@ :fill-color="calendar.color" /> </template> - <template v-if="!deleteTimeout" slot="counter"> - <Actions v-if="showSharingIcon" class="sharing"> - <ActionButton @click="toggleShareMenu"> - <template #icon> - <LinkVariant v-if="isPublished" :size="20" decorative /> - <ShareVariant v-else - :size="20" - decorative - :class="{share: !isShared}" /> - </template> - </ActionButton> - </Actions> - <Avatar v-if="isSharedWithMe && loadedOwnerPrincipal" :user="ownerUserId" :display-name="ownerDisplayname" /> + <template #counter> + <NcAvatar 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" - @click.prevent.stop="openRenameInput"> - <template #icon> - <Pencil :size="20" decorative /> - </template> - {{ $t('calendar', 'Edit name') }} - </ActionButton> - <ActionInput v-if="showRenameInput" - :value="calendar.displayName" - @submit.prevent.stop="saveRenameInput"> - <template #icon> - <Pencil :size="20" decorative /> - </template> - </ActionInput> - <ActionText v-if="showRenameSaving" - icon="icon-loading-small"> - <!-- eslint-disable-next-line no-irregular-whitespace --> - {{ $t('calendar', 'Saving name …') }} - </ActionText> - <ActionButton v-if="showColorLabel" - @click.prevent.stop="openColorInput"> - <template #icon> - <Pencil :size="20" decorative /> - </template> - {{ $t('calendar', 'Edit color') }} - </ActionButton> - <ActionInput v-if="showColorInput" - :value="calendar.color" - type="color" - @submit.prevent.stop="saveColorInput"> - <template #icon> - <Pencil :size="20" decorative /> - </template> - </ActionInput> - <ActionText v-if="showColorSaving" - icon="icon-loading-small"> - <!-- eslint-disable-next-line no-irregular-whitespace --> - {{ $t('calendar', 'Saving color …') }} - </ActionText> - <ActionButton @click.stop.prevent="copyLink"> - <template #icon> - <LinkVariant :size="20" decorative /> - </template> - {{ $t('calendar', 'Copy private link') }} - </ActionButton> - <ActionLink target="_blank" - :href="downloadUrl"> - <template #icon> - <Download :size="20" decorative /> - </template> - {{ $t('calendar', 'Export') }} - </ActionLink> - <ActionButton v-if="calendar.isSharedWithMe" - @click.prevent.stop="deleteCalendar"> - <template #icon> - <Close :size="20" decorative /> - </template> - {{ $t('calendar', 'Unshare from me') }} - </ActionButton> - <ActionButton v-if="!calendar.isSharedWithMe" - @click.prevent.stop="deleteCalendar"> - <template #icon> - <Delete :size="20" decorative /> - </template> - {{ $t('calendar', 'Delete') }} - </ActionButton> - </template> - - <template v-if="!!deleteTimeout" slot="actions"> - <ActionButton v-if="calendar.isSharedWithMe" - @click.prevent.stop="cancelDeleteCalendar"> - <template #icon> - <Undo :size="20" decorative /> - </template> - {{ $n('calendar', 'Unsharing the calendar in {countdown} second', 'Unsharing the calendar in {countdown} seconds', countdown, { countdown }) }} - </ActionButton> - <ActionButton v-else - @click.prevent.stop="cancelDeleteCalendar"> - <template #icon> - <Undo :size="20" decorative /> - </template> - {{ $n('calendar', 'Deleting the calendar in {countdown} second', 'Deleting the calendar in {countdown} seconds', countdown, { countdown }) }} - </ActionButton> - </template> + <template #actions> + <template v-if="!deleteTimeout"> + <ActionButton @click.prevent.stop="showEditModal"> + <template #icon> + <Pencil :size="20" decorative /> + </template> + {{ $t('calendar', 'Edit and share calendar') }} + </ActionButton> + <ActionButton @click.stop.prevent="copyLink"> + <template #icon> + <LinkVariant :size="20" decorative /> + </template> + {{ $t('calendar', 'Copy private link') }} + </ActionButton> + <ActionLink target="_blank" + :href="downloadUrl"> + <template #icon> + <Download :size="20" decorative /> + </template> + {{ $t('calendar', 'Export') }} + </ActionLink> + <ActionButton v-if="calendar.isSharedWithMe" + @click.prevent.stop="deleteCalendar"> + <template #icon> + <Close :size="20" decorative /> + </template> + {{ $t('calendar', 'Unshare from me') }} + </ActionButton> + <ActionButton v-if="!calendar.isSharedWithMe" + @click.prevent.stop="deleteCalendar"> + <template #icon> + <Delete :size="20" decorative /> + </template> + {{ $t('calendar', 'Delete') }} + </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 v-if="deleteTimeout"> + <ActionButton v-if="calendar.isSharedWithMe" + @click.prevent.stop="cancelDeleteCalendar"> + <template #icon> + <Undo :size="20" decorative /> + </template> + {{ $n('calendar', 'Unsharing the calendar in {countdown} second', 'Unsharing the calendar in {countdown} seconds', countdown, { countdown }) }} + </ActionButton> + <ActionButton v-else + @click.prevent.stop="cancelDeleteCalendar"> + <template #icon> + <Undo :size="20" decorative /> + </template> + {{ $n('calendar', 'Deleting the calendar in {countdown} second', 'Deleting the calendar in {countdown} seconds', countdown, { countdown }) }} + </ActionButton> + </template> </template> </AppNavigationItem> </template> <script> -import Avatar from '@nextcloud/vue/dist/Components/NcAvatar.js' -import Actions from '@nextcloud/vue/dist/Components/NcActions.js' +import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js' import ActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' -import ActionInput from '@nextcloud/vue/dist/Components/NcActionInput.js' import ActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js' -import ActionText from '@nextcloud/vue/dist/Components/NcActionText.js' import AppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js' -import ClickOutside from 'vue-click-outside' import { - showInfo, showSuccess, showError, } from '@nextcloud/dialogs' @@ -169,9 +111,6 @@ import { generateRemoteUrl, } from '@nextcloud/router' -import CalendarListItemSharingSearch from './CalendarListItemSharingSearch.vue' -import CalendarListItemSharingPublishItem from './CalendarListItemSharingPublishItem.vue' -import CalendarListItemSharingShareItem from './CalendarListItemSharingShareItem.vue' import CheckboxBlankCircle from 'vue-material-design-icons/CheckboxBlankCircle.vue' import CheckboxBlankCircleOutline from 'vue-material-design-icons/CheckboxBlankCircleOutline.vue' import Close from 'vue-material-design-icons/Close.vue' @@ -179,22 +118,15 @@ import Delete from 'vue-material-design-icons/Delete.vue' import Download from 'vue-material-design-icons/Download.vue' import LinkVariant from 'vue-material-design-icons/LinkVariant.vue' import Pencil from 'vue-material-design-icons/Pencil.vue' -import ShareVariant from 'vue-material-design-icons/ShareVariant.vue' import Undo from 'vue-material-design-icons/Undo.vue' export default { name: 'CalendarListItem', components: { - Avatar, - Actions, + NcAvatar, ActionButton, - ActionInput, ActionLink, - ActionText, AppNavigationItem, - CalendarListItemSharingSearch, - CalendarListItemSharingPublishItem, - CalendarListItemSharingShareItem, CheckboxBlankCircle, CheckboxBlankCircleOutline, Close, @@ -202,12 +134,8 @@ export default { Download, LinkVariant, Pencil, - ShareVariant, Undo, }, - directives: { - ClickOutside, - }, props: { calendar: { type: Object, @@ -216,14 +144,6 @@ export default { }, data() { return { - // Rename action - showRenameLabel: true, - showRenameInput: false, - showRenameSaving: false, - // Color action - showColorLabel: true, - showColorInput: false, - showColorSaving: false, // share menu shareMenuOpen: false, // Deleting @@ -348,6 +268,7 @@ export default { console.error(error) }) }, + /** * Deletes or unshares the calendar */ @@ -373,6 +294,7 @@ export default { } }, 7000) }, + /** * Cancels the deletion of a calendar */ @@ -383,36 +305,7 @@ export default { 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 @@ -431,85 +324,14 @@ export default { 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 + * Open the calendar modal for this calendar item. */ - 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 - } + showEditModal() { + this.$store.commit('showEditCalendarModal', { + calendarId: this.calendar.id, + }) }, }, } diff --git a/src/components/AppNavigation/EditCalendarModal.vue b/src/components/AppNavigation/EditCalendarModal.vue new file mode 100644 index 00000000..7005f475 --- /dev/null +++ b/src/components/AppNavigation/EditCalendarModal.vue @@ -0,0 +1,301 @@ +<!-- + - @copyright Copyright (c) 2022 Richard Steinmetz <richard@steinmetz.cloud> + - + - @author Richard Steinmetz <richard@steinmetz.cloud> + - + - @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="!!editCalendarModal && calendar" size="normal" @close="saveAndClose"> + <div class="edit-calendar-modal"> + <h2>{{ $t('calendar', 'Edit calendar') }}</h2> + + <div class="edit-calendar-modal__name-and-color"> + <div class="edit-calendar-modal__name-and-color__color"> + <div v-if="loading" + class="edit-calendar-modal__name-and-color__color__dot" + :style="{'background-color': calendarColor}" /> + <NcColorPicker v-else v-model="calendarColor"> + <div class="edit-calendar-modal__name-and-color__color__dot" + :style="{'background-color': calendarColor}" /> + </NcColorPicker> + </div> + + <input v-model="calendarName" + class="edit-calendar-modal__name-and-color__name" + type="text" + :disabled="loading"> + </div> + + <!--<h2>Additional actions</h2>--> + <div class="edit-calendar-modal__additional-actions"> + <NcButton type="primary" @click="copyPrivateLink"> + <template #icon> + <LinkVariantIcon :size="20" /> + </template> + {{ $t('calendar', 'Copy private link') }} + </NcButton> + + <NcButton type="primary" @click="exportCalendar"> + <template #icon> + <DownloadIcon :size="20" /> + </template> + {{ $t('calendar', 'Export') }} + </NcButton> + + <NcButton type="error" @click="deleteCalendar"> + <template #icon> + <DeleteIcon :size="20" /> + </template> + {{ $t('calendar', 'Delete') }} + </NcButton> + </div> + + <h2 class="edit-calendar-modal__sharing-header"> + {{ $t('calendar', 'Share calendar') }} + </h2> + + <div class="edit-calendar-modal__sharing"> + <SharingSearch :calendar="calendar" /> + <PublishCalendar :calendar="calendar" /> + <ShareItem v-for="sharee in calendar.shares" + :key="sharee.uri" + :sharee="sharee" + :calendar="calendar" /> + </div> + + <!-- + <div class="edit-calendar-modal__actions"> + <NcButton type="primary" @click="saveAndClose"> + {{ $t('calendar', 'Save') }} + </NcButton> + </div> + --> + </div> + </NcModal> +</template> + +<script> +import NcModal from '@nextcloud/vue/dist/Components/NcModal' +import NcColorPicker from '@nextcloud/vue/dist/Components/NcColorPicker' +import NcButton from '@nextcloud/vue/dist/Components/NcButton' +import PublishCalendar from './EditCalendarModal/PublishCalendar' +import SharingSearch from './EditCalendarModal/SharingSearch' +import ShareItem from './EditCalendarModal/ShareItem' +import { mapGetters } from 'vuex' +import logger from '../../utils/logger' +import debounce from 'debounce' +import LinkVariantIcon from 'vue-material-design-icons/LinkVariant.vue' +import DownloadIcon from 'vue-material-design-icons/Download.vue' +import DeleteIcon from 'vue-material-design-icons/Delete.vue' + +export default { + name: 'EditCalendarModal', + components: { + NcModal, + NcColorPicker, + NcButton, + PublishCalendar, + SharingSearch, + ShareItem, + LinkVariantIcon, + DownloadIcon, + DeleteIcon, + }, + props: { + }, + data() { + return { + loading: false, + calendarColor: undefined, + calendarColorChanged: false, + calendarName: undefined, + calendarNameChanged: false, + } + }, + computed: { + ...mapGetters(['editCalendarModal']), + calendar() { + const id = this.editCalendarModal?.calendarId + if (!id) { + return undefined + } + + return this.$store.getters.getCalendarById(id) + }, + }, + watch: { + async calendarColor() { + await this.saveColor() + }, + async calendarName() { + await this.saveName() + }, + editCalendarModal(value) { + if (!value) { + return + } + + this.calendarName = this.calendar.displayName + this.calendarColor = this.calendar.color + }, + }, + methods: { + /** + * Close the modal (without saving). + */ + closeModal() { + this.$store.commit('hideEditCalendarModal') + }, + + /** + * Save the calendar color. + */ + async saveColor() { + this.loading = true + + try { + await this.$store.dispatch('changeCalendarColor', { + calendar: this.calendar, + newColor: this.calendarColor, + }) + } catch (error) { + logger.error('Failed to save calendar color', { + calendar: this.calendar, + newColor: this.calendarColor, + }) + } finally { + this.loading = false + } + }, + saveColorDebounced: debounce(() => this.saveColor(), 1000), + + /** + * Save the calendar name. + */ + async saveName() { + this.loading = true + + try { + await this.$store.dispatch('renameCalendar', { + calendar: this.calendar, + newName: this.calendarName, + }) + } catch (error) { + logger.error('Failed to save calendar name', { + calendar: this.calendar, + newName: this.calendarName, + }) + } finally { + this.loading = false + } + }, + saveNameDebounced: debounce(() => this.saveName(), 1000), + + /** + * Save unsaved changes and close the modal. + * + * @return {Promise<void>} + */ + async saveAndClose() { + await this.saveColor.flush() + await this.saveName.flush() + this.closeModal() + }, + + /** + * Copy the internal/private link of this calendar to the clipboard. + * + * @return {Promise<void>} + */ + async copyPrivateLink() { + // TODO + }, + + /** + * Export the calendar as a download. + * + * @return {Promise<void>} + */ + async exportCalendar() { + // TODO + }, + + /** + * Delete the calendar. + * + * @return {Promise<void>} + */ + async deleteCalendar() { + // TODO + }, + }, +} +</script> + +<style lang="scss" scoped> +.edit-calendar-modal { + padding: 20px; + display: flex; + flex-direction: column; + + &__name-and-color { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 10px; + + &__color { + ::v-deep &__dot { + width: 24px; + height: 24px; + border-radius: 12px; + } + } + + &__name { + flex: 1 auto; + } + } + + &__additional-actions { + display: flex; + gap: 10px; + + button { + flex: 1 auto; + } + } + + &__sharing-header { + margin-top: 25px; + } + + &__sharing { + display: flex; + flex-direction: column; + gap: 5px; + } + + &__actions { + display: flex; + justify-content: end; + margin-top: 15px; + } +} +</style> diff --git a/src/components/AppNavigation/CalendarList/CalendarListItemSharingPublishItem.vue b/src/components/AppNavigation/EditCalendarModal/PublishCalendar.vue index 7768a29b..3e1c1e8e 100644 --- a/src/components/AppNavigation/CalendarList/CalendarListItemSharingPublishItem.vue +++ b/src/components/AppNavigation/EditCalendarModal/PublishCalendar.vue @@ -1,6 +1,8 @@ <!-- - @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com> + - - @author Georg Ehrke <oc.list@georgehrke.com> + - @author Richard Steinmetz <richard@steinmetz.cloud> - - @license AGPL-3.0-or-later - @@ -20,135 +22,127 @@ --> <template> - <AppNavigationItem :title="$t('calendar', 'Share link')" - :menu-open.sync="menuOpen"> - <template slot="icon"> - <LinkVariant :class="{published: isPublished}" - :size="18" - decorative - class="avatar" /> - </template> + <div class="publish-calendar"> + <div class="publish-calendar__icon"> + <LinkVariant :size="20" /> + </div> - <template v-if="!isPublished" slot="actions"> - <ActionButton v-if="!publishingCalendar" - key="publish" - @click.prevent.stop="publishCalendar"> - <template #icon> - <Plus :size="20" decorative /> - </template> - {{ $t('calendar', 'Publish calendar') }} - </ActionButton> - <ActionButton v-else - key="publishing" - icon="icon-loading-small" - :disabled="true"> - {{ $t('calendar', 'Publishing calendar') }} - </ActionButton> - </template> + <p class="publish-calendar__label"> + {{ $t('calendar', 'Share link') }} + </p> - <template v-if="isPublished" slot="counter"> - <Actions> - <ActionButton @click.prevent.stop="copyPublicLink"> + <template v-if="isPublished"> + <NcActions> + <NcActionButton @click.prevent.stop="copyPublicLink"> <template #icon> <ClipboardArrowLeftOutline :size="20" decorative /> </template> {{ $t('calendar', 'Copy public link') }} - </ActionButton> - </Actions> - </template> - <template v-if="isPublished" slot="actions"> - <ActionButton v-if="showEMailLabel" - @click.prevent.stop="openEMailLinkInput"> - <template #icon> - <Email :size="20" decorative /> - </template> - {{ $t('calendar', 'Send link to calendar via email') }} - </ActionButton> - <ActionInput v-if="showEMailInput" - @submit.prevent.stop="sendLinkViaEMail"> - <template #icon> - <Email :size="20" decorative /> - </template> - {{ $t('calendar', 'Enter one address') }} - </ActionInput> - <ActionText v-if="showEMailSending" - icon="icon-loading-small"> - <!-- eslint-disable-next-line no-irregular-whitespace --> - {{ $t('calendar', 'Sending email …') }} - </ActionText> + </NcActionButton> + </NcActions> - <ActionButton v-if="showCopySubscriptionLinkLabel" - @click.prevent.stop="copySubscriptionLink"> - <template #icon> - <CalendarBlank :size="20" decorative /> - </template> - {{ $t('calendar', 'Copy subscription link') }} - </ActionButton> - <ActionText v-if="showCopySubscriptionLinkSpinner" - icon="icon-loading-small"> - <!-- eslint-disable-next-line no-irregular-whitespace --> - {{ $t('calendar', 'Copying link …') }} - </ActionText> - <ActionText v-if="showCopySubscriptionLinkSuccess"> - <template #icon> - <CalendarBlank :size="20" decorative /> - </template> - {{ $t('calendar', 'Copied link') }} - </ActionText> - <ActionText v-if="showCopySubscriptionLinkError"> - <template #icon> - <CalendarBlank :size="20" decorative /> - </template> - {{ $t('calendar', 'Could not copy link') }} - </ActionText> + <NcActions> + <NcActionButton v-if="showEMailLabel" + @click.prevent.stop="openEMailLinkInput"> + <template #icon> + <Email :size="20" decorative /> + </template> + {{ $t('calendar', 'Send link to calendar via email') }} + </NcActionButton> + <NcActionInput v-if="showEMailInput" + @submit.prevent.stop="sendLinkViaEMail"> + <template #icon> + <Email :size="20" decorative /> + </template> + {{ $t('calendar', 'Enter one address') }} + </NcActionInput> + <NcActionText v-if="showEMailSending" + icon="icon-loading-small"> + <!-- eslint-disable-next-line no-irregular-whitespace --> + {{ $t('calendar', 'Sending email …') }} + </NcActionText> - <ActionButton v-if="showCopyEmbedCodeLinkLabel" - @click.prevent.stop="copyEmbedCode"> - <template #icon> - <CodeBrackets :size="20" decorative /> - </template> - {{ $t('calendar', 'Copy embedding code') }} - </ActionButton> - <ActionText v-if="showCopyEmbedCodeLinkSpinner" - icon="icon-loading-small"> - <!-- eslint-disable-next-line no-irregular-whitespace --> - {{ $t('calendar', 'Copying code …') }} - </ActionText> - <ActionText v-if="showCopyEmbedCodeLinkSuccess"> - <template #icon> - <CodeBrackets :size="20" decorative /> - </template> - {{ $t('calendar', 'Copied code') }} - </ActionText> - <ActionText v-if="showCopyEmbedCodeLinkError"> - <template #icon> - <CodeBrackets :size="20" decorative /> - </template> - {{ $t('calendar', 'Could not copy code') }} - </ActionText> + <NcActionButton v-if="showCopySubscriptionLinkLabel" + @click.prevent.stop="copySubscriptionLink"> + <template #icon> + <CalendarBlank :size="20" decorative /> + </template> + {{ $t('calendar', 'Copy subscription link') }} + </NcActionButton> + <NcActionText v-if="showCopySubscriptionLinkSpinner" + icon="icon-loading-small"> + <!-- eslint-disable-next-line no-irregular-whitespace --> + {{ $t('calendar', 'Copying link …') }} + </NcActionText> + <NcActionText v-if="showCopySubscriptionLinkSuccess"> + <template #icon> + <CalendarBlank :size="20" decorative /> + </template> + {{ $t('calendar', 'Copied link') }} + </NcActionText> + <NcActionText v-if="showCopySubscriptionLinkError"> + <template #icon> + <CalendarBlank :size="20" decorative /> + </template> + {{ $t('calendar', 'Could not copy link') }} + </NcActionText> + + <NcActionButton v-if="showCopyEmbedCodeLinkLabel" + @click.prevent.stop="copyEmbedCode"> + <template #icon> + <CodeBrackets :size="20" decorative /> + </template> + {{ $t('calendar', 'Copy embedding code') }} + </NcActionButton> + <NcActionText v-if="showCopyEmbedCodeLinkSpinner" + icon="icon-loading-small"> + <!-- eslint-disable-next-line no-irregular-whitespace --> + {{ $t('calendar', 'Copying code …') }} + </NcActionText> + <NcActionText v-if="showCopyEmbedCodeLinkSuccess"> + <template #icon> + <CodeBrackets :size="20" decorative /> + </template> + {{ $t('calendar', 'Copied code') }} + </NcActionText> + <NcActionText v-if="showCopyEmbedCodeLinkError"> + <template #icon> + <CodeBrackets :size="20" decorative /> + </template> + {{ $t('calendar', 'Could not copy code') }} + </NcActionText> - <ActionButton v-if="!unpublishingCalendar" - @click.prevent.stop="unpublishCalendar"> + <NcActionButton v-if="!unpublishingCalendar" + @click.prevent.stop="unpublishCalendar"> + <template #icon> + <Delete :size="20" decorative /> + </template> + {{ $t('calendar', 'Delete share link') }} + </NcActionButton> + <NcActionText v-if="unpublishingCalendar" + icon="icon-loading-small"> + <!-- eslint-disable-next-line no-irregular-whitespace --> + {{ $t('calendar', 'Deleting share link …') }} + </NcActionText> + </NcActions> + </template> + <NcActions v-else> + <NcActionButton aria-label="$t('calendar', 'Share link')" + :disabled="publishingCalendar" + @click.prevent.stop="publishCalendar"> <template #icon> - <Delete :size="20" decorative /> + <PlusIcon :size="20" /> </template> - {{ $t('calendar', 'Delete share link') }} - </ActionButton> - <ActionText v-if="unpublishingCalendar" - icon="icon-loading-small"> - <!-- eslint-disable-next-line no-irregular-whitespace --> - {{ $t('calendar', 'Deleting share link …') }} - </ActionText> - </template> - </AppNavigationItem> + </NcActionButton> + </NcActions> + </div> </template> <script> -import Actions from '@nextcloud/vue/dist/Components/NcActions.js' -import ActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' -import ActionInput from '@nextcloud/vue/dist/Components/NcActionInput.js' -import ActionText from '@nextcloud/vue/dist/Components/NcActionText.js' -import AppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js' +import NcActions from '@nextcloud/vue/dist/Components/NcActions.js' +import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' +import NcActionInput from '@nextcloud/vue/dist/Components/NcActionInput.js' +import NcActionText from '@nextcloud/vue/dist/Components/NcActionText.js' import ClickOutside from 'vue-click-outside' import { generateRemoteUrl, @@ -167,23 +161,22 @@ import CodeBrackets from 'vue-material-design-icons/CodeBrackets.vue' import Delete from 'vue-material-design-icons/Delete.vue' import Email from 'vue-material-design-icons/Email.vue' import LinkVariant from 'vue-material-design-icons/LinkVariant.vue' -import Plus from 'vue-material-design-icons/Plus.vue' +import PlusIcon from 'vue-material-design-icons/Plus.vue' export default { - name: 'CalendarListItemSharingPublishItem', + name: 'PublishCalendar', components: { - Actions, - ActionButton, - ActionInput, - ActionText, - AppNavigationItem, + NcActions, + NcActionButton, + NcActionInput, + NcActionText, CalendarBlank, ClipboardArrowLeftOutline, CodeBrackets, Delete, Email, LinkVariant, - Plus, + PlusIcon, }, directives: { ClickOutside, @@ -385,3 +378,26 @@ export default { }, } </script> + +<style lang="scss" scoped> +.publish-calendar { + display: flex; + align-items: center; + gap: 10px; + + &__icon { + display: flex; + width: 32px; + height: 32px; + border-radius: 16px; + color: white; + background-color: var(--color-primary); + align-items: center; + justify-content: center; + } + + &__label { + flex: 1 auto; + } +} +</style> diff --git a/src/components/AppNavigation/CalendarList/CalendarListItemSharingShareItem.vue b/src/components/AppNavigation/EditCalendarModal/ShareItem.vue index 8f1f79e3..d6419def 100644 --- a/src/components/AppNavigation/CalendarList/CalendarListItemSharingShareItem.vue +++ b/src/components/AppNavigation/EditCalendarModal/ShareItem.vue @@ -1,6 +1,8 @@ <!-- - @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com> + - - @author Georg Ehrke <oc.list@georgehrke.com> + - @author Richard Steinmetz <richard@steinmetz.cloud> - - @license AGPL-3.0-or-later - @@ -20,56 +22,53 @@ --> <template> - <AppNavigationItem :title="sharee.displayName"> - <template slot="icon"> - <AccountMultiple v-if="sharee.isGroup" - :size="18" - decorative - class="avatar" /> - <IconCircle v-else-if="sharee.isCircle" /> - <Avatar v-else :user="sharee.id" :display-name="sharee.displayName" /> - </template> + <div class="share-item"> + <AccountMultiple v-if="sharee.isGroup" :size="20" class="share-item__group-icon" /> + <IconCircle v-else-if="sharee.isCircle" /> + <NcAvatar v-else :user="sharee.id" :display-name="sharee.displayName" /> + + <p class="share-item__label"> + {{ sharee.displayName }} + </p> - <template slot="counter"> - <ActionCheckbox :disabled="updatingSharee" - :checked="sharee.writeable" - @update:checked="updatePermission"> - {{ $t('calendar', 'can edit') }} - </ActionCheckbox> - </template> + <input :id="`${id}-can-edit`" + :disabled="updatingSharee" + :checked="sharee.writeable" + type="checkbox" + class="checkbox" + @update:checked="updatePermission"> + <label :for="`${id}-can-edit`">{{ $t('calendar', 'can edit') }}</label> - <template slot="actions"> - <ActionButton :disabled="updatingSharee" + <NcActions> + <NcActionButton :disabled="updatingSharee" @click.prevent.stop="unshare"> <template #icon> <Delete :size="20" decorative /> </template> {{ $t('calendar', 'Unshare with {displayName}', { displayName: sharee.displayName }) }} - </ActionButton> - </template> - </AppNavigationItem> + </NcActionButton> + </NcActions> + </div> </template> <script> -import ActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' -import ActionCheckbox from '@nextcloud/vue/dist/Components/NcActionCheckbox.js' -import AppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js' -import Avatar from '@nextcloud/vue/dist/Components/NcAvatar.js' -import { - showInfo, -} from '@nextcloud/dialogs' - +import NcActions from '@nextcloud/vue/dist/Components/NcActions.js' +import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' +import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js' import AccountMultiple from 'vue-material-design-icons/AccountMultiple.vue' import IconCircle from '../../Icons/IconCircles.vue' import Delete from 'vue-material-design-icons/Delete.vue' +import { + showInfo, +} from '@nextcloud/dialogs' +import { randomId } from '../../../utils/randomId' export default { - name: 'CalendarListItemSharingShareItem', + name: 'ShareItem', components: { - ActionButton, - ActionCheckbox, - AppNavigationItem, - Avatar, + NcActions, + NcActionButton, + NcAvatar, IconCircle, AccountMultiple, Delete, @@ -86,6 +85,7 @@ export default { }, data() { return { + id: randomId(), updatingSharee: false, } }, @@ -138,3 +138,23 @@ export default { }, } </script> + +<style lang="scss" scoped> +.share-item { + display: flex; + align-items: center; + gap: 10px; + + &__group-icon { + width: 32px; + height: 32px; + border-radius: 16px; + color: white; + background-color: var(--color-text-maxcontrast); + } + + &__label { + flex: 1 auto; + } +} +</style> diff --git a/src/components/AppNavigation/CalendarList/CalendarListItemSharingSearch.vue b/src/components/AppNavigation/EditCalendarModal/SharingSearch.vue index cfae4c5d..d8c26a60 100644 --- a/src/components/AppNavigation/CalendarList/CalendarListItemSharingSearch.vue +++ b/src/components/AppNavigation/EditCalendarModal/SharingSearch.vue @@ -4,6 +4,7 @@ - - @author Georg Ehrke <oc.list@georgehrke.com> - @author Jakob Röhrl <jakob.roehrl@web.de> + - @author Richard Steinmetz <richard@steinmetz.cloud> - - @license AGPL-3.0-or-later - @@ -23,23 +24,24 @@ --> <template> - <li class="app-navigation-entry__multiselect"> + <div class="sharing-search"> <Multiselect :options="usersOrGroups" :searchable="true" :internal-search="false" :max-height="600" :show-no-results="true" :placeholder="$t('calendar', 'Share with users or groups')" + class="sharing-search__select" :class="{ 'showContent': inputGiven, 'icon-loading': isLoading }" :user-select="true" - open-direction="bottom" + open-direction="above" track-by="user" label="displayName" @search-change="findSharee" @change="shareCalendar"> <span slot="noResult">{{ $t('calendar', 'No users or groups') }}</span> </Multiselect> - </li> + </div> </template> <script> @@ -51,7 +53,7 @@ import { generateOcsUrl } from '@nextcloud/router' import { urldecode } from '../../../utils/url.js' export default { - name: 'CalendarListItemSharingSearch', + name: 'SharingSearch', components: { Multiselect, }, @@ -232,3 +234,18 @@ export default { }, } </script> + +<style lang="scss" scoped> +.sharing-search { + display: flex; + + &__select { + flex: 1 auto; + + // Fix weird height of multiselect + ::v-deep .multiselect__tags { + box-sizing: border-box; + } + } +} +</style> diff --git a/src/store/calendars.js b/src/store/calendars.js index bbb5f272..e0b06d9a 100644 --- a/src/store/calendars.js +++ b/src/store/calendars.js @@ -57,6 +57,7 @@ const state = { deletedCalendarObjects: [], calendarsById: {}, initialCalendarsLoaded: false, + editCalendarModal: undefined, } const mutations = { @@ -378,6 +379,16 @@ const mutations = { markCalendarAsNotLoading(state, { calendar }) { state.calendarsById[calendar.id].loading = false }, + + showEditCalendarModal(state, { calendarId }) { + state.editCalendarModal = { + calendarId, + } + }, + + hideEditCalendarModal(state) { + state.editCalendarModal = undefined + } } const getters = { @@ -555,6 +566,8 @@ const getters = { return true }) }, + + editCalendarModal: (state) => state.editCalendarModal, } const actions = { diff --git a/src/views/Calendar.vue b/src/views/Calendar.vue index 1805cde7..b7ff0734 100644 --- a/src/views/Calendar.vue +++ b/src/views/Calendar.vue @@ -33,6 +33,7 @@ :loading-calendars="loadingCalendars" /> <CalendarListNew v-if="!loadingCalendars && isAuthenticatedUser" :disabled="loadingCalendars" /> + <EditCalendarModal /> <!-- Appointment Configuration List --> <template v-if="isAuthenticatedUser"> @@ -73,6 +74,7 @@ import CalendarListNew from '../components/AppNavigation/CalendarList/CalendarLi import EmbedTopNavigation from '../components/AppNavigation/EmbedTopNavigation.vue' import EmptyCalendar from '../components/EmptyCalendar.vue' import CalendarGrid from '../components/CalendarGrid.vue' +import EditCalendarModal from '../components/AppNavigation/EditCalendarModal.vue' // Import CalDAV related methods import { @@ -118,6 +120,7 @@ export default { AppNavigationSpacer, CalendarListNew, Trashbin, + EditCalendarModal, }, data() { return { |