🐛 fix: File upload component updated

This commit is contained in:
Paul Valerie GOMA 2025-07-25 18:30:52 +02:00
parent 0d925df378
commit fc64beda13

View File

@ -11,17 +11,16 @@ import type { FileUploadErrorEvent, FileUploadProgressEvent, FileUploadRemoveEve
import { computed, useId, ref, onMounted } from 'vue'; import { computed, useId, ref, onMounted } from 'vue';
import styles from '@visua/typography.module.css'; import styles from '@visua/typography.module.css';
const fileUploadRef = ref();
const fileProgressMap = ref<Record<string, number>>({});
const fileSelected = ref(false);
const hasActiveError = ref(false);
const lastSelectedFile = ref<File | null>(null);
defineOptions({ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}) })
const fileUploadRef = ref();
const fileProgressMap = ref<Record<string, number>>({});
defineExpose({
upload: () => fileUploadRef.value.upload()
});
const props = withDefaults(defineProps<IVFileUpload>(), { const props = withDefaults(defineProps<IVFileUpload>(), {
id: () => useId(), id: () => useId(),
label: 'Ajouter un fichier', label: 'Ajouter un fichier',
@ -43,7 +42,7 @@ const props = withDefaults(defineProps<IVFileUpload>(), {
cancelLabel: 'Annuler', cancelLabel: 'Annuler',
showCancelButton: true, showCancelButton: true,
showUploadButton: true, showUploadButton: true,
advanced: false, advanced: false
}) })
const emit = defineEmits([ const emit = defineEmits([
@ -58,22 +57,21 @@ const emit = defineEmits([
'removeUploadFile', 'removeUploadFile',
'uploader', 'uploader',
]); ]);
const fileSelected = ref(false);
const hasActiveError = ref(false);
const lastSelectedFile = ref<File | null>(null); defineExpose({
upload: () => fileUploadRef.value.upload()
});
const handleSelect = (event: FileUploadSelectEvent) => { const handleSelect = (event: FileUploadSelectEvent) => {
emit('select', event); emit('select', event);
if (!props.advanced && event.files.length > 0 && fileUploadRef.value) { if (!props.advanced && event.files.length > 0 && fileUploadRef.value) {
fileUploadRef.value.clear(); fileUploadRef.value.files = [event.files.at(-1)];
fileUploadRef.value.files.push(event.files[event.files.length - 1]); lastSelectedFile.value = event.files.at(-1);
} }
fileSelected.value = true; fileSelected.value = true;
}; };
const handleClear = () => { const handleClear = () => {
emit('clear'); emit('clear');
fileSelected.value = false; fileSelected.value = false;
@ -82,13 +80,9 @@ const handleClear = () => {
}; };
const handleUpload = (event: FileUploadUploadEvent) => { const handleUpload = (event: FileUploadUploadEvent) => {
totalSize.value = 0;
totalSizePercent.value = 100;
emit('upload', event); emit('upload', event);
hasActiveError.value = false;
}; };
const handleRemove = (event: FileUploadRemoveEvent) => { const handleRemove = (event: FileUploadRemoveEvent) => {
emit('remove', event); emit('remove', event);
fileSelected.value = false; fileSelected.value = false;
@ -110,34 +104,19 @@ const handleError = (event: FileUploadErrorEvent) => {
hasActiveError.value = true; hasActiveError.value = true;
} }
const error = computed(() => hasActiveError.value); const uploadEvent = (callback: () => void) => {
callback();
};
const isSimpleAndEmpty = computed(() => {
return !props.advanced && !fileSelected.value;
});
onMounted(() => { onMounted(() => {
const currentFiles = fileUploadRef.value?.files || []; const currentFiles = fileUploadRef.value?.files || [];
fileSelected.value = currentFiles.length > 0; fileSelected.value = currentFiles.length > 0;
}); });
const totalSize = ref(0);
const totalSizePercent = ref(0);
const uploadEvent = (callback: () => void) => {
totalSizePercent.value = totalSize.value / 10;
callback();
fileUploadRef.value.upload();
};
const padding = computed(() => props.advanced ? '1.125rem' : '0rem') const padding = computed(() => props.advanced ? '1.125rem' : '0rem')
const borderColor = computed(() => props.advanced ? 'var(--border-default-grey)' : 'transparent'); const borderColor = computed(() => props.advanced ? 'var(--border-default-grey)' : 'transparent');
const getLastFileSelected = (files: File[]) => { const error = computed(() => hasActiveError.value);
const indexOfLastFileSelected = files.length - 1;
return files[indexOfLastFileSelected];
}
const labelState = computed(() => { const labelState = computed(() => {
if(!error.value && !props.disabled) return 'default'; if(!error.value && !props.disabled) return 'default';
@ -163,7 +142,6 @@ const globalStatusMessage = computed<{
return null; return null;
}); });
</script> </script>
<template> <template>
@ -211,88 +189,101 @@ const globalStatusMessage = computed<{
class="p-fileupload" class="p-fileupload"
> >
<template #header="slotProps"> <template #header="slotProps">
<div v-if="props.advanced" class="advanced-fileupload-header"> <VButtonGroup
<VButtonGroup v-if="props.advanced"
:buttons="[ :buttons="[
{ {
label: 'Parcourir...', label: 'Parcourir...',
onClick: slotProps.chooseCallback, onClick: slotProps.chooseCallback,
title: 'parcourir les fichiers', title: 'parcourir les fichiers',
}, },
{ {
label: 'Téléverser', label: 'Téléverser',
icon: 'ri-upload-cloud-line', icon: 'ri-upload-cloud-line',
onClick: () => uploadEvent(slotProps.uploadCallback), onClick: () => uploadEvent(slotProps.uploadCallback),
secondary: true, secondary: true,
disabled: !slotProps.files || slotProps.files.length === 0, disabled: !slotProps.files || slotProps.files.length === 0,
title: 'televerser les fichiers' title: 'televerser les fichiers'
}, },
{ {
label: 'Supprimer', label: 'Supprimer',
tertiary: true, tertiary: true,
onClick: slotProps.clearCallback, onClick: slotProps.clearCallback,
disabled: !slotProps.files || slotProps.files.length === 0, disabled: !slotProps.files || slotProps.files.length === 0,
title: 'suprimer les fichiers' title: 'suprimer les fichiers'
} }
]" ]"
size="sm" size="sm"
:disabled="props.disabled"
inlineLayoutWhen="always"
title="boutons de gestion des fichiers"
/>
<div v-else class="simple">
<VButton
label="Parcourir..."
:disabled="props.disabled" :disabled="props.disabled"
inlineLayoutWhen="always" size="sm"
title="boutons de gestion des fichiers" @click="slotProps.chooseCallback"
title="parcourir les fichiers"
/> />
<VMessage
v-if="globalStatusMessage"
:type="globalStatusMessage.type"
:title="globalStatusMessage.title"
/>
</div>
<div v-else class="simple-fileupload-header">
<VButton label="Parcourir..." :disabled="props.disabled" size="sm" @click="slotProps.chooseCallback" title="parcourir les fichiers"/>
<span <span
v-if="isSimpleAndEmpty" v-if="(!slotProps.files || slotProps.files.length === 0) && (!slotProps.uploadedFiles || slotProps.uploadedFiles.length === 0)"
:class="[styles['text-body-SM-detail-text-Regular']]" :class="[styles['text-body-SM-detail-text-Regular']]"
> >
Aucun fichier sélectionné Aucun fichier sélectionné
</span> </span>
</div> </div>
</template> </template>
<template #content="slotProps" v-if="!isSimpleAndEmpty"> <template #empty>
<div v-if="props.advanced" class="advanced-container-content"> <div class="fileupload-empty" v-if="props.advanced" :class="[styles['text-body-SM-detail-text-Regular']]">
<div class="fileupload-content"> <i class="ri-upload-cloud-line upload-cloud-icon"></i>
<div v-if="slotProps.files.length > 0"> <p>Glissez-déposez les fichiers ici pour les téléverser.</p>
<div class="file-area"> </div>
<VFile </template>
v-for="(file, index) in slotProps.files" <template #content="slotProps">
:key="file.name + file.type + file.size" <div v-if="props.advanced" style="margin-top: 0.5rem;">
:file="file" <VMessage
:index="index" v-if="globalStatusMessage"
:remove-file-callback="slotProps.removeFileCallback" :type="globalStatusMessage.type"
:progress="fileProgressMap[file.name] || 0" :title="globalStatusMessage.title"
status="pending" />
advanced <div v-if="slotProps.files.length > 0" class="file-area">
/> <VFile
</div> v-for="(file, index) in slotProps.files"
</div> :key="file.name + file.type + file.size"
<div v-if="slotProps.uploadedFiles.length > 0" class="fileupload-content"> :file="file"
<div class="file-area"> :index="index"
<VFile :remove-file-callback="slotProps.removeFileCallback"
v-for="(file, index) in slotProps.uploadedFiles" :progress="fileProgressMap[file.name] || 0"
:key="file.name + file.type + file.size" status="pending"
:file="file" advanced
:index="index" />
:remove-file-callback="slotProps.removeFileCallback" </div>
:progress="100" <div v-if="slotProps.uploadedFiles.length > 0" class="file-area">
status="completed" <VFile
advanced v-for="(file, index) in slotProps.uploadedFiles"
/> :key="file.name + file.type + file.size"
</div> :file="file"
</div> :index="index"
:remove-file-callback="slotProps.removeFileCallback"
:progress="100"
status="completed"
advanced
/>
</div> </div>
</div> </div>
<div v-else-if="slotProps.files.length > 0" class="simple-container-content"> <div v-else>
<VFile <VFile
:file="getLastFileSelected(slotProps.files)" v-if="lastSelectedFile"
:file="lastSelectedFile"
:remove-file-callback="slotProps.removeFileCallback" :remove-file-callback="slotProps.removeFileCallback"
status="pending"
/>
<VFile
v-if="slotProps.uploadedFiles.length > 0 && slotProps.uploadedFiles.at(-1)"
:file="slotProps.uploadedFiles.at(-1)!"
:remove-file-callback="slotProps.removeFileCallback"
status="completed"
/> />
</div> </div>
<VHint <VHint
@ -303,12 +294,6 @@ const globalStatusMessage = computed<{
icon icon
/> />
</template> </template>
<template #empty v-if="props.advanced">
<div class="fileupload-empty" v-if="props.advanced">
<i class="ri-upload-cloud-line upload-cloud-icon"></i>
<p>Glissez-déposez les fichiers ici pour les téléverser.</p>
</div>
</template>
</FileUpload> </FileUpload>
</div> </div>
</template> </template>
@ -334,27 +319,19 @@ const globalStatusMessage = computed<{
padding: v-bind(padding); padding: v-bind(padding);
} }
.simple-fileupload-header{ .simple{
display: flex; display: flex;
flex-direction: row; flex-direction: row;
gap: var(--p-fileupload-basic-gap); align-items: center;
align-items: baseline; gap: var(--p-fileupload-file-gap);
padding: 0px;
}
.advanced-fileupload-header{
width: 100%;
display: flex;
flex-direction: column;
gap: var(--p-fileupload-content-gap);
margin-bottom: 0.75rem;
} }
.fileupload-empty{ .fileupload-empty{
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-around;
align-items: center; align-items: center;
margin-top: 1rem;
gap: 1.5rem;
} }
.upload-cloud-icon{ .upload-cloud-icon{