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

185 lines
6.0 KiB
Vue

<script setup lang="ts">
import { useUploadState } from './useUploadState'
const { isMobileMode } = useGlobal()
const { tempFiles, addFiles, removeFile, clearFiles, closeModal, isLoading } = useUploadState()
const dropZoneRef = ref<HTMLDivElement>()
const thumbnails = ref(new Map<File, string>())
const onRemoveFileClick = (file: File) => {
const index = tempFiles.value.indexOf(file)
if (index > -1) {
removeFile(index)
thumbnails.value.delete(file)
}
}
const { files, open } = useFileDialog({
reset: true,
})
watch(files, (newFiles) => {
if (!newFiles) return
const fileArray = Object.values(newFiles)
addFiles(fileArray)
// Generate thumbnails for images
fileArray.forEach((file) => {
if (file.type.startsWith('image/')) {
const reader = new FileReader()
reader.onload = (e) => {
thumbnails.value.set(file, e.target?.result as string)
}
reader.readAsDataURL(file)
}
})
})
const onDrop = (files: File[], event: DragEvent) => {
addFiles(files)
files.forEach((file) => {
if (file.type.startsWith('image/')) {
const reader = new FileReader()
reader.onload = (e) => {
thumbnails.value.set(file, e.target?.result as string)
}
reader.readAsDataURL(file)
}
})
event.preventDefault()
event.stopPropagation()
}
const { isOverDropZone } = useDropZone(dropZoneRef, onDrop)
onUnmounted(() => {
thumbnails.value.clear()
})
</script>
<template>
<div
:class="{
'flex flex-col relative justify-center items-center': !tempFiles.length,
'h-full': tempFiles.length > 0,
}"
class="w-full p-2 h-full"
>
<NcTooltip v-if="tempFiles.length === 0" class="absolute top-3 right-3">
<NcButton type="text" class="!border-0" :disabled="isLoading" size="xsmall" @click="closeModal">
<GeneralIcon icon="close" />
</NcButton>
<template #title> {{ $t('general.close') }} </template>
</NcTooltip>
<div v-if="tempFiles.length > 0" class="flex w-full border-b-1 py-1 h-9.5 items-center justify-between top-0">
<NcButton :disabled="isLoading" type="text" size="small" @click="clearFiles">
{{ $t('labels.clearAllFiles') }}
</NcButton>
<span class="text-xs">
{{ tempFiles.length }} files, total size:
{{
formatBytes(
tempFiles.reduce((acc, file) => acc + file.size, 0),
0,
)
}}
</span>
<NcButton :disabled="isLoading" type="text" size="small" @click="open">
<div class="flex gap-1 items-center">
<component :is="iconMap.plus" />
{{ $t('labels.addMore') }}
</div>
</NcButton>
</div>
<div
ref="dropZoneRef"
:class="{
'border-nc-border-brand': isOverDropZone,
'border-dashed border-2': !tempFiles.length,
}"
data-testid="attachment-drop-zone"
:style="`height: ${tempFiles.length > 0 ? '324px' : '100%'}`"
class="flex flex-col items-center justify-center h-full w-full flex-grow-1 rounded-lg"
@click="tempFiles.length > 0 ? () => {} : open()"
>
<div v-if="!tempFiles.length" class="flex cursor-pointer items-center justify-center flex-col gap-4">
<template v-if="!isOverDropZone">
<component :is="iconMap.upload" class="w-8 h-8 text-nc-content-gray-muted" />
<span class="p-4">
{{ $t('labels.clickTo') }}
<span class="font-semibold text-nc-content-brand"> {{ $t('labels.browseFiles') }} </span>
{{ $t('general.or') }}
<span class="font-semibold"> {{ $t('labels.dragFilesHere') }} </span>
{{ $t('labels.toUpload') }}
</span>
</template>
<template v-if="isOverDropZone">
<component :is="iconMap.upload" class="w-8 text-nc-content-brand h-8" />
<h1 class="text-nc-content-brand font-bold">{{ $t('labels.dropHere') }}</h1>
</template>
</div>
<template v-else>
<div
class="grid overflow-y-auto flex-grow-1 nc-scrollbar-md grid-cols-4 w-full h-full items-start py-2 justify-center gap-4"
>
<div v-for="file in tempFiles" :key="file.name" class="flex gap-1.5 group max-w-34 pb-4 flex-col relative">
<div
v-if="!thumbnails.get(file)"
style="height: 140px"
class="flex items-center justify-center rounded-md bg-nc-bg-gray-light"
>
<GeneralIcon :icon="getAttachmentIcon(file.name, file.type)" :class="isMobileMode ? 'w-12 h-12' : 'w-16 h-16'" />
</div>
<img v-else :src="thumbnails.get(file)" style="height: 140px" alt="thumbnail" class="rounded-md object-cover" />
<div class="relative text-[12px] font-semibold items-center text-nc-content-gray flex">
<NcTooltip class="flex-auto truncate" placement="bottom">
<template #title> {{ file.name }} </template>
{{ file.name }}
</NcTooltip>
<div
v-if="!isLoading"
class="flex-none hide-ui transition-all transition-ease-in-out !h-4 flex items-center bg-nc-bg-default"
>
<NcTooltip placement="bottom">
<template #title> {{ $t('title.removeFile') }} </template>
<GeneralIcon
icon="delete"
class="!text-nc-content-red-medium w-3 h-3 cursor-pointer"
@click="onRemoveFileClick(file)"
/>
</NcTooltip>
</div>
</div>
<div class="flex-none text-[10px] font-semibold text-nc-content-gray-muted">
{{ formatBytes(file.size, 0) }}
</div>
</div>
</div>
</template>
</div>
<NcFileUploadProvidersBottomBar v-if="tempFiles.length" />
</div>
</template>
<style lang="scss">
.hide-ui {
@apply md:(h-0 w-0 overflow-hidden whitespace-nowrap);
.group:hover & {
@apply h-auto w-auto overflow-visible whitespace-normal;
}
}
</style>