Fix virtual sample routing and refactor for clarity
Virtual sample variations now display correctly when loading from URL hash. Old URLs with underscores are normalized to hyphens on load. URL hash updates automatically when navigating between variations. Refactored both DynamicView and Selector components with explicit function names, removed unnecessary comments, and improved code organization. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
dfb8d1038b
commit
6b80e242b8
2 changed files with 224 additions and 168 deletions
|
|
@ -76,112 +76,149 @@ const { items, label, isCompareModeEnabled, index } = defineProps({
|
|||
|
||||
// Local state
|
||||
const currentValue = ref(null);
|
||||
const syncing = ref(false); // empêche les réactions pendant les mises à jour programmatiques
|
||||
const syncing = ref(false);
|
||||
|
||||
// Store
|
||||
const { activeTracks } = storeToRefs(useDialogStore());
|
||||
|
||||
// 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 normalizeSlug(slug) {
|
||||
return slug.replace(/_/g, '-');
|
||||
}
|
||||
|
||||
function toVariation(v) {
|
||||
if (!v) return null;
|
||||
return Array.isArray(v) ? v[v.length - 1] || null : v;
|
||||
function areVariationsEqual(variationA, variationB) {
|
||||
if (!variationA || !variationB) return false;
|
||||
|
||||
if (variationA.slug && variationB.slug) {
|
||||
return normalizeSlug(variationA.slug) === normalizeSlug(variationB.slug);
|
||||
}
|
||||
|
||||
return variationA.title === variationB.title;
|
||||
}
|
||||
|
||||
// Initialisation : remplir le 1er select localement ET initialiser le store
|
||||
onBeforeMount(() => {
|
||||
function extractVariation(value) {
|
||||
if (!value) return null;
|
||||
return Array.isArray(value) ? value[value.length - 1] || null : value;
|
||||
}
|
||||
|
||||
function convertValueForCompareMode(value, shouldBeArray) {
|
||||
if (shouldBeArray) {
|
||||
return value && !Array.isArray(value) ? [value] : value;
|
||||
} else {
|
||||
return Array.isArray(value) ? value[0] || null : value;
|
||||
}
|
||||
}
|
||||
|
||||
function findMatchingVariationsInStore(storeVariations) {
|
||||
return storeVariations.filter((storeVar) =>
|
||||
items.some((item) => areVariationsEqual(item, storeVar))
|
||||
);
|
||||
}
|
||||
|
||||
function syncCurrentValueFromStore(storeVariations) {
|
||||
syncing.value = true;
|
||||
|
||||
if (index === 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];
|
||||
}
|
||||
const matchedVariations = findMatchingVariationsInStore(storeVariations);
|
||||
|
||||
if (isCompareModeEnabled) {
|
||||
currentValue.value = matchedVariations.length ? [...matchedVariations] : [];
|
||||
} else {
|
||||
// les autres ne forcent pas le store ; leur currentValue restera à null
|
||||
currentValue.value = null;
|
||||
currentValue.value = matchedVariations[0] || null;
|
||||
}
|
||||
|
||||
nextTick(() => (syncing.value = false));
|
||||
});
|
||||
}
|
||||
|
||||
function detectVariationChanges(newValues, oldValues) {
|
||||
const newList = Array.isArray(newValues)
|
||||
? newValues
|
||||
: newValues
|
||||
? [newValues]
|
||||
: [];
|
||||
const oldList = Array.isArray(oldValues)
|
||||
? oldValues
|
||||
: oldValues
|
||||
? [oldValues]
|
||||
: [];
|
||||
|
||||
const addedVariation = newList.find(
|
||||
(n) => !oldList.some((o) => areVariationsEqual(o, n))
|
||||
);
|
||||
const removedVariation = oldList.find(
|
||||
(o) => !newList.some((n) => areVariationsEqual(n, o))
|
||||
);
|
||||
|
||||
return { addedVariation, removedVariation };
|
||||
}
|
||||
|
||||
function handleVariationChange(newValue, oldValue) {
|
||||
if (syncing.value) return;
|
||||
|
||||
const { addedVariation, removedVariation } = detectVariationChanges(
|
||||
newValue,
|
||||
oldValue
|
||||
);
|
||||
|
||||
if (
|
||||
addedVariation &&
|
||||
items.some((item) => areVariationsEqual(item, addedVariation))
|
||||
) {
|
||||
updateActiveTracks(addedVariation, 'add');
|
||||
} else if (
|
||||
removedVariation &&
|
||||
items.some((item) => areVariationsEqual(item, removedVariation))
|
||||
) {
|
||||
updateActiveTracks(removedVariation, 'remove');
|
||||
}
|
||||
}
|
||||
|
||||
// Quand on bascule compare mode (objet <-> tableau)
|
||||
watch(
|
||||
() => isCompareModeEnabled,
|
||||
(flag) => {
|
||||
(shouldBeArray) => {
|
||||
syncing.value = true;
|
||||
if (flag) {
|
||||
if (currentValue.value && !Array.isArray(currentValue.value)) {
|
||||
currentValue.value = [currentValue.value];
|
||||
}
|
||||
} else {
|
||||
if (Array.isArray(currentValue.value)) {
|
||||
currentValue.value = currentValue.value[0] || null;
|
||||
}
|
||||
}
|
||||
currentValue.value = convertValueForCompareMode(
|
||||
currentValue.value,
|
||||
shouldBeArray
|
||||
);
|
||||
nextTick(() => (syncing.value = false));
|
||||
}
|
||||
);
|
||||
|
||||
// Détection ajout / suppression dans le MultiSelect (côté composant)
|
||||
// On n'agit que si l'ajout/suppression concerne une variation appartenant à `items`
|
||||
watch(
|
||||
currentValue,
|
||||
(newVal, oldVal) => {
|
||||
if (syncing.value) return;
|
||||
watch(currentValue, handleVariationChange, { deep: true });
|
||||
|
||||
const newItems = Array.isArray(newVal) ? newVal : newVal ? [newVal] : [];
|
||||
const oldItems = Array.isArray(oldVal) ? oldVal : oldVal ? [oldVal] : [];
|
||||
|
||||
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 }
|
||||
);
|
||||
|
||||
// Quand activeTracks change elsewhere -> synchroniser l'affichage local
|
||||
// Mais n'adopter que les variations qui appartiennent à ce Selector (`items`)
|
||||
watch(
|
||||
activeTracks,
|
||||
(newVal) => {
|
||||
syncing.value = true;
|
||||
|
||||
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 (isCompareModeEnabled) {
|
||||
currentValue.value = matched.length ? [...matched] : [];
|
||||
} else {
|
||||
currentValue.value = matched[0] || null;
|
||||
}
|
||||
|
||||
nextTick(() => (syncing.value = false));
|
||||
(storeVariations) => {
|
||||
const variationsList = Array.isArray(storeVariations)
|
||||
? storeVariations
|
||||
: [];
|
||||
syncCurrentValueFromStore(variationsList);
|
||||
},
|
||||
{ deep: true }
|
||||
{ deep: true, immediate: 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);
|
||||
function removeVariationFromActiveTracks(variation) {
|
||||
activeTracks.value = activeTracks.value.filter(
|
||||
(track) => !areVariationsEqual(track, variation)
|
||||
);
|
||||
}
|
||||
|
||||
function addVariationToActiveTracks(variation) {
|
||||
const isAlreadyPresent = activeTracks.value.some((track) =>
|
||||
areVariationsEqual(track, variation)
|
||||
);
|
||||
|
||||
if (isAlreadyPresent) return;
|
||||
|
||||
if (activeTracks.value.length === 0) {
|
||||
activeTracks.value = [variation];
|
||||
} else if (activeTracks.value.length === 1) {
|
||||
activeTracks.value = [activeTracks.value[0], variation];
|
||||
} else {
|
||||
activeTracks.value = [activeTracks.value[0], variation];
|
||||
}
|
||||
}
|
||||
|
||||
function updateActiveTracks(track, action = 'add') {
|
||||
const variation = extractVariation(track);
|
||||
if (!variation) return;
|
||||
|
||||
if (!isCompareModeEnabled) {
|
||||
|
|
@ -190,34 +227,12 @@ function selectTrack(track, action = 'add') {
|
|||
}
|
||||
|
||||
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];
|
||||
removeVariationFromActiveTracks(variation);
|
||||
} else {
|
||||
// remplacer le 2e
|
||||
activeTracks.value = [activeTracks.value[0], variation];
|
||||
addVariationToActiveTracks(variation);
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers pour affichage (inchangés)
|
||||
function getFrontViewUrl(item) {
|
||||
if (!item) return '';
|
||||
if (Array.isArray(item)) {
|
||||
|
|
@ -231,8 +246,8 @@ function getFrontViewUrl(item) {
|
|||
}
|
||||
|
||||
function setImage() {
|
||||
return getFrontViewUrl(currentValue.value)
|
||||
? '--image: url(\'' + getFrontViewUrl(currentValue.value) + '\')'
|
||||
return getFrontViewUrl(currentValue.value)
|
||||
? "--image: url('" + getFrontViewUrl(currentValue.value) + "')"
|
||||
: undefined;
|
||||
}
|
||||
</script>
|
||||
|
|
@ -250,7 +265,8 @@ function setImage() {
|
|||
padding: var(--space-8) var(--space-48) var(--space-8) var(--space-16);
|
||||
}
|
||||
.selector-dropdown.has-image,
|
||||
.selector-dropdown.has-image :is(#selector-select, #selector-multiselect, [role='combobox']) {
|
||||
.selector-dropdown.has-image
|
||||
:is(#selector-select, #selector-multiselect, [role='combobox']) {
|
||||
padding-left: var(--space-64);
|
||||
}
|
||||
.selector-dropdown.has-image:before {
|
||||
|
|
@ -290,7 +306,9 @@ function setImage() {
|
|||
cursor: pointer;
|
||||
}
|
||||
[role='combobox'] p,
|
||||
.selector-dropdown [data-pc-section="labelcontainer"] > [data-pc-section='label'] {
|
||||
.selector-dropdown
|
||||
[data-pc-section='labelcontainer']
|
||||
> [data-pc-section='label'] {
|
||||
max-height: 1lh;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue