mirror of https://github.com/nocodb/nocodb
braks
2 years ago
6 changed files with 412 additions and 207 deletions
@ -1,207 +0,0 @@ |
|||||||
<script setup lang="ts"> |
|
||||||
import { notification } from 'ant-design-vue' |
|
||||||
import { computed, inject, ref, useApi, useDropZone, useFileDialog, useProject, watch } from '#imports' |
|
||||||
import { ColumnInj, EditModeInj, MetaInj } from '~/context' |
|
||||||
import { NOCO } from '~/lib' |
|
||||||
import { isImage, openLink } from '~/utils' |
|
||||||
import MaterialSymbolsAttachFile from '~icons/material-symbols/attach-file' |
|
||||||
import MaterialArrowExpandIcon from '~icons/mdi/arrow-expand' |
|
||||||
import MaterialSymbolsFileCopyOutline from '~icons/material-symbols/file-copy-outline' |
|
||||||
import MdiReload from '~icons/mdi/reload' |
|
||||||
import IcOutlineInsertDriveFile from '~icons/ic/outline-insert-drive-file' |
|
||||||
import MdiPdfBox from '~icons/mdi/pdf-box' |
|
||||||
import MdiFileWordOutline from '~icons/mdi/file-word-outline' |
|
||||||
import MdiFilePowerpointBox from '~icons/mdi/file-powerpoint-box' |
|
||||||
import MdiFileExcelOutline from '~icons/mdi/file-excel-outline' |
|
||||||
|
|
||||||
interface Props { |
|
||||||
modelValue: string | Record<string, any>[] | null |
|
||||||
} |
|
||||||
|
|
||||||
interface Emits { |
|
||||||
(event: 'update:modelValue', value: string | Record<string, any>): void |
|
||||||
} |
|
||||||
|
|
||||||
const { modelValue } = defineProps<Props>() |
|
||||||
|
|
||||||
const emits = defineEmits<Emits>() |
|
||||||
|
|
||||||
const isPublicForm = inject('isPublicForm', false) |
|
||||||
|
|
||||||
const isForm = inject('isForm', false) |
|
||||||
|
|
||||||
// todo: replace placeholder var |
|
||||||
const isPublicGrid = $ref(false) |
|
||||||
|
|
||||||
const meta = inject(MetaInj)! |
|
||||||
|
|
||||||
const column = inject(ColumnInj)! |
|
||||||
|
|
||||||
const editEnabled = inject(EditModeInj, ref(false)) |
|
||||||
|
|
||||||
const storedFiles = ref<{ title: string; file: File }[]>([]) |
|
||||||
|
|
||||||
const attachments = ref<File[]>([]) |
|
||||||
|
|
||||||
const dropZoneRef = ref<HTMLDivElement>() |
|
||||||
|
|
||||||
const { api, isLoading } = useApi() |
|
||||||
|
|
||||||
const { project } = useProject() |
|
||||||
|
|
||||||
const { files, open, reset } = useFileDialog() |
|
||||||
|
|
||||||
const { isOverDropZone } = useDropZone(dropZoneRef, onDrop) |
|
||||||
|
|
||||||
watch( |
|
||||||
() => modelValue, |
|
||||||
(nextModel) => { |
|
||||||
if (nextModel) { |
|
||||||
attachments.value = ((typeof nextModel === 'string' ? JSON.parse(nextModel) : nextModel) || []).filter(Boolean) |
|
||||||
} |
|
||||||
}, |
|
||||||
{ immediate: true }, |
|
||||||
) |
|
||||||
|
|
||||||
function onDrop(droppedFiles: File[] | null) { |
|
||||||
if (droppedFiles) { |
|
||||||
// set files |
|
||||||
onFileSelection(droppedFiles) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const selectImage = (file: any, i: unknown) => { |
|
||||||
// todo: implement |
|
||||||
} |
|
||||||
|
|
||||||
async function onFileSelection(selectedFiles: FileList | File[]) { |
|
||||||
if (!selectedFiles.length || isPublicGrid) return |
|
||||||
|
|
||||||
if (isPublicForm) { |
|
||||||
storedFiles.value.push( |
|
||||||
...Array.from(selectedFiles).map((file) => { |
|
||||||
const res = { file, title: file.name } |
|
||||||
if (isImage(file.name, (file as any).mimetype)) { |
|
||||||
const reader = new FileReader() |
|
||||||
reader.readAsDataURL(file) |
|
||||||
} |
|
||||||
return res |
|
||||||
}), |
|
||||||
) |
|
||||||
|
|
||||||
emits( |
|
||||||
'update:modelValue', |
|
||||||
storedFiles.value.map((f) => f.file), |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
const newAttachments = [] |
|
||||||
|
|
||||||
for (const file of selectedFiles) { |
|
||||||
try { |
|
||||||
const data = await api.storage.upload( |
|
||||||
{ |
|
||||||
path: [NOCO, project.value.title, meta.value.title, column.title].join('/'), |
|
||||||
}, |
|
||||||
{ |
|
||||||
files: file, |
|
||||||
json: '{}', |
|
||||||
}, |
|
||||||
) |
|
||||||
|
|
||||||
newAttachments.push(...data) |
|
||||||
} catch (e: any) { |
|
||||||
notification.error({ |
|
||||||
message: e.message || 'Some internal error occurred', |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
emits('update:modelValue', JSON.stringify([...attachments.value, ...newAttachments])) |
|
||||||
} |
|
||||||
|
|
||||||
watch(files, (nextFiles) => nextFiles && onFileSelection(nextFiles)) |
|
||||||
|
|
||||||
const items = computed(() => (isPublicForm ? storedFiles.value : attachments.value) || []) |
|
||||||
|
|
||||||
const FileIcon = (icon: string) => { |
|
||||||
switch (icon) { |
|
||||||
case 'mdi-pdf-box': |
|
||||||
return MdiPdfBox |
|
||||||
case 'mdi-file-word-outline': |
|
||||||
return MdiFileWordOutline |
|
||||||
case 'mdi-file-powerpoint-box': |
|
||||||
return MdiFilePowerpointBox |
|
||||||
case 'mdi-file-excel-outline': |
|
||||||
return MdiFileExcelOutline |
|
||||||
default: |
|
||||||
return IcOutlineInsertDriveFile |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<div ref="dropZoneRef" class="flex-1 color-transition flex items-center justify-between gap-1"> |
|
||||||
<template v-if="isOverDropZone"> |
|
||||||
<div |
|
||||||
class="w-full h-full flex items-center justify-center p-1 rounded gap-1 bg-gradient-to-t from-primary/10 via-primary/25 to-primary/10 !text-primary" |
|
||||||
> |
|
||||||
<MaterialSymbolsFileCopyOutline class="text-pink-500" /> Drop here |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
<template v-else> |
|
||||||
<div |
|
||||||
:class="{ 'mx-auto px-4': !items.length }" |
|
||||||
class="group flex gap-1 items-center active:ring rounded border-1 p-1 hover:bg-primary/10" |
|
||||||
@click.stop="open" |
|
||||||
> |
|
||||||
<MdiReload v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" /> |
|
||||||
|
|
||||||
<a-tooltip v-else placement="bottom"> |
|
||||||
<template #title> Click or drop a file into cell </template> |
|
||||||
|
|
||||||
<div class="flex items-center gap-2"> |
|
||||||
<MaterialSymbolsAttachFile class="transform group-hover:(text-pink-500 scale-120)" /> |
|
||||||
|
|
||||||
<div v-if="!items.length" class="group-hover:text-primary">Add file(s)</div> |
|
||||||
</div> |
|
||||||
</a-tooltip> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="h-full w-full flex flex-wrap flex-col overflow-x-scroll overflow-y-hidden gap-2 scrollbar-thin-primary py-1"> |
|
||||||
<div |
|
||||||
v-for="(item, i) of items" |
|
||||||
:key="item.url || item.title" |
|
||||||
class="flex-auto flex items-center justify-center w-[45px] border-1" |
|
||||||
> |
|
||||||
<a-tooltip placement="bottom"> |
|
||||||
<template #title> |
|
||||||
<div class="text-center w-full">{{ item.title }}</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<img |
|
||||||
v-if="isImage(item.title, item.mimetype)" |
|
||||||
:alt="item.title || `#${i}`" |
|
||||||
:src="item.url || item.data" |
|
||||||
@click="selectImage(item.url || item.data, i)" |
|
||||||
/> |
|
||||||
|
|
||||||
<component :is="FileIcon(item.icon)" v-else-if="item.icon" @click="openLink(item.url || item.data)" /> |
|
||||||
|
|
||||||
<IcOutlineInsertDriveFile v-else @click.stop="openLink(item.url || item.data)" /> |
|
||||||
</a-tooltip> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div v-if="items.length" class="group flex gap-1 items-center active:ring rounded border-1 p-1 hover:bg-primary/10"> |
|
||||||
<MdiReload v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" /> |
|
||||||
|
|
||||||
<a-tooltip v-else placement="bottom"> |
|
||||||
<template #title> View attachments </template> |
|
||||||
|
|
||||||
<MaterialArrowExpandIcon class="transform group-hover:(text-pink-500 scale-120)" @click.stop /> |
|
||||||
</a-tooltip> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
</div> |
|
||||||
</template> |
|
@ -0,0 +1,131 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import { onKeyDown } from '@vueuse/core' |
||||||
|
import FileSaver from 'file-saver' |
||||||
|
import { useAttachmentCell } from './utils' |
||||||
|
import { ref, useUIPermission } from '#imports' |
||||||
|
import { isImage, openLink } from '~/utils' |
||||||
|
import MaterialSymbolsAttachFile from '~icons/material-symbols/attach-file' |
||||||
|
import MdiCloseCircle from '~icons/mdi/close-circle' |
||||||
|
import MdiDownload from '~icons/mdi/download' |
||||||
|
import IcOutlineInsertDriveFile from '~icons/ic/outline-insert-drive-file' |
||||||
|
|
||||||
|
const { isUIAllowed } = useUIPermission() |
||||||
|
|
||||||
|
const { open, isLoading, isPublicGrid, isForm, visibleItems, modalVisible, column, FileIcon, removeFile } = useAttachmentCell() |
||||||
|
|
||||||
|
// todo: replace placeholder var |
||||||
|
const isLocked = ref(false) |
||||||
|
|
||||||
|
onKeyDown('Escape', () => (modalVisible.value = false)) |
||||||
|
|
||||||
|
async function downloadFile(item: Record<string, any>) { |
||||||
|
FileSaver.saveAs(item.url || item.data, item.title) |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<a-modal v-model:visible="modalVisible" width="80%" :footer="null"> |
||||||
|
<template #title> |
||||||
|
<div class="flex gap-4"> |
||||||
|
<div |
||||||
|
v-if="(isForm || isUIAllowed('tableAttachment')) && !isPublicGrid && !isLocked" |
||||||
|
class="nc-attach-file group" |
||||||
|
@click="open" |
||||||
|
> |
||||||
|
<MaterialSymbolsAttachFile class="transform group-hover:(text-pink-500 scale-120)" /> |
||||||
|
Attach File |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="flex items-center gap-2"> |
||||||
|
Viewing Attachments of |
||||||
|
<div class="font-semibold underline">{{ column.title }}</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-6"> |
||||||
|
<div v-for="(item, i) of visibleItems" :key="`${item.title}-${i}`" class="flex flex-col gap-1"> |
||||||
|
<a-card class="nc-attachment-item group"> |
||||||
|
<a-tooltip> |
||||||
|
<template #title> Remove File </template> |
||||||
|
|
||||||
|
<MdiCloseCircle |
||||||
|
v-if="isUIAllowed('tableAttachment') && !isPublicGrid && !isLocked" |
||||||
|
class="nc-attachment-remove" |
||||||
|
@click.stop="removeFile(i)" |
||||||
|
/> |
||||||
|
</a-tooltip> |
||||||
|
|
||||||
|
<a-tooltip placement="bottom"> |
||||||
|
<template #title> Download file </template> |
||||||
|
|
||||||
|
<div class="nc-attachment-download"> |
||||||
|
<MdiDownload @click.stop="downloadFile(item)" /> |
||||||
|
</div> |
||||||
|
</a-tooltip> |
||||||
|
|
||||||
|
<div class="p-2 flex items-center cursor-pointer"> |
||||||
|
<img v-if="isImage(item.title, item.mimetype)" :alt="item.title || `#${i}`" :src="item.url || item.data" /> |
||||||
|
|
||||||
|
<component |
||||||
|
:is="FileIcon(item.icon)" |
||||||
|
v-else-if="item.icon" |
||||||
|
height="150" |
||||||
|
width="150" |
||||||
|
@click.stop="openLink(item.url || item.data)" |
||||||
|
/> |
||||||
|
|
||||||
|
<IcOutlineInsertDriveFile v-else height="150" width="150" @click.stop="openLink(item.url || item.data)" /> |
||||||
|
</div> |
||||||
|
</a-card> |
||||||
|
|
||||||
|
<div class="truncate" :title="item.title"> |
||||||
|
{{ item.title }} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</a-modal> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
.nc-attach-file { |
||||||
|
@apply select-none cursor-pointer color-transition flex items-center gap-1 border-1 p-2 rounded |
||||||
|
@apply hover:(bg-primary/10 text-primary ring); |
||||||
|
@apply active:(ring-pink-500 bg-primary/20); |
||||||
|
} |
||||||
|
|
||||||
|
.nc-attachment-item { |
||||||
|
@apply cursor-pointer !h-2/3 !min-h-[200px] flex items-center justify-center relative; |
||||||
|
|
||||||
|
&::after { |
||||||
|
@apply pointer-events-none rounded absolute top-0 left-0 right-0 bottom-0 transition-all duration-150 ease-in-out; |
||||||
|
content: ''; |
||||||
|
} |
||||||
|
|
||||||
|
&:hover::after { |
||||||
|
@apply ring shadow transform scale-103; |
||||||
|
} |
||||||
|
|
||||||
|
&:active::after { |
||||||
|
@apply ring ring-pink-500 shadow transform scale-103; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.nc-attachment-download { |
||||||
|
@apply absolute bottom-2 right-2; |
||||||
|
@apply transition-opacity duration-150 ease-in opacity-0 group-hover:(opacity-100) hover:ring; |
||||||
|
@apply cursor-pointer rounded shadow flex items-center p-1 border-1; |
||||||
|
@apply active:(ring border-0 ring-pink-500); |
||||||
|
} |
||||||
|
|
||||||
|
.nc-attachment-remove { |
||||||
|
@apply absolute top-2 right-2; |
||||||
|
@apply hover:(ring ring-red-500); |
||||||
|
@apply cursor-pointer rounded-full border-1; |
||||||
|
@apply active:(ring border-0 ring-red-500); |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.ant-card-body) { |
||||||
|
@apply !p-2; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,132 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import { useProvideAttachmentCell } from './utils' |
||||||
|
import Modal from './Modal.vue' |
||||||
|
import { ref, useDropZone, watch } from '#imports' |
||||||
|
import { isImage, openLink } from '~/utils' |
||||||
|
import MaterialSymbolsAttachFile from '~icons/material-symbols/attach-file' |
||||||
|
import MaterialArrowExpandIcon from '~icons/mdi/arrow-expand' |
||||||
|
import MaterialSymbolsFileCopyOutline from '~icons/material-symbols/file-copy-outline' |
||||||
|
import MdiReload from '~icons/mdi/reload' |
||||||
|
import IcOutlineInsertDriveFile from '~icons/ic/outline-insert-drive-file' |
||||||
|
|
||||||
|
interface Props { |
||||||
|
modelValue: string | Record<string, any>[] | null |
||||||
|
} |
||||||
|
|
||||||
|
interface Emits { |
||||||
|
(event: 'update:modelValue', value: string | Record<string, any>): void |
||||||
|
} |
||||||
|
|
||||||
|
const { modelValue } = defineProps<Props>() |
||||||
|
|
||||||
|
const emits = defineEmits<Emits>() |
||||||
|
|
||||||
|
const dropZoneRef = ref<HTMLDivElement>() |
||||||
|
|
||||||
|
const { modalVisible, attachments, visibleItems, onFileSelect, isLoading, open, FileIcon, fileRemovedHook, fileAddedHook } = |
||||||
|
useProvideAttachmentCell() |
||||||
|
|
||||||
|
const { isOverDropZone } = useDropZone(dropZoneRef, onDrop) |
||||||
|
|
||||||
|
watch( |
||||||
|
() => modelValue, |
||||||
|
(nextModel) => { |
||||||
|
if (nextModel) { |
||||||
|
attachments.value = ((typeof nextModel === 'string' ? JSON.parse(nextModel) : nextModel) || []).filter(Boolean) |
||||||
|
} |
||||||
|
}, |
||||||
|
{ immediate: true }, |
||||||
|
) |
||||||
|
|
||||||
|
fileRemovedHook.on((data) => { |
||||||
|
emits('update:modelValue', data) |
||||||
|
}) |
||||||
|
|
||||||
|
fileAddedHook.on((data) => { |
||||||
|
emits('update:modelValue', data) |
||||||
|
}) |
||||||
|
|
||||||
|
function onDrop(droppedFiles: File[] | null) { |
||||||
|
if (droppedFiles) { |
||||||
|
// set files |
||||||
|
onFileSelect(droppedFiles) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const selectImage = (file: any, i: unknown) => { |
||||||
|
// todo: implement |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div ref="dropZoneRef" class="flex-1 color-transition flex items-center justify-between gap-1"> |
||||||
|
<template v-if="isOverDropZone"> |
||||||
|
<div |
||||||
|
class="w-full h-full flex items-center justify-center p-1 rounded gap-1 bg-gradient-to-t from-primary/10 via-primary/25 to-primary/10 !text-primary" |
||||||
|
> |
||||||
|
<MaterialSymbolsFileCopyOutline class="text-pink-500" /> Drop here |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<template v-else> |
||||||
|
<div |
||||||
|
:class="{ 'mx-auto px-4': !visibleItems.length }" |
||||||
|
class="group flex gap-1 items-center active:ring rounded border-1 p-1 hover:bg-primary/10" |
||||||
|
@click.stop="open" |
||||||
|
> |
||||||
|
<MdiReload v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" /> |
||||||
|
|
||||||
|
<a-tooltip v-else placement="bottom"> |
||||||
|
<template #title> Click or drop a file into cell </template> |
||||||
|
|
||||||
|
<div class="flex items-center gap-2"> |
||||||
|
<MaterialSymbolsAttachFile class="transform group-hover:(text-pink-500 scale-120)" /> |
||||||
|
|
||||||
|
<div v-if="!visibleItems.length" class="group-hover:text-primary">Add file(s)</div> |
||||||
|
</div> |
||||||
|
</a-tooltip> |
||||||
|
</div> |
||||||
|
|
||||||
|
<template v-if="visibleItems.length"> |
||||||
|
<div |
||||||
|
class="h-full w-full flex flex-wrap flex-col gap-2 content-start py-1 overflow-x-scroll overflow-y-hidden scrollbar-thin-primary" |
||||||
|
> |
||||||
|
<div |
||||||
|
v-for="(item, i) of visibleItems" |
||||||
|
:key="item.url || item.title" |
||||||
|
class="flex-auto flex items-center justify-center w-[45px] border-1" |
||||||
|
> |
||||||
|
<a-tooltip placement="bottom"> |
||||||
|
<template #title> |
||||||
|
<div class="text-center w-full">{{ item.title }}</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<img |
||||||
|
v-if="isImage(item.title, item.mimetype)" |
||||||
|
:alt="item.title || `#${i}`" |
||||||
|
:src="item.url || item.data" |
||||||
|
@click="selectImage(item.url || item.data, i)" |
||||||
|
/> |
||||||
|
|
||||||
|
<component :is="FileIcon(item.icon)" v-else-if="item.icon" @click="openLink(item.url || item.data)" /> |
||||||
|
|
||||||
|
<IcOutlineInsertDriveFile v-else @click.stop="openLink(item.url || item.data)" /> |
||||||
|
</a-tooltip> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="group flex gap-1 items-center active:ring rounded border-1 p-1 hover:bg-primary/10"> |
||||||
|
<MdiReload v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" /> |
||||||
|
|
||||||
|
<a-tooltip v-else placement="bottom"> |
||||||
|
<template #title> View attachments </template> |
||||||
|
|
||||||
|
<MaterialArrowExpandIcon class="transform group-hover:(text-pink-500 scale-120)" @click.stop="modalVisible = true" /> |
||||||
|
</a-tooltip> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
</template> |
||||||
|
|
||||||
|
<Modal /> |
||||||
|
</div> |
||||||
|
</template> |
@ -0,0 +1,135 @@ |
|||||||
|
import { notification } from 'ant-design-vue' |
||||||
|
import { computed, createEventHook, inject, ref, useApi, useFileDialog, useInjectionState, useProject, watch } from '#imports' |
||||||
|
import { ColumnInj, EditModeInj, MetaInj } from '~/context' |
||||||
|
import { isImage } from '~/utils' |
||||||
|
import { NOCO } from '~/lib' |
||||||
|
import MdiPdfBox from '~icons/mdi/pdf-box' |
||||||
|
import MdiFileWordOutline from '~icons/mdi/file-word-outline' |
||||||
|
import MdiFilePowerpointBox from '~icons/mdi/file-powerpoint-box' |
||||||
|
import MdiFileExcelOutline from '~icons/mdi/file-excel-outline' |
||||||
|
import IcOutlineInsertDriveFile from '~icons/ic/outline-insert-drive-file' |
||||||
|
|
||||||
|
export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(() => { |
||||||
|
const isPublicForm = inject('isPublicForm', false) |
||||||
|
|
||||||
|
const isForm = inject('isForm', false) |
||||||
|
|
||||||
|
// todo: replace placeholder var
|
||||||
|
const isPublicGrid = $ref(false) |
||||||
|
|
||||||
|
const meta = inject(MetaInj)! |
||||||
|
|
||||||
|
const column = inject(ColumnInj)! |
||||||
|
|
||||||
|
const editEnabled = inject(EditModeInj, ref(false)) |
||||||
|
|
||||||
|
const storedFiles = ref<{ title: string; file: File }[]>([]) |
||||||
|
|
||||||
|
const attachments = ref<File[]>([]) |
||||||
|
|
||||||
|
const modalVisible = ref(false) |
||||||
|
|
||||||
|
const { project } = useProject() |
||||||
|
|
||||||
|
const { api, isLoading } = useApi() |
||||||
|
|
||||||
|
const { files, open } = useFileDialog() |
||||||
|
|
||||||
|
const fileRemovedHook = createEventHook<string | Record<string, any>[]>() |
||||||
|
|
||||||
|
const fileAddedHook = createEventHook<File[]>() |
||||||
|
|
||||||
|
function removeFile(i: number) { |
||||||
|
if (isPublicForm) { |
||||||
|
storedFiles.value.splice(i, 1) |
||||||
|
|
||||||
|
fileRemovedHook.trigger(storedFiles.value.map((storedFile) => storedFile.file)) |
||||||
|
} else { |
||||||
|
attachments.value.splice(i, 1) |
||||||
|
fileRemovedHook.trigger(attachments.value) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async function onFileSelect(selectedFiles: FileList | File[]) { |
||||||
|
if (!selectedFiles.length || isPublicGrid) return |
||||||
|
|
||||||
|
if (isPublicForm) { |
||||||
|
storedFiles.value.push( |
||||||
|
...Array.from(selectedFiles).map((file) => { |
||||||
|
const res = { file, title: file.name } |
||||||
|
if (isImage(file.name, (file as any).mimetype)) { |
||||||
|
const reader = new FileReader() |
||||||
|
reader.readAsDataURL(file) |
||||||
|
} |
||||||
|
return res |
||||||
|
}), |
||||||
|
) |
||||||
|
|
||||||
|
return fileAddedHook.trigger(storedFiles.value.map((storedFile) => storedFile.file)) |
||||||
|
} |
||||||
|
|
||||||
|
const newAttachments = [] |
||||||
|
|
||||||
|
for (const file of selectedFiles) { |
||||||
|
try { |
||||||
|
const data = await api.storage.upload( |
||||||
|
{ |
||||||
|
path: [NOCO, project.value.title, meta.value.title, column.title].join('/'), |
||||||
|
}, |
||||||
|
{ |
||||||
|
files: file, |
||||||
|
json: '{}', |
||||||
|
}, |
||||||
|
) |
||||||
|
|
||||||
|
newAttachments.push(...data) |
||||||
|
} catch (e: any) { |
||||||
|
notification.error({ |
||||||
|
message: e.message || 'Some internal error occurred', |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fileAddedHook.trigger([...attachments.value, ...newAttachments]) |
||||||
|
} |
||||||
|
|
||||||
|
const FileIcon = (icon: string) => { |
||||||
|
switch (icon) { |
||||||
|
case 'mdi-pdf-box': |
||||||
|
return MdiPdfBox |
||||||
|
case 'mdi-file-word-outline': |
||||||
|
return MdiFileWordOutline |
||||||
|
case 'mdi-file-powerpoint-box': |
||||||
|
return MdiFilePowerpointBox |
||||||
|
case 'mdi-file-excel-outline': |
||||||
|
return MdiFileExcelOutline |
||||||
|
default: |
||||||
|
return IcOutlineInsertDriveFile |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const visibleItems = computed(() => (isPublicForm ? storedFiles.value : attachments.value) || []) |
||||||
|
|
||||||
|
watch(files, (nextFiles) => nextFiles && onFileSelect(nextFiles)) |
||||||
|
|
||||||
|
return { |
||||||
|
attachments, |
||||||
|
storedFiles, |
||||||
|
visibleItems, |
||||||
|
isPublicForm, |
||||||
|
isForm, |
||||||
|
isPublicGrid, |
||||||
|
meta, |
||||||
|
column, |
||||||
|
editEnabled, |
||||||
|
isLoading, |
||||||
|
api, |
||||||
|
open, |
||||||
|
onFileSelect, |
||||||
|
modalVisible, |
||||||
|
FileIcon, |
||||||
|
fileRemovedHook, |
||||||
|
fileAddedHook, |
||||||
|
removeFile, |
||||||
|
} |
||||||
|
}, 'attachmentCell') |
Loading…
Reference in new issue