diff options
author | skjnldsv <skjnldsv@protonmail.com> | 2024-06-20 14:02:53 +0200 |
---|---|---|
committer | John Molakvoæ <skjnldsv@users.noreply.github.com> | 2024-07-12 20:14:30 +0200 |
commit | 443c48aefb70983afefce48652880a1bcaad4d53 (patch) | |
tree | 791143357ce720357653a03489b81534d03f3699 /apps/files_sharing/src/components/NewFileRequestDialog.vue | |
parent | b6f635f6f6dd79b073107bde7f7e378e00ceaccf (diff) | |
download | nextcloud-server-443c48aefb70983afefce48652880a1bcaad4d53.tar.gz nextcloud-server-443c48aefb70983afefce48652880a1bcaad4d53.zip |
feat(files_sharing): add `new file request` option
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
Diffstat (limited to 'apps/files_sharing/src/components/NewFileRequestDialog.vue')
-rw-r--r-- | apps/files_sharing/src/components/NewFileRequestDialog.vue | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/apps/files_sharing/src/components/NewFileRequestDialog.vue b/apps/files_sharing/src/components/NewFileRequestDialog.vue new file mode 100644 index 00000000000..53b4408b263 --- /dev/null +++ b/apps/files_sharing/src/components/NewFileRequestDialog.vue @@ -0,0 +1,330 @@ +<!-- + - SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> + +<template> + <NcDialog can-close + class="file-request-dialog" + data-cy-file-request-dialog + :close-on-click-outside="false" + :name="currentStep !== STEP.LAST ? t('files_sharing', 'Create a file request') : t('files_sharing', 'File request created')" + size="normal" + @closing="onCancel"> + <!-- Header --> + <NcNoteCard v-show="currentStep === STEP.FIRST" type="info" class="file-request-dialog__header"> + <p id="file-request-dialog-description" class="file-request-dialog__description"> + {{ t('files_sharing', 'Collect files from others even if they don\'t have an account.') }} + {{ t('files_sharing', 'To ensure you can receive files, verify you have enough storage available.') }} + </p> + </NcNoteCard> + + <!-- Main form --> + <form ref="form" + class="file-request-dialog__form" + aria-labelledby="file-request-dialog-description" + data-cy-file-request-dialog-form + @submit.prevent.stop="onSubmit"> + <FileRequestIntro v-if="currentStep === STEP.FIRST" + :context="context" + :destination.sync="destination" + :disabled="loading" + :label.sync="label" + :note.sync="note" /> + + <FileRequestDatePassword v-else-if="currentStep === STEP.SECOND" + :deadline.sync="deadline" + :disabled="loading" + :password.sync="password" /> + + <FileRequestFinish v-else-if="share" + :emails="emails" + :share="share" + @add-email="email => emails.push(email)" + @remove-email="onRemoveEmail" /> + </form> + + <!-- Controls --> + <template #actions> + <!-- Cancel the creation --> + <NcButton :aria-label="t('files_sharing', 'Cancel')" + :disabled="loading" + :title="t('files_sharing', 'Cancel the file request creation')" + data-cy-file-request-dialog-controls="cancel" + type="tertiary" + @click="onCancel"> + {{ t('files_sharing', 'Cancel') }} + </NcButton> + + <!-- Align right --> + <span class="dialog__actions-separator" /> + + <!-- Back --> + <NcButton v-show="currentStep === STEP.SECOND" + :aria-label="t('files_sharing', 'Previous step')" + :disabled="loading" + data-cy-file-request-dialog-controls="back" + type="tertiary" + @click="currentStep = STEP.FIRST"> + {{ t('files_sharing', 'Previous') }} + </NcButton> + + <!-- Next --> + <NcButton v-if="currentStep !== STEP.LAST" + :aria-label="t('files_sharing', 'Continue')" + :disabled="loading" + data-cy-file-request-dialog-controls="next" + @click="onPageNext"> + <template #icon> + <NcLoadingIcon v-if="loading" /> + <IconNext v-else :size="20" /> + </template> + {{ continueButtonLabel }} + </NcButton> + + <!-- Finish --> + <NcButton v-else + :aria-label="t('files_sharing', 'Close the creation dialog')" + data-cy-file-request-dialog-controls="finish" + type="primary" + @click="$emit('close')"> + <template #icon> + <IconCheck :size="20" /> + </template> + {{ finishButtonLabel }} + </NcButton> + </template> + </NcDialog> +</template> + +<script lang="ts"> +import type { AxiosError } from 'axios' +import type { Folder, Node } from '@nextcloud/files' +import type { OCSResponse } from '@nextcloud/typings/ocs' +import type { PropType } from 'vue' + +import { defineComponent } from 'vue' +import { emit } from '@nextcloud/event-bus' +import { generateOcsUrl } from '@nextcloud/router' +import { showError } from '@nextcloud/dialogs' +import { translate, translatePlural } from '@nextcloud/l10n' +import { Type } from '@nextcloud/sharing' +import axios from '@nextcloud/axios' + +import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js' +import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' +import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js' + +import IconCheck from 'vue-material-design-icons/Check.vue' +import IconNext from 'vue-material-design-icons/ArrowRight.vue' + +import FileRequestDatePassword from './NewFileRequestDialog/FileRequestDatePassword.vue' +import FileRequestFinish from './NewFileRequestDialog/FileRequestFinish.vue' +import FileRequestIntro from './NewFileRequestDialog/FileRequestIntro.vue' +import Share from '../models/Share' +import logger from '../services/logger' + +enum STEP { + FIRST = 0, + SECOND = 1, + LAST = 2, +} + +export default defineComponent({ + name: 'NewFileRequestDialog', + + components: { + FileRequestDatePassword, + FileRequestFinish, + FileRequestIntro, + IconCheck, + IconNext, + NcButton, + NcDialog, + NcLoadingIcon, + NcNoteCard, + }, + + props: { + context: { + type: Object as PropType<Folder>, + required: true, + }, + content: { + type: Array as PropType<Node[]>, + required: true, + }, + }, + + setup() { + return { + n: translatePlural, + t: translate, + STEP, + } + }, + + data() { + return { + currentStep: STEP.FIRST, + loading: false, + + destination: this.context.path || '/', + label: '', + note: '', + + deadline: null as Date | null, + password: null as string | null, + + share: null as Share | null, + emails: [] as string[], + } + }, + + computed: { + continueButtonLabel() { + if (this.currentStep === STEP.LAST) { + return this.t('files_sharing', 'Close') + } + return this.t('files_sharing', 'Continue') + }, + + finishButtonLabel() { + if (this.emails.length === 0) { + return this.t('files_sharing', 'Close') + } + return this.n('files_sharing', 'Close and send email', 'Close and send {count} emails', this.emails.length, { count: this.emails.length }) + }, + }, + + methods: { + onPageNext() { + const form = this.$refs.form as HTMLFormElement + if (!form.checkValidity()) { + form.reportValidity() + } + + // custom destination validation + // cannot share root + if (this.destination === '/' || this.destination === '') { + const destinationInput = form.querySelector('input[name="destination"]') as HTMLInputElement + destinationInput?.setCustomValidity(this.t('files_sharing', 'Please select a folder, you cannot share the root directory.')) + form.reportValidity() + return + } + + if (this.currentStep === STEP.FIRST) { + this.currentStep = STEP.SECOND + return + } + + this.createShare() + }, + + onRemoveEmail(email: string) { + const index = this.emails.indexOf(email) + this.emails.splice(index, 1) + }, + + onCancel() { + this.$emit('close') + }, + + onSubmit() { + this.$emit('submit') + }, + + async createShare() { + this.loading = true + + const shareUrl = generateOcsUrl('apps/files_sharing/api/v1/shares') + // Format must be YYYY-MM-DD + const expireDate = this.deadline ? this.deadline.toISOString().split('T')[0] : undefined + try { + const request = await axios.post(shareUrl, { + path: this.destination, + shareType: Type.SHARE_TYPE_LINK, + publicUpload: 'true', + password: this.password || undefined, + expireDate, + label: this.label, + attributes: JSON.stringify({ is_file_request: true }) + }) + + // If not an ocs request + if (!request?.data?.ocs) { + throw request + } + + const share = new Share(request.data.ocs.data) + this.share = share + + logger.info('New file request created', { share }) + emit('files_sharing:share:created', { share }) + + // Move to the last page + this.currentStep = STEP.LAST + } catch (error) { + const errorMessage = (error as AxiosError<OCSResponse>)?.response?.data?.ocs?.meta?.message + showError( + errorMessage + ? this.t('files_sharing', 'Error creating the share: {errorMessage}', { errorMessage }) + : this.t('files_sharing', 'Error creating the share'), + ) + logger.error('Error while creating share', { error, errorMessage }) + throw error + } finally { + this.loading = false + } + }, + }, +}) +</script> + +<style scoped lang="scss"> +.file-request-dialog { + --margin: 36px; + --secondary-margin: 18px; + + &__header { + margin: 0 var(--margin); + } + + &__form { + position: relative; + overflow: auto; + padding: var(--secondary-margin) var(--margin); + // overlap header bottom padding + margin-top: calc(-1 * var(--secondary-margin)); + } + + :deep(fieldset) { + display: flex; + flex-direction: column; + width: 100%; + margin-top: var(--secondary-margin); + + :deep(legend) { + display: flex; + align-items: center; + width: 100%; + } + } + + :deep(.dialog__actions) { + width: auto; + margin-inline: 12px; + // align left and remove margin + margin-left: 0; + span.dialog__actions-separator { + margin-left: auto; + } + } + + :deep(.input-field__helper-text-message) { + // reduce helper text standing out + color: var(--color-text-maxcontrast); + } +} +</style> |