add img preview + fix navbar
This commit is contained in:
@@ -214,13 +214,21 @@
|
||||
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
|
||||
>
|
||||
<div class="flex items-center gap-3 text-sm">
|
||||
<span class="text-xl" :class="documentIcon(document).colorClass">
|
||||
<div class="h-14 w-14 flex-shrink-0 overflow-hidden rounded-md border border-base-200 bg-base-200/70 flex items-center justify-center">
|
||||
<img
|
||||
v-if="isImageDocument(document) && document.path"
|
||||
:src="document.path"
|
||||
class="h-full w-full object-cover"
|
||||
:alt="`Aperçu de ${document.name}`"
|
||||
>
|
||||
<component
|
||||
v-else
|
||||
:is="documentIcon(document).component"
|
||||
class="h-6 w-6"
|
||||
:class="documentIcon(document).colorClass"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium">
|
||||
{{ document.name }}
|
||||
@@ -309,7 +317,7 @@ import DocumentUpload from './DocumentUpload.vue'
|
||||
import ConstructeurSelect from './ConstructeurSelect.vue'
|
||||
import { useDocuments } from '~/composables/useDocuments'
|
||||
import { getFileIcon } from '~/utils/fileIcons'
|
||||
import { canPreviewDocument } from '~/utils/documentPreview'
|
||||
import { canPreviewDocument, isImageDocument } from '~/utils/documentPreview'
|
||||
import DocumentPreviewModal from '~/components/DocumentPreviewModal.vue'
|
||||
import IconLucideChevronRight from '~icons/lucide/chevron-right'
|
||||
import { useCustomFields } from '~/composables/useCustomFields'
|
||||
|
||||
@@ -37,12 +37,21 @@
|
||||
<ul v-if="selectedFiles.length" class="mt-4 w-full space-y-2 text-left">
|
||||
<li v-for="file in selectedFiles" :key="file.name" class="flex items-center justify-between text-sm">
|
||||
<div class="flex items-center gap-3">
|
||||
<component
|
||||
:is="getIcon(file).component"
|
||||
class="h-6 w-6"
|
||||
:class="getIcon(file).colorClass"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div class="h-14 w-14 flex-shrink-0 overflow-hidden rounded-md border border-base-300 bg-base-200/70 flex items-center justify-center">
|
||||
<img
|
||||
v-if="isImageFile(file)"
|
||||
:src="getFilePreview(file)"
|
||||
class="h-full w-full object-cover"
|
||||
:alt="`Aperçu de ${file.name}`"
|
||||
>
|
||||
<component
|
||||
v-else
|
||||
:is="getIcon(file).component"
|
||||
class="h-6 w-6"
|
||||
:class="getIcon(file).colorClass"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ file.name }}</span>
|
||||
<span class="text-xs text-gray-500">{{ formatSize(file.size) }} • {{ file.type || 'Type inconnu' }}</span>
|
||||
@@ -58,7 +67,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { ref, computed, watch, onBeforeUnmount } from 'vue'
|
||||
import { useToast } from '~/composables/useToast'
|
||||
import { getFileIcon } from '~/utils/fileIcons'
|
||||
import IconLucideCloudUpload from '~icons/lucide/cloud-upload'
|
||||
@@ -96,6 +105,30 @@ const dragActive = ref(false)
|
||||
const fileInput = ref(null)
|
||||
const internalFiles = ref([])
|
||||
const { showError } = useToast()
|
||||
const previewUrls = new Map()
|
||||
|
||||
const isImageFile = (file) => (file?.type || '').startsWith('image/')
|
||||
|
||||
const getFilePreview = (file) => {
|
||||
if (!isImageFile(file)) { return null }
|
||||
if (!previewUrls.has(file)) {
|
||||
previewUrls.set(file, URL.createObjectURL(file))
|
||||
}
|
||||
return previewUrls.get(file)
|
||||
}
|
||||
|
||||
const cleanupRemovedPreviews = (previousFiles = [], nextFiles = []) => {
|
||||
const nextSet = new Set(nextFiles)
|
||||
previousFiles.forEach((file) => {
|
||||
if (!nextSet.has(file)) {
|
||||
const url = previewUrls.get(file)
|
||||
if (url) {
|
||||
URL.revokeObjectURL(url)
|
||||
previewUrls.delete(file)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const selectedFiles = computed(() => internalFiles.value)
|
||||
|
||||
@@ -103,6 +136,7 @@ watch(
|
||||
() => props.modelValue,
|
||||
(newValue) => {
|
||||
if (Array.isArray(newValue)) {
|
||||
cleanupRemovedPreviews(internalFiles.value, newValue)
|
||||
internalFiles.value = [...newValue]
|
||||
}
|
||||
},
|
||||
@@ -114,6 +148,7 @@ const triggerFileDialog = () => {
|
||||
}
|
||||
|
||||
const emitFiles = (files) => {
|
||||
cleanupRemovedPreviews(internalFiles.value, files)
|
||||
internalFiles.value = files
|
||||
emit('update:modelValue', files)
|
||||
emit('files-added', files)
|
||||
@@ -180,4 +215,11 @@ const formatSize = (size) => {
|
||||
const getIcon = (file) => {
|
||||
return getFileIcon({ name: file.name, mime: file.type })
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
previewUrls.forEach((url) => {
|
||||
URL.revokeObjectURL(url)
|
||||
})
|
||||
previewUrls.clear()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -276,13 +276,21 @@
|
||||
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
|
||||
>
|
||||
<div class="flex items-center gap-3 text-sm">
|
||||
<span class="text-xl" :class="documentIcon(document).colorClass">
|
||||
<div class="h-14 w-14 flex-shrink-0 overflow-hidden rounded-md border border-base-200 bg-base-200/70 flex items-center justify-center">
|
||||
<img
|
||||
v-if="isImageDocument(document) && document.path"
|
||||
:src="document.path"
|
||||
class="h-full w-full object-cover"
|
||||
:alt="`Aperçu de ${document.name}`"
|
||||
>
|
||||
<component
|
||||
v-else
|
||||
:is="documentIcon(document).component"
|
||||
class="h-6 w-6"
|
||||
:class="documentIcon(document).colorClass"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium">
|
||||
{{ document.name }}
|
||||
@@ -340,7 +348,7 @@ import { useCustomFields } from "~/composables/useCustomFields";
|
||||
import { useToast } from "~/composables/useToast";
|
||||
import { useDocuments } from "~/composables/useDocuments";
|
||||
import { getFileIcon } from "~/utils/fileIcons";
|
||||
import { canPreviewDocument } from "~/utils/documentPreview";
|
||||
import { canPreviewDocument, isImageDocument } from "~/utils/documentPreview";
|
||||
import DocumentUpload from "~/components/DocumentUpload.vue";
|
||||
import DocumentPreviewModal from "~/components/DocumentPreviewModal.vue";
|
||||
import IconLucidePackage from "~icons/lucide/package";
|
||||
|
||||
@@ -53,9 +53,21 @@
|
||||
class="flex items-center justify-between rounded border border-base-200 bg-base-100 px-3 py-2"
|
||||
>
|
||||
<div class="flex items-center gap-3 text-sm">
|
||||
<span class="text-xl" :class="documentIcon(document).colorClass">
|
||||
<component :is="documentIcon(document).component" class="h-6 w-6" aria-hidden="true" />
|
||||
</span>
|
||||
<div class="h-14 w-14 flex-shrink-0 overflow-hidden rounded-md border border-base-200 bg-base-200/70 flex items-center justify-center">
|
||||
<img
|
||||
v-if="isImageDocument(document) && document.path"
|
||||
:src="document.path"
|
||||
class="h-full w-full object-cover"
|
||||
:alt="`Aperçu de ${document.name}`"
|
||||
>
|
||||
<component
|
||||
v-else
|
||||
:is="documentIcon(document).component"
|
||||
class="h-6 w-6"
|
||||
:class="documentIcon(document).colorClass"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium">
|
||||
{{ document.name }}
|
||||
@@ -103,6 +115,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, toRefs } from 'vue'
|
||||
import { isImageDocument } from '~/utils/documentPreview'
|
||||
import DocumentUpload from '~/components/DocumentUpload.vue'
|
||||
import SiteContactFormFields from '~/components/sites/SiteContactFormFields.vue'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user