Files
nocodb/packages/nc-gui/components/nc/File/index.vue
mertmit 69a29568c7 chore: sync
Signed-off-by: mertmit <mertmit99@gmail.com>
2026-01-10 00:21:02 +03:00

178 lines
5.2 KiB
Vue

<script setup lang="ts">
import type { AttachmentReqType, PublicAttachmentScope } from 'nocodb-sdk'
import type { UploadFile } from 'ant-design-vue'
import { useProvideUploadState } from './UploadProviders/useUploadState'
interface Props {
disabled?: boolean
multiple?: boolean
accept?: string
maxSize?: number // in MB
enabledProviders?: ('local' | 'url' | 'webcam')[]
uploadPath?: string
uploadScope?: PublicAttachmentScope
}
interface Emits {
(e: 'upload', attachments: AttachmentReqType[]): void
}
const props = withDefaults(defineProps<Props>(), {
disabled: false,
multiple: false,
accept: '*',
maxSize: undefined,
enabledProviders: () => ['local', 'url', 'webcam'],
uploadPath: '',
uploadScope: undefined,
})
const emit = defineEmits<Emits>()
const { batchUploadFiles } = useAttachment()
const { base } = storeToRefs(useBase())
const showModal = ref(false)
const uploadedFiles = ref<UploadFile[]>([])
const handleRemove = (file: UploadFile) => {
uploadedFiles.value = uploadedFiles.value.filter((f) => f.uid !== file.uid)
}
const openModal = () => {
showModal.value = true
}
const closeModalFn = () => {
showModal.value = false
}
const handleModalUpload = async (files: File[]) => {
const uploadPath = props.uploadPath || [NOCO, base.value?.id].filter(Boolean).join('/')
const uploadResult = await batchUploadFiles([...files], uploadPath)
const newFiles = uploadResult.map((attachment, index) => ({
uid: `${Date.now()}-${attachment.title}`,
name: attachment.title || files[index].name,
status: 'done' as const,
size: attachment.size || files[index].size,
type: attachment.mimetype || files[index].type,
originFileObj: files[index],
url: attachment.url,
}))
if (props.multiple) {
uploadedFiles.value.push(...newFiles)
} else {
uploadedFiles.value = newFiles
}
// Emit uploaded attachments
emit('upload', uploadResult)
showModal.value = false
}
const handleAttachmentUpload = async (attachments: any[]) => {
const newFiles = attachments.map((attachment) => ({
uid: `${Date.now()}-${attachment.title}`,
name: attachment.title,
status: 'done' as const,
size: attachment.size,
type: attachment.mimetype,
url: attachment.url || attachment.path,
}))
if (props.multiple) {
uploadedFiles.value.push(...newFiles)
} else {
uploadedFiles.value = newFiles
}
// Emit uploaded attachments
emit('upload', attachments)
showModal.value = false
}
// Provide upload state to modal and all providers
const uploadPath = computed(() => props.uploadPath || [NOCO, base.value?.id].filter(Boolean).join('/'))
useProvideUploadState(handleModalUpload, handleAttachmentUpload, closeModalFn, uploadPath.value, props.uploadScope)
</script>
<template>
<div class="nc-file-upload flex flex-col gap-2">
<!-- Trigger slot - customizable button/trigger -->
<div>
<slot name="trigger" :open="openModal">
<NcButton size="small" :disabled="disabled" type="secondary" @click="openModal">
<div class="flex gap-2 items-center">
<GeneralIcon icon="upload" />
Click to Upload
</div>
</NcButton>
</slot>
</div>
<!-- File list display -->
<div v-if="uploadedFiles.length > 0" class="flex flex-col gap-2">
<slot name="file-list" :files="uploadedFiles" :remove="handleRemove" :format-bytes="formatBytes">
<!-- Default file list rendering -->
<div
v-for="file in uploadedFiles"
:key="file.uid"
class="border-1 border-nc-border-gray-medium bg-nc-bg-default flex items-center pl-1 py-2 pr-2 rounded-xl group"
>
<CellAttachmentIconView class="w-10 h-10" :item="{ title: file.name, mimetype: file.type }" />
<div class="flex flex-col flex-1 min-w-0 px-2">
<div class="text-caption text-nc-content-gray">
<NcTooltip class="!max-w-[200px] truncate" show-on-truncate-only>
{{ file.name }}
<template #title>
{{ file.name }}
</template>
</NcTooltip>
</div>
<div class="text-caption text-nc-content-gray-muted">
{{ formatBytes(file.size || 0) }}
</div>
</div>
<NcButton
v-if="!disabled"
type="text"
size="xsmall"
class="!opacity-0 group-hover:!opacity-100 transition-opacity"
@click="handleRemove(file)"
>
<GeneralIcon icon="delete" class="text-nc-content-red-medium" />
</NcButton>
</div>
</slot>
<!-- Add more button for multiple uploads -->
<div v-if="multiple" @click="openModal">
<slot name="add-more">
<NcButton size="small" :disabled="disabled" type="text">
<div class="flex gap-2 items-center">
<GeneralIcon icon="plus" />
Add More Files
</div>
</NcButton>
</slot>
</div>
</div>
<!-- Upload Modal -->
<NcFileUploadModal v-model:visible="showModal" :enabled-providers="enabledProviders" />
</div>
</template>
<style lang="scss" scoped>
.nc-file-upload {
:deep(.ant-upload) {
width: 100%;
}
}
</style>