Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 89 additions & 1 deletion src/components/fields/ListOfMediaField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,29 @@
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<div class="media-list-field">
<div class="media-list-field"
:class="{
draggedOver: isDraggedOver,
}"
@dragover="onDragOver"
@dragenter="onDragEnter"
@dragleave="onDragLeave"
@drop="onDrop">
<div class="label-row">
<label class="field-label"
:title="field.description">
{{ field.name }}
</label>
<NcButton
variant="tertiary"
:title="t('assistant', 'Drop files here to upload them')">
<template #icon>
<InformationOutlineIcon />
</template>
</NcButton>
<strong v-show="isDraggedOver">
{{ t('assistant', 'Drop files here to upload them') }}
</strong>
</div>
<div v-if="!isOutput"
class="select-media">
Expand Down Expand Up @@ -91,6 +108,7 @@ import TrayArrowDownIcon from 'vue-material-design-icons/TrayArrowDown.vue'
import ShareVariantIcon from 'vue-material-design-icons/ShareVariant.vue'
import ContentSaveOutlineIcon from 'vue-material-design-icons/ContentSaveOutline.vue'
import TrashCanOutlineIcon from 'vue-material-design-icons/TrashCanOutline.vue'
import InformationOutlineIcon from 'vue-material-design-icons/InformationOutline.vue'

import NcButton from '@nextcloud/vue/components/NcButton'

