Browse Source

Merge pull request #7491 from nocodb/nc-feat/copy-paste-attachment

feat: add attachments from clipboard
pull/7529/head
Raju Udava 9 months ago committed by GitHub
parent
commit
e88fcb0334
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 67
      packages/nc-gui/composables/useMultiSelect/convertCellData.ts
  2. 104
      packages/nc-gui/composables/useMultiSelect/index.ts
  3. 16
      packages/nocodb-sdk/src/lib/helperFunctions.ts

67
packages/nc-gui/composables/useMultiSelect/convertCellData.ts

@ -5,16 +5,16 @@ import type { AppInfo } from '~/composables/useGlobal'
import { parseProp } from '#imports'
export default function convertCellData(
args: { to: UITypes; value: string; column: ColumnType; appInfo: AppInfo },
args: { to: UITypes; value: string; column: ColumnType; appInfo: AppInfo; files?: FileList | File[]; oldValue?: unknown },
isMysql = false,
isMultiple = false,
) {
const { to, value, column } = args
const { to, value, column, files = [], oldValue } = args
const dateFormat = isMysql ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ'
// return null if value is empty
if (value === '') return null
if (value === '' && to !== UITypes.Attachment) return null
switch (to) {
case UITypes.SingleLineText:
@ -113,22 +113,36 @@ export default function convertCellData(
}
}
case UITypes.Attachment: {
let parsedVal
try {
parsedVal = parseProp(value)
parsedVal = Array.isArray(parsedVal) ? parsedVal : [parsedVal]
} catch (e) {
if (isMultiple) {
return null
} else {
throw new Error('Invalid attachment data')
}
}
const parsedOldValue = parseProp(oldValue)
const oldAttachments = parsedOldValue && Array.isArray(parsedOldValue) ? parsedOldValue : []
if (parsedVal.some((v: any) => v && !(v.url || v.data || v.path))) {
if (!value && !files.length) {
if (oldAttachments.length) return undefined
return null
}
let parsedVal = []
if (value) {
try {
parsedVal = parseProp(value)
parsedVal = Array.isArray(parsedVal)
? parsedVal
: typeof parsedVal === 'object' && Object.keys(parsedVal).length
? [parsedVal]
: []
} catch (e) {
if (isMultiple) {
return null
} else {
throw new Error('Invalid attachment data')
}
}
if (parsedVal.some((v: any) => v && !(v.url || v.data || v.path))) {
return null
}
}
// TODO(refactor): duplicate logic in attachment/utils.ts
const defaultAttachmentMeta = {
...(args.appInfo.ee && {
@ -147,7 +161,7 @@ export default function convertCellData(
const attachments = []
for (const attachment of parsedVal) {
for (const attachment of value ? parsedVal : files) {
if (args.appInfo.ee) {
// verify number of files
if (parsedVal.length > attachmentMeta.maxNumberOfAttachments) {
@ -164,7 +178,6 @@ export default function convertCellData(
message.error(`The size of ${attachment.name} exceeds the maximum file size ${attachmentMeta.maxAttachmentSize} MB.`)
continue
}
// verify mime type
if (
!attachmentMeta.supportedAttachmentMimeTypes.includes('*') &&
@ -175,11 +188,29 @@ export default function convertCellData(
continue
}
}
// this prevent file with same names
const isFileNameAlreadyExist = oldAttachments.some((el) => el.title === (attachment?.title || attachment?.name))
if (isFileNameAlreadyExist) {
if (isMultiple) {
message.error(`File with name ${attachment?.title || attachment?.name} already attached`)
continue
} else {
throw new Error(`File with name ${attachment?.title || attachment?.name} already attached`)
}
}
attachments.push(attachment)
}
return JSON.stringify(attachments)
if (oldAttachments.length && !attachments.length) {
return undefined
} else if (value && attachments.length) {
return JSON.stringify([...oldAttachments, ...attachments])
} else if (files.length && attachments.length) {
return attachments
} else {
return null
}
}
case UITypes.SingleSelect:
case UITypes.MultiSelect: {

104
packages/nc-gui/composables/useMultiSelect/index.ts

@ -20,6 +20,7 @@ import {
reactive,
ref,
unref,
useApi,
useBase,
useCopy,
useEventListener,
@ -61,6 +62,10 @@ export function useMultiSelect(
const { isMysql, isPg } = useBase()
const { base } = storeToRefs(useBase())
const { api } = useApi()
const editEnabled = ref(_editEnabled)
const isMouseDown = ref(false)
@ -772,7 +777,6 @@ export function useMultiSelect(
// Replace \" with " in clipboard data
const clipboardData = e.clipboardData?.getData('text/plain') || ''
try {
if (clipboardData?.includes('\n') || clipboardData?.includes('\t')) {
// if the clipboard data contains new line or tab, then it is a matrix or LongText
@ -819,6 +823,7 @@ export function useMultiSelect(
to: pasteCol.uidt as UITypes,
column: pasteCol,
appInfo: unref(appInfo),
oldValue: pasteCol.uidt === UITypes.Attachment ? pasteRow.row[pasteCol.title!] : undefined,
},
isMysql(meta.value?.source_id),
true,
@ -881,11 +886,20 @@ export function useMultiSelect(
to: columnObj.uidt as UITypes,
column: columnObj,
appInfo: unref(appInfo),
files: columnObj.uidt === UITypes.Attachment && e.clipboardData?.files?.length ? e.clipboardData?.files : undefined,
oldValue: rowObj.row[columnObj.title!],
},
isMysql(meta.value?.source_id),
)
if (pasteValue !== undefined) {
if (columnObj.uidt === UITypes.Attachment && e.clipboardData?.files?.length && pasteValue?.length) {
const uploadedFiles = await handleFileUpload(pasteValue, columnObj.id!)
rowObj.row[columnObj.title!] =
Array.isArray(uploadedFiles) && uploadedFiles.length
? JSON.stringify([...handleParseAttachmentCellData(rowObj.row[columnObj.title!]), ...uploadedFiles])
: null
} else if (pasteValue !== undefined) {
rowObj.row[columnObj.title!] = pasteValue
}
@ -903,29 +917,60 @@ export function useMultiSelect(
const rows = unref(data).slice(startRow, endRow + 1)
const props = []
let pasteValue
const files = e.clipboardData?.files
for (const row of rows) {
// TODO handle insert new row
if (!row || row.rowMeta.new) continue
for (const col of cols) {
if (!col.title) continue
if (!isPasteable(row, col)) {
if (!col.title || !isPasteable(row, col)) {
continue
}
props.push(col.title)
if (files?.length) {
if (col.uidt !== UITypes.Attachment) {
continue
}
if (pasteValue === undefined) {
const fileUploadPayload = convertCellData(
{
value: '',
to: col.uidt as UITypes,
column: col,
appInfo: unref(appInfo),
files,
oldValue: row.row[col.title],
},
isMysql(meta.value?.source_id),
true,
)
if (fileUploadPayload?.length) {
const uploadedFiles = await handleFileUpload(fileUploadPayload, col.id!)
pasteValue =
Array.isArray(uploadedFiles) && uploadedFiles.length
? JSON.stringify([...handleParseAttachmentCellData(row.row[col.title]), ...uploadedFiles])
: null
}
}
} else {
pasteValue = convertCellData(
{
value: clipboardData,
to: col.uidt as UITypes,
column: col,
appInfo: unref(appInfo),
oldValue: row.row[col.title],
},
isMysql(meta.value?.source_id),
true,
)
}
const pasteValue = convertCellData(
{
value: clipboardData,
to: col.uidt as UITypes,
column: col,
appInfo: unref(appInfo),
},
isMysql(meta.value?.source_id),
true,
)
props.push(col.title)
if (pasteValue !== undefined) {
row.row[col.title] = pasteValue
@ -933,6 +978,7 @@ export function useMultiSelect(
}
}
if (!props.length) return
await bulkUpdateRows?.(rows, props)
}
}
@ -957,6 +1003,32 @@ export function useMultiSelect(
event.preventDefault()
}
async function handleFileUpload(files: File[], columnId: string) {
try {
const data = await api.storage.upload(
{
path: [NOCO, base.value.id, meta.value?.id, columnId].join('/'),
},
{
files,
},
)
return data
} catch (e: any) {
message.error(e.message || t('msg.error.internalError'))
}
}
function handleParseAttachmentCellData(value: string | null) {
const parsedVal = parseProp(value)
if (parsedVal && Array.isArray(parsedVal)) {
return parsedVal
} else {
return []
}
}
useEventListener(document, 'keydown', handleKeyDown)
useEventListener(document, 'mouseup', handleMouseUp)
useEventListener(document, 'paste', handlePaste)

16
packages/nocodb-sdk/src/lib/helperFunctions.ts

@ -14,13 +14,15 @@ const getSystemColumnsIds = (columns) => {
const getSystemColumns = (columns) => columns.filter(isSystemColumn) || [];
const isSystemColumn = (col): boolean =>
col &&
(col.uidt === UITypes.ForeignKey ||
((col.column_name === 'created_at' || col.column_name === 'updated_at') &&
col.uidt === UITypes.DateTime) ||
(col.pk && (col.ai || col.cdf)) ||
(col.pk && col.meta && col.meta.ag) ||
col.system);
!!(
col &&
(col.uidt === UITypes.ForeignKey ||
((col.column_name === 'created_at' || col.column_name === 'updated_at') &&
col.uidt === UITypes.DateTime) ||
(col.pk && (col.ai || col.cdf)) ||
(col.pk && col.meta && col.meta.ag) ||
col.system)
);
const isSelfReferencingTableColumn = (col): boolean => {
return (

Loading…
Cancel
Save