multiselect working

This commit is contained in:
isUnknown 2025-10-01 13:10:28 +02:00
parent d41a05d367
commit 8262466f5a

View file

@ -61,7 +61,7 @@
</template> </template>
<script setup> <script setup>
import { onBeforeMount, ref, watch } from 'vue'; import { onBeforeMount, ref, watch, nextTick } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useDialogStore } from '../stores/dialog'; import { useDialogStore } from '../stores/dialog';
@ -69,31 +69,54 @@ import { useDialogStore } from '../stores/dialog';
const { items, label, isCompareModeEnabled, index } = defineProps({ const { items, label, isCompareModeEnabled, index } = defineProps({
label: String, label: String,
items: Array, items: Array,
isCompareModeEnabled: { isCompareModeEnabled: { type: Boolean, default: false },
type: Boolean,
default: false,
},
index: Number, index: Number,
}); });
// Variables // Local state
const currentValue = ref(null); const currentValue = ref(null);
const syncing = ref(false); // empêche les réactions pendant les mises à jour programmatiques
// Stores // Store
const { activeTracks } = storeToRefs(useDialogStore()); const { activeTracks } = storeToRefs(useDialogStore());
// Hooks // Utils
function isSame(a, b) {
if (!a || !b) return false;
if (a.slug && b.slug) return a.slug === b.slug;
return a.title === b.title;
}
function toVariation(v) {
if (!v) return null;
return Array.isArray(v) ? v[v.length - 1] || null : v;
}
// Initialisation : remplir le 1er select localement ET initialiser le store
onBeforeMount(() => { onBeforeMount(() => {
syncing.value = true;
if (index === 0) { if (index === 0) {
currentValue.value = items[0]; currentValue.value = items[0] || null;
// si le store est vide, initialiser avec la variation du premier sélecteur
if (!activeTracks.value || activeTracks.value.length === 0) {
const v = toVariation(items[0]);
if (v) activeTracks.value = [v];
}
} else {
// les autres ne forcent pas le store ; leur currentValue restera à null
currentValue.value = null;
} }
nextTick(() => (syncing.value = false));
}); });
// Watchers // Quand on bascule compare mode (objet <-> tableau)
watch( watch(
() => isCompareModeEnabled, () => isCompareModeEnabled,
(newValue) => { (flag) => {
if (newValue) { syncing.value = true;
if (flag) {
if (currentValue.value && !Array.isArray(currentValue.value)) { if (currentValue.value && !Array.isArray(currentValue.value)) {
currentValue.value = [currentValue.value]; currentValue.value = [currentValue.value];
} }
@ -102,80 +125,107 @@ watch(
currentValue.value = currentValue.value[0] || null; currentValue.value = currentValue.value[0] || null;
} }
} }
nextTick(() => (syncing.value = false));
} }
); );
watch(currentValue, (newValue) => { // Détection ajout / suppression dans le MultiSelect (côté composant)
if ( // On n'agit que si l'ajout/suppression concerne une variation appartenant à `items`
newValue !== null &&
(Array.isArray(newValue) ? newValue.length > 0 : true)
) {
selectTrack(newValue);
}
});
watch( watch(
activeTracks, currentValue,
(newValue) => { (newVal, oldVal) => {
if (!isCompareModeEnabled || !currentValue.value) return; if (syncing.value) return;
currentValue.value.forEach((item) => { const newItems = Array.isArray(newVal) ? newVal : newVal ? [newVal] : [];
if (!newValue.includes(item)) { const oldItems = Array.isArray(oldVal) ? oldVal : oldVal ? [oldVal] : [];
currentValue.value = currentValue.value.filter((el) => el !== item);
} const added = newItems.find((n) => !oldItems.some((o) => isSame(o, n)));
}); const removed = oldItems.find((o) => !newItems.some((n) => isSame(n, o)));
if (added && items.some((it) => isSame(it, added))) {
selectTrack(added, 'add');
} else if (removed && items.some((it) => isSame(it, removed))) {
selectTrack(removed, 'remove');
}
}, },
{ deep: true } { deep: true }
); );
function selectTrack(track) { // Quand activeTracks change elsewhere -> synchroniser l'affichage local
if (!isCompareModeEnabled) { // Mais n'adopter que les variations qui appartiennent à ce Selector (`items`)
activeTracks.value = [track]; watch(
return; activeTracks,
} (newVal) => {
syncing.value = true;
const lastVariation = track[track.length - 1]; const storeList = Array.isArray(newVal) ? newVal : [];
// ne garder que les variations du store qui sont dans `items`
const matched = storeList.filter((av) =>
items.some((it) => isSame(it, av))
);
if ( if (isCompareModeEnabled) {
activeTracks.value.length === 1 && currentValue.value = matched.length ? [...matched] : [];
!activeTracks.value.includes(lastVariation)
) {
activeTracks.value.push(lastVariation);
return;
}
if (activeTracks.value.length === 2) {
if (activeTracks.value.includes(lastVariation)) {
removeTrack(track);
} else { } else {
activeTracks.value.pop(); currentValue.value = matched[0] || null;
activeTracks.value.push(lastVariation);
} }
nextTick(() => (syncing.value = false));
},
{ deep: true }
);
// Logique centrale de sélection (ajout / suppression)
// Règles :
// - mode normal -> activeTracks = [variation]
// - mode comparaison -> conserver activeTracks[0] si possible; second élément ajouté/remplacé; suppression gère le cas de la suppression de la première
function selectTrack(track, action = 'add') {
const variation = toVariation(track);
if (!variation) return;
if (!isCompareModeEnabled) {
activeTracks.value = [variation];
return;
}
if (action === 'remove') {
const wasFirst =
activeTracks.value.length && isSame(activeTracks.value[0], variation);
activeTracks.value = activeTracks.value.filter(
(t) => !isSame(t, variation)
);
// si on a retiré la première et qu'il reste une piste, elle devient naturellement index 0
// pas d'action supplémentaire nécessaire ici (déjà assuré par le filter)
return;
}
// action === 'add'
if (activeTracks.value.some((t) => isSame(t, variation))) {
// déjà présent -> ignore
return;
}
if (activeTracks.value.length === 0) {
activeTracks.value = [variation];
} else if (activeTracks.value.length === 1) {
activeTracks.value = [activeTracks.value[0], variation];
} else {
// remplacer le 2e
activeTracks.value = [activeTracks.value[0], variation];
} }
} }
function removeTrack(track) { // Helpers pour affichage (inchangés)
activeTracks.value = activeTracks.value.filter(
(activeTrack) => activeTrack.title !== track.title
);
}
watch(activeTracks, (newValue) => {
if (newValue[newValue.length - 1] !== currentValue.value) {
currentValue.value = isCompareModeEnabled ? [] : null;
}
});
// Functions
function getFrontViewUrl(item) { function getFrontViewUrl(item) {
if (!item) return ''; if (!item) return '';
if (Array.isArray(item)) { if (Array.isArray(item)) {
return item.length > 0 ? getFrontViewUrl(item[0]) : ''; return item.length > 0 ? getFrontViewUrl(item[0]) : '';
} }
if (item.files.length > 1) { if (item.files && item.files.length > 1) {
return item.files[7]?.url || item.files[0].url; return item.files[7]?.url || item.files[0].url;
} else { } else {
return item.files[0].url; return item.files && item.files[0] ? item.files[0].url : '';
} }
} }
</script> </script>