| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 |
- <template>
- <component
- :is="tag"
- :href="tag === 'a' ? '#' : undefined"
- :type="tag === 'button' ? 'button' : undefined"
- :class="classe"
- :title="titre"
- :data-bs-toggle="tooltip ? 'tooltip' : undefined"
- :aria-busy="enChargement"
- @click.prevent="switchEtat"
- >
- <i :class="iconeCourante"></i>
- </component>
- </template>
- <script setup>
- import { computed, ref } from 'vue';
- const props = defineProps({
- // Route du contrôleur qui bascule l'état
- path: { type: String, required: true },
- // Identifiant du film concerné
- idFilm: { type: [String, Number], required: true },
- // Classes Font Awesome de l'icône quand actif / inactif
- iconeActif: { type: String, required: true },
- iconeInactif: { type: String, required: true },
- // État initial (définit quelle icône est affichée au montage)
- actif: { type: Boolean, default: false },
- // Texte de l'infobulle décrivant l'action
- titre: { type: String, default: '' },
- // Active l'attribut data-bs-toggle="tooltip" de Bootstrap
- tooltip: { type: Boolean, default: false },
- // Balise racine : 'a' (par défaut) ou 'button'
- tag: { type: String, default: 'a' },
- // Classes CSS additionnelles pour la balise (ex: 'btn text-primary')
- classe: { type: String, default: '' },
- // Animation Font Awesome appliquée à l'icône cible pendant la requête
- // (fa-beat-fade, fa-beat, fa-flip, ... — vide pour désactiver)
- animationChargement: { type: String, default: 'fa-beat-fade' },
- });
- const estActif = ref(props.actif);
- const enChargement = ref(false);
- // L'icône cible est celle qui sera affichée si la requête réussit
- const iconeCible = computed(() => (estActif.value ? props.iconeInactif : props.iconeActif));
- // Préserve les classes de taille (fa-lg, fa-2xl, ...) pendant le chargement
- const classesTaille = computed(() => {
- const icone = enChargement.value ? iconeCible.value : (estActif.value ? props.iconeActif : props.iconeInactif);
- return icone.split(' ').filter((c) => /^fa-(2?xs|sm|lg|2?xl|[0-9]x|fw)$/.test(c));
- });
- const iconeCourante = computed(() => {
- if (enChargement.value) {
- return [iconeCible.value, props.animationChargement, ...classesTaille.value]
- .filter(Boolean)
- .join(' ');
- }
- return estActif.value ? props.iconeActif : props.iconeInactif;
- });
- async function switchEtat() {
- if (enChargement.value) {
- return;
- }
- enChargement.value = true;
- try {
- const response = await fetch(props.path, {
- method: 'PATCH',
- body: JSON.stringify({ id_film: props.idFilm }),
- });
- if (!response.ok) {
- throw new Error(`Erreur Http ${response.status}`);
- }
- await response.json();
- estActif.value = !estActif.value;
- } catch (error) {
- console.error(error);
- } finally {
- enChargement.value = false;
- }
- }
- </script>
- <style scoped>
- i {
- display: inline-block;
- transition: opacity 0.3s ease;
- }
- </style>
|