Browse Source

feat(nc-gui): drag and drop image from webpage

pull/7605/head
Ramesh Mane 9 months ago
parent
commit
58f78917fb
  1. 131
      packages/nc-gui/components/cell/attachment/utils.ts
  2. 6
      packages/nc-gui/utils/fileUtils.ts

131
packages/nc-gui/components/cell/attachment/utils.ts

@ -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':

6
packages/nc-gui/utils/fileUtils.ts

@ -4,7 +4,11 @@ const isImage = (name: string, mimetype?: string) => {
return imageExt.some((e) => name?.toLowerCase().endsWith(`.${e}`)) || mimetype?.startsWith('image/')
}
export { isImage, imageExt }
const isImageUrl = (url: string) => {
return imageExt.some((e) => url?.toLowerCase()?.endsWith(`.${e}`))
}
export { isImage, imageExt, isImageUrl }
// Ref : https://stackoverflow.com/a/12002275
// Tested in Mozilla Firefox browser, Chrome

Loading…
Cancel
Save