mirror of
https://gitlab.com/bramw/baserow.git
synced 2024-11-25 00:46:46 +00:00
259 lines
7.7 KiB
Vue
259 lines
7.7 KiB
Vue
<template>
|
|
<div
|
|
ref="cell"
|
|
class="grid-view__cell grid-field-file__cell"
|
|
:class="{ active: selected }"
|
|
@drop.prevent="onDrop($event)"
|
|
@dragover.prevent
|
|
@dragenter.prevent="dragEnter($event)"
|
|
@dragleave="dragLeave($event)"
|
|
>
|
|
<div v-show="dragging" class="grid-field-file__dragging">
|
|
<div>
|
|
<i class="grid-field-file__drop-icon iconoir-cloud-upload"></i>
|
|
{{ $t('gridViewFieldFile.dropHere') }}
|
|
</div>
|
|
</div>
|
|
<ul v-if="Array.isArray(value)" class="grid-field-file__list">
|
|
<li
|
|
v-for="(file, index) in value"
|
|
:key="file.name + index"
|
|
class="grid-field-file__item"
|
|
>
|
|
<a
|
|
v-tooltip="file.visible_name"
|
|
class="grid-field-file__link"
|
|
@click.prevent="showFileModal(index)"
|
|
>
|
|
<img
|
|
v-if="file.is_image"
|
|
class="grid-field-file__image"
|
|
:src="file.thumbnails.tiny.url"
|
|
/>
|
|
<i
|
|
v-else
|
|
class="grid-field-file__icon"
|
|
:class="getIconClass(file.mime_type)"
|
|
></i>
|
|
</a>
|
|
</li>
|
|
<li
|
|
v-for="loading in loadings"
|
|
:key="loading.id"
|
|
class="grid-field-file__item"
|
|
>
|
|
<div class="grid-field-file__loading"></div>
|
|
</li>
|
|
<li v-if="!readOnly" v-show="selected" class="grid-field-file__item">
|
|
<a class="grid-field-file__item-add" @click.prevent="showUploadModal()">
|
|
<i class="iconoir-plus"></i>
|
|
</a>
|
|
<div v-if="value.length == 0" class="grid-field-file__drop">
|
|
<i class="grid-field-file__drop-icon iconoir-cloud-upload"></i>
|
|
{{ $t('gridViewFieldFile.dropFileHere') }}
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
<UserFilesModal
|
|
v-if="Array.isArray(value) && !readOnly"
|
|
ref="uploadModal"
|
|
@uploaded="addFiles(value, $event)"
|
|
@hidden="hideModal"
|
|
></UserFilesModal>
|
|
<FileFieldModal
|
|
v-if="Array.isArray(value)"
|
|
ref="fileModal"
|
|
:files="value"
|
|
:read-only="readOnly"
|
|
@hidden="hideModal"
|
|
@removed="removeFile(value, $event)"
|
|
@renamed="renameFile(value, $event.index, $event.value)"
|
|
></FileFieldModal>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { uuid } from '@baserow/modules/core/utils/string'
|
|
import { isElement } from '@baserow/modules/core/utils/dom'
|
|
import { notifyIf } from '@baserow/modules/core/utils/error'
|
|
import UserFilesModal from '@baserow/modules/core/components/files/UserFilesModal'
|
|
import { UploadFileUserFileUploadType } from '@baserow/modules/core/userFileUploadTypes'
|
|
import UserFileService from '@baserow/modules/core/services/userFile'
|
|
import FileFieldModal from '@baserow/modules/database/components/field/FileFieldModal'
|
|
import gridField from '@baserow/modules/database/mixins/gridField'
|
|
import fileField from '@baserow/modules/database/mixins/fileField'
|
|
|
|
export default {
|
|
name: 'GridViewFieldFile',
|
|
components: { UserFilesModal, FileFieldModal },
|
|
mixins: [gridField, fileField],
|
|
data() {
|
|
return {
|
|
modalOpen: false,
|
|
dragging: false,
|
|
loadings: [],
|
|
dragTarget: null,
|
|
}
|
|
},
|
|
methods: {
|
|
/**
|
|
* Method is called when the user drops his files into the field. The files should
|
|
* automatically be uploaded to the user files and added to the field after that.
|
|
*/
|
|
async onDrop(event) {
|
|
const files = [...event.dataTransfer.items].map((item) =>
|
|
item.getAsFile()
|
|
)
|
|
await this.uploadFiles(files)
|
|
},
|
|
async uploadFiles(fileArray) {
|
|
if (this.readOnly) {
|
|
return
|
|
}
|
|
|
|
this.dragging = false
|
|
|
|
// Indicates that this component must not be destroyed even though the user might
|
|
// select another cell.
|
|
this.$emit('add-keep-alive')
|
|
|
|
const files = fileArray.map((file) => {
|
|
return {
|
|
id: uuid(),
|
|
file,
|
|
}
|
|
})
|
|
|
|
if (files === null) {
|
|
return
|
|
}
|
|
|
|
this.$emit('select')
|
|
|
|
// First add the file ids to the loading list so the user sees a visual loading
|
|
// indication for each file.
|
|
files.forEach((file) => {
|
|
this.loadings.push({ id: file.id })
|
|
})
|
|
|
|
// Now upload the files one by one to not overload the backend. When finished,
|
|
// regardless of is has succeeded, the loading state for that file can be removed
|
|
// because it has already been added as a file.
|
|
for (let i = 0; i < files.length; i++) {
|
|
const id = files[i].id
|
|
const file = files[i].file
|
|
|
|
try {
|
|
const { data } = await UserFileService(this.$client).uploadFile(file)
|
|
this.addFiles(this.value, [data])
|
|
} catch (error) {
|
|
notifyIf(error, 'userFile')
|
|
}
|
|
|
|
const index = this.loadings.findIndex((l) => l.id === id)
|
|
this.loadings.splice(index, 1)
|
|
}
|
|
|
|
// Indicates that this component can be destroyed if it is not selected.
|
|
this.$emit('remove-keep-alive')
|
|
},
|
|
select() {
|
|
// While the field is selected we want to open the select row toast by pressing
|
|
// the enter key.
|
|
this.$el.keydownEvent = (event) => {
|
|
if (event.key === 'Enter' && !this.modalOpen) {
|
|
this.showUploadModal()
|
|
}
|
|
}
|
|
document.body.addEventListener('keydown', this.$el.keydownEvent)
|
|
},
|
|
beforeUnSelect() {
|
|
document.body.removeEventListener('keydown', this.$el.keydownEvent)
|
|
},
|
|
/**
|
|
* If the user clicks inside the select row modal we do not want to unselect the
|
|
* field. The modal lives in the root of the body element and not inside the cell,
|
|
* so the system naturally wants to unselect when the user clicks inside one of
|
|
* these contexts.
|
|
*/
|
|
canUnselectByClickingOutside(event) {
|
|
return (
|
|
(!this.$refs.uploadModal ||
|
|
!isElement(this.$refs.uploadModal.$el, event.target)) &&
|
|
!isElement(this.$refs.fileModal.$el, event.target)
|
|
)
|
|
},
|
|
/**
|
|
* Prevent unselecting the field cell by changing the event. Because the deleted
|
|
* item is not going to be part of the dom anymore after deleting it will get
|
|
* noticed as if the user clicked outside the cell which wasn't the case.
|
|
*/
|
|
removeFile(event, index) {
|
|
event.preventFieldCellUnselect = true
|
|
return fileField.methods.removeFile.call(this, event, index)
|
|
},
|
|
showUploadModal() {
|
|
if (this.readOnly) {
|
|
return
|
|
}
|
|
|
|
this.modalOpen = true
|
|
this.$refs.uploadModal.show(UploadFileUserFileUploadType.getType())
|
|
},
|
|
showFileModal(index) {
|
|
this.modalOpen = true
|
|
this.$refs.fileModal.show(index)
|
|
},
|
|
hideModal() {
|
|
this.modalOpen = false
|
|
},
|
|
/**
|
|
* While the modal is open, all key combinations related to the field must be
|
|
* ignored.
|
|
*/
|
|
canSelectNext() {
|
|
return !this.modalOpen
|
|
},
|
|
canKeyDown() {
|
|
return !this.modalOpen
|
|
},
|
|
canKeyboardShortcut() {
|
|
return !this.modalOpen
|
|
},
|
|
dragEnter(event) {
|
|
if (this.readOnly) {
|
|
return
|
|
}
|
|
|
|
this.dragging = true
|
|
this.dragTarget = event.target
|
|
},
|
|
dragLeave(event) {
|
|
if (this.dragTarget === event.target && !this.readOnly) {
|
|
event.stopPropagation()
|
|
event.preventDefault()
|
|
this.dragging = false
|
|
this.dragTarget = null
|
|
}
|
|
},
|
|
onPaste(event) {
|
|
if (
|
|
!event.clipboardData.types.includes('text/plain') ||
|
|
event.clipboardData.getData('text/plain').startsWith('file:///')
|
|
) {
|
|
const { items } = event.clipboardData
|
|
for (let i = 0; i < items.length; i++) {
|
|
const item = items[i]
|
|
if (item.type.includes('image')) {
|
|
const file = item.getAsFile()
|
|
this.uploadFiles([file])
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
},
|
|
},
|
|
}
|
|
</script>
|