diff options
Diffstat (limited to 'app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue')
-rw-r--r-- | app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue b/app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue new file mode 100644 index 00000000000..b645758d891 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue @@ -0,0 +1,172 @@ +<script> +import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui'; +import { __ } from '~/locale'; +import { isValidImage } from './utils'; +import { VALID_DATA_TRANSFER_TYPE, VALID_IMAGE_FILE_MIMETYPE } from './constants'; + +export default { + components: { + GlIcon, + GlLink, + GlSprintf, + }, + props: { + displayAsCard: { + type: Boolean, + required: false, + default: false, + }, + enableDragBehavior: { + type: Boolean, + required: false, + default: false, + }, + dropToStartMessage: { + type: String, + required: false, + default: __('Drop your files to start your upload.'), + }, + isFileValid: { + type: Function, + required: false, + default: isValidImage, + }, + validFileMimetypes: { + type: Array, + required: false, + default: () => [VALID_IMAGE_FILE_MIMETYPE.mimetype], + }, + }, + data() { + return { + dragCounter: 0, + isDragDataValid: false, + }; + }, + computed: { + dragging() { + return this.dragCounter !== 0; + }, + iconStyles() { + return { + size: this.displayAsCard ? 24 : 16, + class: this.displayAsCard ? 'gl-mb-2' : 'gl-mr-3 gl-text-gray-500', + }; + }, + validMimeTypeString() { + return this.validFileMimetypes.join(); + }, + }, + methods: { + isValidUpload(files) { + return files.every(this.isFileValid); + }, + isValidDragDataType({ dataTransfer }) { + return Boolean(dataTransfer && dataTransfer.types.some(t => t === VALID_DATA_TRANSFER_TYPE)); + }, + ondrop({ dataTransfer = {} }) { + this.dragCounter = 0; + // User already had feedback when dropzone was active, so bail here + if (!this.isDragDataValid) { + return; + } + + const { files } = dataTransfer; + if (!this.isValidUpload(Array.from(files))) { + this.$emit('error'); + return; + } + + this.$emit('change', files); + }, + ondragenter(e) { + this.dragCounter += 1; + this.isDragDataValid = this.isValidDragDataType(e); + }, + ondragleave() { + this.dragCounter -= 1; + }, + openFileUpload() { + this.$refs.fileUpload.click(); + }, + onFileInputChange(e) { + this.$emit('change', e.target.files); + }, + }, +}; +</script> + +<template> + <div + class="gl-w-full gl-relative" + @dragstart.prevent.stop + @dragend.prevent.stop + @dragover.prevent.stop + @dragenter.prevent.stop="ondragenter" + @dragleave.prevent.stop="ondragleave" + @drop.prevent.stop="ondrop" + > + <slot> + <button + class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3" + @click="openFileUpload" + > + <div + :class="{ 'gl-flex-direction-column': displayAsCard }" + class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center" + data-testid="dropzone-area" + > + <gl-icon name="upload" :size="iconStyles.size" :class="iconStyles.class" /> + <p class="gl-mb-0"> + <slot name="upload-text" :openFileUpload="openFileUpload"> + <gl-sprintf :message="__('Drop or %{linkStart}upload%{linkEnd} files to attach')"> + <template #link="{ content }"> + <gl-link @click.stop="openFileUpload"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </slot> + </p> + </div> + </button> + + <input + ref="fileUpload" + type="file" + name="upload_file" + :accept="validFileMimetypes" + class="hide" + multiple + @change="onFileInputChange" + /> + </slot> + <transition name="upload-dropzone-fade"> + <div + v-show="dragging && !enableDragBehavior" + class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" + > + <div v-show="!isDragDataValid" class="mw-50 gl-text-center"> + <slot name="invalid-drag-data-slot"> + <h3 :class="{ 'gl-font-base gl-display-inline': !displayAsCard }"> + {{ __('Oh no!') }} + </h3> + <span>{{ + __( + 'You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.', + ) + }}</span> + </slot> + </div> + <div v-show="isDragDataValid" class="mw-50 gl-text-center"> + <slot name="valid-drag-data-slot"> + <h3 :class="{ 'gl-font-base gl-display-inline': !displayAsCard }"> + {{ __('Incoming!') }} + </h3> + <span>{{ dropToStartMessage }}</span> + </slot> + </div> + </div> + </transition> + </div> +</template> |