|
|
|
@ -1,4 +1,4 @@
|
|
|
|
|
import type { AttachmentType } from 'nocodb-sdk' |
|
|
|
|
import type { AttachmentReqType, AttachmentType } from 'nocodb-sdk' |
|
|
|
|
import RenameFile from './RenameFile.vue' |
|
|
|
|
import { |
|
|
|
|
ColumnInj, |
|
|
|
@ -98,8 +98,8 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** save a file on select / drop, either locally (in-memory) or in the db */ |
|
|
|
|
async function onFileSelect(selectedFiles: FileList | File[]) { |
|
|
|
|
if (!selectedFiles.length) return |
|
|
|
|
async function onFileSelect(selectedFiles: FileList | File[], selectedFileUrls?: AttachmentReqType[]) { |
|
|
|
|
if (!selectedFiles.length && !selectedFileUrls?.length) return |
|
|
|
|
|
|
|
|
|
const attachmentMeta = { |
|
|
|
|
...defaultAttachmentMeta, |
|
|
|
@ -110,10 +110,15 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
|
|
|
|
|
|
|
|
|
|
const files: File[] = [] |
|
|
|
|
|
|
|
|
|
for (const file of selectedFiles) { |
|
|
|
|
const imageUrls: AttachmentReqType[] = [] |
|
|
|
|
|
|
|
|
|
for (const file of selectedFiles.length ? selectedFiles : selectedFileUrls || []) { |
|
|
|
|
if (appInfo.value.ee) { |
|
|
|
|
// verify number of files
|
|
|
|
|
if (visibleItems.value.length + selectedFiles.length > attachmentMeta.maxNumberOfAttachments) { |
|
|
|
|
if ( |
|
|
|
|
visibleItems.value.length + (selectedFiles.length || selectedFileUrls?.length || 0) > |
|
|
|
|
attachmentMeta.maxNumberOfAttachments |
|
|
|
|
) { |
|
|
|
|
message.error( |
|
|
|
|
`You can only upload at most ${attachmentMeta.maxNumberOfAttachments} file${ |
|
|
|
|
attachmentMeta.maxNumberOfAttachments > 1 ? 's' : '' |
|
|
|
@ -123,32 +128,53 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// verify file size
|
|
|
|
|
if (file.size > attachmentMeta.maxAttachmentSize * 1024 * 1024) { |
|
|
|
|
message.error(`The size of ${file.name} exceeds the maximum file size ${attachmentMeta.maxAttachmentSize} MB.`) |
|
|
|
|
if (file?.size && file.size > attachmentMeta.maxAttachmentSize * 1024 * 1024) { |
|
|
|
|
message.error( |
|
|
|
|
`The size of ${(file as File)?.name || (file as AttachmentReqType)?.fileName} exceeds the maximum file size ${ |
|
|
|
|
attachmentMeta.maxAttachmentSize |
|
|
|
|
} MB.`,
|
|
|
|
|
) |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// verify mime type
|
|
|
|
|
if ( |
|
|
|
|
!attachmentMeta.supportedAttachmentMimeTypes.includes('*') && |
|
|
|
|
!attachmentMeta.supportedAttachmentMimeTypes.includes(file.type) && |
|
|
|
|
!attachmentMeta.supportedAttachmentMimeTypes.includes(file.type.split('/')[0]) |
|
|
|
|
!attachmentMeta.supportedAttachmentMimeTypes.includes((file as File).type || (file as AttachmentReqType).mimetype) && |
|
|
|
|
!attachmentMeta.supportedAttachmentMimeTypes.includes( |
|
|
|
|
((file as File)?.type || (file as AttachmentReqType).mimetype)?.split('/')[0], |
|
|
|
|
) |
|
|
|
|
) { |
|
|
|
|
message.error(`${file.name} has the mime type ${file.type} which is not allowed in this column.`) |
|
|
|
|
message.error( |
|
|
|
|
`${(file as File)?.name || (file as AttachmentReqType)?.fileName} has the mime type ${ |
|
|
|
|
(file as File)?.type || (file as AttachmentReqType)?.mimetype |
|
|
|
|
} which is not allowed in this column.`,
|
|
|
|
|
) |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// this prevent file with same names
|
|
|
|
|
const isFileNameAlreadyExist = attachments.value.some((el) => el.title === file.name) |
|
|
|
|
if (isFileNameAlreadyExist) { |
|
|
|
|
message.error( |
|
|
|
|
t('labels.duplicateAttachment', { |
|
|
|
|
filename: file.name, |
|
|
|
|
}), |
|
|
|
|
) |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
if (selectedFiles.length) { |
|
|
|
|
// this prevent file with same names
|
|
|
|
|
const isFileNameAlreadyExist = attachments.value.some((el) => el.title === (file as File).name) |
|
|
|
|
if (isFileNameAlreadyExist) { |
|
|
|
|
message.error( |
|
|
|
|
t('labels.duplicateAttachment', { |
|
|
|
|
filename: (file as File).name, |
|
|
|
|
}), |
|
|
|
|
) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
files.push(file as File) |
|
|
|
|
} else { |
|
|
|
|
let fileName = `image.${(file as AttachmentReqType).mimetype?.split('/')[1]}` |
|
|
|
|
let count = 1 |
|
|
|
|
while (attachments.value.some((el) => el.title === fileName)) { |
|
|
|
|
fileName = fileName.replace(/(.+?)(\.[^.]+)$/, `$1(${count})$2`) |
|
|
|
|
count++ |
|
|
|
|
} |
|
|
|
|
imageUrls.push({ ...(file as AttachmentReqType), fileName }) |
|
|
|
|
} |
|
|
|
|
files.push(file) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (isPublic.value && isForm.value) { |
|
|
|
@ -188,18 +214,32 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
|
|
|
|
|
return updateModelValue(attachments.value) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
const data = await api.storage.upload( |
|
|
|
|
{ |
|
|
|
|
path: [NOCO, base.value.id, meta.value?.id, column.value?.id].join('/'), |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
files, |
|
|
|
|
}, |
|
|
|
|
) |
|
|
|
|
newAttachments.push(...data) |
|
|
|
|
} catch (e: any) { |
|
|
|
|
message.error(e.message || t('msg.error.internalError')) |
|
|
|
|
if (selectedFiles.length) { |
|
|
|
|
try { |
|
|
|
|
const data = await api.storage.upload( |
|
|
|
|
{ |
|
|
|
|
path: [NOCO, base.value.id, meta.value?.id, column.value?.id].join('/'), |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
files, |
|
|
|
|
}, |
|
|
|
|
) |
|
|
|
|
newAttachments.push(...data) |
|
|
|
|
} catch (e: any) { |
|
|
|
|
message.error(e.message || t('msg.error.internalError')) |
|
|
|
|
} |
|
|
|
|
} else if (imageUrls.length) { |
|
|
|
|
try { |
|
|
|
|
const data = await api.storage.uploadByUrl( |
|
|
|
|
{ |
|
|
|
|
path: [NOCO, base.value.id, meta.value?.id, column.value?.id].join('/'), |
|
|
|
|
}, |
|
|
|
|
imageUrls, |
|
|
|
|
) |
|
|
|
|
newAttachments.push(...data) |
|
|
|
|
} catch (e: any) { |
|
|
|
|
message.error(e.message || t('msg.error.internalError')) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
updateModelValue(JSON.stringify([...attachments.value, ...newAttachments])) |
|
|
|
@ -224,12 +264,20 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** save files on drop */ |
|
|
|
|
async function onDrop(droppedFiles: FileList | File[] | null) { |
|
|
|
|
async function onDrop(droppedFiles: FileList | File[] | null, event: DragEvent) { |
|
|
|
|
if (isReadonly.value) return |
|
|
|
|
|
|
|
|
|
if (droppedFiles) { |
|
|
|
|
// set files
|
|
|
|
|
await onFileSelect(droppedFiles) |
|
|
|
|
} else { |
|
|
|
|
event.preventDefault() |
|
|
|
|
|
|
|
|
|
const imageUrl = encodeURIComponent(event.dataTransfer?.getData('text/uri-list') ?? '') |
|
|
|
|
|
|
|
|
|
const imageData = await getImageDataFromUrl(imageUrl) |
|
|
|
|
if (imageData) { |
|
|
|
|
await onFileSelect([], [{ ...imageData, url: imageUrl, fileName: `image.${imageData.mimetype?.split('/')[1]}` }]) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -249,6 +297,21 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async function getImageDataFromUrl(imageUrl: string) { |
|
|
|
|
try { |
|
|
|
|
const response = await fetch(imageUrl) |
|
|
|
|
if (response.ok && response.headers.get('content-type')?.startsWith('image/')) { |
|
|
|
|
return { |
|
|
|
|
mimetype: response.headers.get('content-type') ?? undefined, |
|
|
|
|
size: +(response.headers.get('content-length') || 0) ?? undefined, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
throw Error('Field to parse image url') |
|
|
|
|
} catch (err) { |
|
|
|
|
console.log(err) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const FileIcon = (icon: string) => { |
|
|
|
|
switch (icon) { |
|
|
|
|
case 'mdi-pdf-box': |
|
|
|
|