Expand All @@ -104,6 +122,7 @@ import AudioRecorderWrapper from './AudioRecorderWrapper.vue'
import { generateOcsUrl, generateUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { uploadInputFile } from '../../utils.js'

import {
SHAPE_TYPE_NAMES,
Expand All @@ -123,6 +142,7 @@ export default {
TrayArrowDownIcon,
ShareVariantIcon,
ContentSaveOutlineIcon,
InformationOutlineIcon,
NcButton,
},

Expand Down Expand Up @@ -157,6 +177,7 @@ export default {
return {
isUploading: false,
isRecording: false,
isDraggedOver: false,
}
},

Expand Down Expand Up @@ -319,6 +340,66 @@ export default {
console.error(error)
})
},
onDragOver(e) {
const fileItems = [...e.dataTransfer.items].filter(
(item) => item.kind === 'file',
)
if (fileItems.length > 0) {
e.preventDefault()
e.stopPropagation()
this.isDraggedOver = true
}
},
onDragEnter(e) {
const fileItems = [...e.dataTransfer.items].filter(
(item) => item.kind === 'file',
)
if (fileItems.length > 0) {
e.preventDefault()
e.stopPropagation()
this.isDraggedOver = true
}
},
onDragLeave(e) {
const fileItems = [...e.dataTransfer.items].filter(
(item) => item.kind === 'file',
)
if (fileItems.length > 0) {
e.preventDefault()
e.stopPropagation()
this.isDraggedOver = false
}
},
onDrop(e) {
const fileItems = [...e.dataTransfer.items].filter(
(item) => item.kind === 'file',
)
if (fileItems.length > 0) {
e.preventDefault()
e.stopPropagation()
this.uploadDroppedFiles(fileItems.map(item => item.getAsFile()))
this.isDraggedOver = false
}
},
uploadDroppedFiles(files) {
this.isUploading = true

Promise.all(Array.from(files).map(f => uploadInputFile(f)))
.then(responses => {
if (responses.some(response => response.code === 'ERR_CANCELED')) {
console.debug('At least one request has been canceled, do nothing')
return
}
this.onFilesUploaded(responses.map(response => response.data.ocs.data))
})
.catch(error => {
showError(t('assistant', 'Could not upload the files'))
console.error(error)
})
.then(() => {
this.isUploading = false
})
},
},
}
</script>
Expand All @@ -330,12 +411,19 @@ export default {
align-items: center;
gap: 8px;

&.draggedOver {
border: solid 2px var(--color-border-success);
border-radius: var(--border-radius-large);
}

.label-row {
width: 100%;
height: var(--default-clickable-area);
display: flex;
flex-direction: row;
justify-content: start;
align-items: center;
gap: 4px;

.field-label {
font-weight: bold;
Expand Down
99 changes: 98 additions & 1 deletion src/components/fields/MediaField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,29 @@
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<div class="media-field">
<div class="media-field"
:class="{
draggedOver: isDraggedOver,
}"
@dragover="onDragOver"
@dragenter="onDragEnter"
@dragleave="onDragLeave"
@drop="onDrop">
<div class="label-row">
<label class="field-label"
:title="field.description">
{{ field.name }}
</label>
<NcButton v-if="value === null"
variant="tertiary"
:title="t('assistant', 'Drop a file here to upload it')">
<template #icon>
<InformationOutlineIcon />
</template>
</NcButton>
<strong v-show="value === null && isDraggedOver">
{{ t('assistant', 'Drop a file here to upload it') }}
</strong>
</div>
<div v-if="!isOutput"
class="select-media">
Expand Down Expand Up @@ -87,6 +104,7 @@ import CloseIcon from 'vue-material-design-icons/Close.vue'
import TrayArrowDownIcon from 'vue-material-design-icons/TrayArrowDown.vue'
import ShareVariantIcon from 'vue-material-design-icons/ShareVariant.vue'
import ContentSaveOutlineIcon from 'vue-material-design-icons/ContentSaveOutline.vue'
import InformationOutlineIcon from 'vue-material-design-icons/InformationOutline.vue'

import NcButton from '@nextcloud/vue/components/NcButton'

Expand All @@ -103,6 +121,7 @@ import { SHAPE_TYPE_NAMES, VALID_AUDIO_MIME_TYPES, VALID_IMAGE_MIME_TYPES, VALID
import { generateOcsUrl, generateUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { uploadInputFile } from '../../utils.js'

export default {
name: 'MediaField',
Expand All @@ -115,6 +134,7 @@ export default {
ShareVariantIcon,
CloseIcon,
ContentSaveOutlineIcon,
InformationOutlineIcon,
NcButton,
},

Expand Down Expand Up @@ -150,6 +170,7 @@ export default {
filePath: null,
isUploading: false,
isRecording: false,
isDraggedOver: false,
}
},

Expand Down Expand Up @@ -296,6 +317,75 @@ export default {
console.error(error)
})
},
onDragOver(e) {
if (this.value !== null) {
return
}
const fileItems = [...e.dataTransfer.items].filter(
(item) => item.kind === 'file',
)
if (fileItems.length === 1) {
e.preventDefault()
e.stopPropagation()
this.isDraggedOver = true
}
},
onDragEnter(e) {
if (this.value !== null) {
return
}
const fileItems = [...e.dataTransfer.items].filter(
(item) => item.kind === 'file',
)
if (fileItems.length === 1) {
e.preventDefault()
e.stopPropagation()
this.isDraggedOver = true
}
},
onDragLeave(e) {
console.debug('leave', e.target)
if (this.value !== null) {
return
}
const fileItems = [...e.dataTransfer.items].filter(
(item) => item.kind === 'file',
)
if (fileItems.length === 1) {
e.preventDefault()
e.stopPropagation()
this.isDraggedOver = false
}
},
onDrop(e) {
if (this.value !== null) {
return
}
const fileItems = [...e.dataTransfer.items].filter(
(item) => item.kind === 'file',
)
if (fileItems.length === 1) {
e.preventDefault()
e.stopPropagation()
this.uploadDroppedFile(fileItems[0].getAsFile())
this.isDraggedOver = false
}
},
uploadDroppedFile(file) {
this.isUploading = true

return uploadInputFile(file)
.then((response) => {
const data = response.data.ocs.data
this.onFileUploaded({ fileId: data.fileId, filePath: data.filePath })
})
.catch(error => {
console.error('error while uploading a file after drop', error)
})
.then(() => {
this.isUploading = false
})
},
},
}
</script>
Expand All @@ -307,11 +397,18 @@ export default {
align-items: start;
gap: 12px;

&.draggedOver {
border: solid 2px var(--color-border-success);
border-radius: var(--border-radius-large);
}

.label-row {
width: 100%;
height: var(--default-clickable-area);
display: flex;
align-items: center;
justify-content: start;
gap: 4px;

.field-label {
font-weight: bold;
Expand Down
15 changes: 3 additions & 12 deletions src/components/fields/UploadInputFileButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import NcButton from '@nextcloud/vue/components/NcButton'

import { showError } from '@nextcloud/dialogs'
import { generateOcsUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios'

const uploadEndpointUrl = generateOcsUrl('/apps/assistant/api/v1/input-file')
import { uploadInputFile } from '../../utils.js'

export default {
name: 'UploadInputFileButton',
Expand Down Expand Up @@ -104,7 +101,7 @@ export default {
if (!this.multiple) {
this.$emit('update:is-uploading', true)
const file = files[0]
this.uploadFile(file).then(response => {
uploadInputFile(file).then(response => {
this.$emit('files-uploaded', response.data.ocs.data)
}).catch(error => {
showError(t('assistant', 'Could not upload the file'))
Expand All @@ -114,7 +111,7 @@ export default {
})
} else {
this.$emit('update:is-uploading', true)
Promise.all(Array.from(files).map(f => this.uploadFile(f)))
Promise.all(Array.from(files).map(f => uploadInputFile(f)))
.then(responses => {
if (responses.some(response => response.code === 'ERR_CANCELED')) {
console.debug('At least one request has been canceled, do nothing')
Expand All @@ -130,12 +127,6 @@ export default {
})
}
},
uploadFile(file) {
const formData = new FormData()
formData.append('data', file)
formData.append('filename', file.name)
return axios.post(uploadEndpointUrl, formData)
},
},
}
</script>
Expand Down
12 changes: 12 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { generateOcsUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios'

const uploadEndpointUrl = generateOcsUrl('/apps/assistant/api/v1/input-file')

let mytimer = 0
export function delay(callback, ms = 0) {
clearTimeout(mytimer)
Expand All @@ -24,3 +29,10 @@ export function parseSpecialSymbols(text) {
.replace(/\r\n|\n|\r/gm, '\n') // remove line breaks
return text
}

export function uploadInputFile(file) {
const formData = new FormData()
formData.append('data', file)
formData.append('filename', file.name)
return axios.post(uploadEndpointUrl, formData)
}