-
- -
-
- {{ attachment.displayName ? attachment.displayName : attachment.fileName }}
-
-
-
- -
-
-
{{ uploading ? t('mail', 'Uploading {percent}% …', {percent: uploadProgress}) : '' }}
-
+
+
+ {{ n('mail', '{count} attachment', '{count} attachments', attachments.length, { count: attachments.length }) }} ({{ formatBytes(totalSizeOfUpload()) }})
+
+
+
+
+
{
+ const top = attachment.$el.getBoundingClientRect().top
+ if (prevTop !== null && prevTop !== top) {
+ if (!this.hasNextLine) {
+ this.isToggle = true
+ this.hasNextLine = true
+ }
+ return true
+ } else {
+ prevTop = top
+ if (this.$refs.attachments.length === i + 1) {
+ this.hasNextLine = false
+ this.isToggle = false
+ return true
+ }
+ }
+ return false
+ })
+ })
+ },
},
created() {
this.bus.$on('onAddLocalAttachment', this.onAddLocalAttachment)
this.bus.$on('onAddCloudAttachment', this.onAddCloudAttachment)
this.bus.$on('onAddCloudAttachmentLink', this.onAddCloudAttachmentLink)
+ this.value.map(attachment => {
+ this.attachments.push({
+ id: attachment.id,
+ fileName: attachment.fileName,
+ displayName: trimStart('/', attachment.fileName),
+ total: attachment.size,
+ finished: true,
+ sizeString: this.formatBytes(attachment.size),
+ imageBlobURL: attachment.isImage ? attachment.downloadUrl : attachment.mimeUrl,
+ })
+ return attachment
+ })
},
methods: {
onAddLocalAttachment() {
@@ -118,7 +188,7 @@ export default {
},
onLocalAttachmentSelected(e) {
this.uploading = true
-
+ // BUG - if choose again - progress lost/ move to complete()
Vue.set(this, 'uploads', {})
const toUpload = sumBy(prop('size'), Object.values(e.target.files))
@@ -131,32 +201,75 @@ export default {
})
if (this.uploadSizeLimit && newTotal > this.uploadSizeLimit) {
this.showAttachmentFileSizeWarning(e.target.files.length)
-
this.uploading = false
return
}
const progress = (id) => (prog, uploaded) => {
this.uploads[id].uploaded = uploaded
- }
+ this.attachments.map((item, i) => {
+ if (item.displayName === id) {
+ this.attachments[i].progress = uploaded
+ this.changeProgress(item, uploaded)
+ }
+ return item
+ })
+ }
+ // TODO bug: cancel axios on close or delete attachment
const promises = map((file) => {
- Vue.set(this.uploads, file.name, {
+ const controller = new AbortController()
+ this.attachments.push({
+ fileName: file.name,
+ fileType: file.type,
+ imageBlobURL: this.generatePreview(file),
+ displayName: trimStart('/', file.name),
+ progress: null,
+ percent: 0,
total: file.size,
- uploaded: 0,
+ finished: false,
+ error: false,
+ hasPreview: false,
+ controller,
})
- return uploadLocalAttachment(file, progress(file.name)).then(({ file, id }) => {
- logger.info('uploaded')
- this.emitNewAttachments([{
- fileName: file.name,
- displayName: trimStart('/', file.name),
- id,
- size: file.size,
- type: 'local',
- }])
+ Vue.set(this.uploads, file.name, {
+ total: file.size,
+ uploaded: 0,
})
+ try {
+ return uploadLocalAttachment(file, progress(file.name), controller)
+ .catch(() => {
+ this.attachments.some(attachment => {
+ if (attachment.displayName === file.name && !attachment.error) {
+ this.$set(attachment, 'error', true)
+ return true
+ }
+ return false
+ })
+ })
+ .then(({ file, id }) => {
+ logger.info('uploaded')
+ this.attachments.some((attachment, i) => {
+ if (attachment.displayName === file.name) {
+ this.attachments[i].id = id
+ this.attachments[i].finished = true
+ return true
+ }
+ return false
+ })
+
+ this.emitNewAttachments([{
+ fileName: file.name,
+ displayName: trimStart('/', file.name),
+ id,
+ size: file.size,
+ type: 'local',
+ }])
+ })
+ } catch (error) {
+ }
}, e.target.files)
const done = Promise.all(promises)
@@ -172,8 +285,14 @@ export default {
try {
const paths = await picker.pick(t('mail', 'Choose a file to add as attachment'))
- const fileSizes = await Promise.all(paths.map(getFileSize))
- const newTotal = sum(fileSizes) + this.totalSizeOfUpload()
+ // maybe fiiled front with placeholder loader...?
+ const filesFromCloud = await Promise.all(paths.map(getFileData))
+
+ const sum = filesFromCloud.reduce((sum, item) => {
+ return sum + item.size
+ }, 0)
+
+ const newTotal = sum + this.totalSizeOfUpload()
if (this.uploadSizeLimit && newTotal > this.uploadSizeLimit) {
this.showAttachmentFileSizeWarning(paths.length)
@@ -181,12 +300,28 @@ export default {
return
}
- this.emitNewAttachments(paths.map(function(name) {
- return {
+ this.emitNewAttachments(paths.map((name, i) => {
+ const _cloudFile = {
fileName: name,
displayName: trimStart('/', name),
type: 'cloud',
+ size: filesFromCloud[i].size,
+
+ }
+ const _toAttachmentData = {
+ finished: true,
+ imageBlobURL: this.generatePreview(_cloudFile),
+ total: filesFromCloud[i].size,
+ sizeString: this.formatBytes(filesFromCloud[i].size),
+ hasPreview: filesFromCloud[i]['has-preview'],
+ // dont know, may be it will be conflict if cloud & local has equal IDs?
+ id: filesFromCloud[i].fileid,
+ uploaded: 0,
}
+
+ this.attachments.push(Object.assign(_toAttachmentData, _cloudFile))
+
+ return _cloudFile
}))
} catch (error) {
logger.error('could not choose a file as attachment', { error })
@@ -216,14 +351,70 @@ export default {
))
},
onDelete(attachment) {
+ if (!attachment.finished) {
+ attachment.controller.abort()
+ }
+ const val = {
+ fileName: attachment.fileName,
+ displayName: attachment.displayName,
+ id: attachment.id,
+ size: attachment.total,
+ type: attachment.type,
+ }
+ const _att = this.attachments.filter((a) => {
+ return a !== attachment
+ })
+ this.attachments = _att
+
this.$emit(
'input',
- this.value.filter((a) => a !== attachment)
+ this.value.filter((a) => {
+ if (val.type === 'cloud') {
+ return a.fileName !== val.fileName
+ } else {
+ return a.id !== val.id
+ }
+
+ })
)
},
appendToBodyAtCursor(toAppend) {
this.bus.$emit('appendToBodyAtCursor', toAppend)
},
+ formatBytes(bytes, decimals = 2) {
+ if (bytes === 0) return '0 B'
+ const k = 1024
+ const dm = decimals < 0 ? 0 : decimals
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
+ },
+ changeProgress(item, progress) {
+ this.attachments.map((attachment, i) => {
+ if (item.fileName === attachment.fileName) {
+ if (!attachment.finished) {
+ const _progress = progress <= attachment.total ? progress : attachment.total
+ this.$set(attachment, 'progress', _progress)
+ this.$set(attachment, 'sizeString', this.formatBytes(_progress))
+ this.$set(attachment, 'percent', (_progress / attachment.total) * 100).toFixed(1)
+ if (item.total <= _progress) {
+ this.$set(attachment, 'finished', true)
+ }
+ }
+ }
+ return attachment
+ })
+ },
+ generatePreview(file) {
+ if (this.isImage(file)) {
+ return URL.createObjectURL(file)
+ } else {
+ return false
+ }
+ },
+ isImage(file) {
+ return file.type && mimes.indexOf(file.type) !== -1
+ },
},
}
@@ -231,51 +422,32 @@ export default {
diff --git a/src/service/AttachmentService.js b/src/service/AttachmentService.js
index 661919a6e7..7f6ed1a9ee 100644
--- a/src/service/AttachmentService.js
+++ b/src/service/AttachmentService.js
@@ -45,12 +45,15 @@ export function downloadAttachment(url) {
return Axios.get(url).then((res) => res.data)
}
-export const uploadLocalAttachment = (file, progress) => {
+export const uploadLocalAttachment = (file, progress, controller) => {
const url = generateUrl('/apps/mail/api/attachments')
const data = new FormData()
const opts = {
onUploadProgress: (prog) => progress(prog, prog.loaded, prog.total),
}
+ if (controller) {
+ opts.signal = controller.signal
+ }
data.append('attachment', file)
return Axios.post(url, data, opts)
diff --git a/src/service/FileService.js b/src/service/FileService.js
index 0acf2ab3dd..d646c9b7bc 100644
--- a/src/service/FileService.js
+++ b/src/service/FileService.js
@@ -35,3 +35,22 @@ export async function getFileSize(path) {
return response?.data?.props?.size
}
+
+export async function getFileData(path) {
+ const response = await getClient('files').stat(path, {
+ data: `
+
+
+
+
+
+
+ `,
+ details: true,
+ })
+
+ return response?.data?.props
+}