fix(extra-features): Fixed the sync product image/gallery
This commit is contained in:
@@ -1,27 +1,34 @@
|
|||||||
@use "sass:map";
|
@use "sass:map";
|
||||||
|
|
||||||
.product-thumbs {
|
.product-thumbs {
|
||||||
margin-right: -#{map.get($spacers, 1)};
|
margin-right: -#{map.get($spacers, 1)};
|
||||||
margin-left: -#{map.get($spacers, 1)};
|
margin-left: -#{map.get($spacers, 1)};
|
||||||
|
|
||||||
&__elem {
|
&.swiper-vertical {
|
||||||
padding: 0 map.get($spacers, 1);
|
max-height: 600px;
|
||||||
cursor: pointer;
|
}
|
||||||
opacity: .3;
|
&.swiper-horizontal {
|
||||||
transition: .3s ease opacity;
|
max-height: 200px;
|
||||||
@include make-col(4);
|
|
||||||
|
|
||||||
&:focus,
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.swiper-slide-thumb-active {
|
&__elem {
|
||||||
opacity: 1;
|
padding: 0 map.get($spacers, 1);
|
||||||
}
|
cursor: pointer;
|
||||||
|
opacity: 0.3;
|
||||||
|
transition: 0.3s ease opacity;
|
||||||
|
@include make-col(4);
|
||||||
|
|
||||||
@include media-breakpoint-up(sm) {
|
&:focus,
|
||||||
@include make-col(3);
|
&:hover {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.swiper-slide-thumb-active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
@include make-col(3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,101 +1,148 @@
|
|||||||
class ProductGallery {
|
class ProductGallery {
|
||||||
constructor({
|
constructor({
|
||||||
thumbsSliderSelector = '.js-product-thumbs',
|
thumbsSliderSelector = ".js-product-thumbs",
|
||||||
mainSliderSelector = '.js-product-main-images',
|
mainSliderSelector = ".js-product-main-images",
|
||||||
modalSliderSelector = '.js-modal-gallery',
|
modalSliderSelector = ".js-modal-gallery",
|
||||||
galleryModalSelector = '.js-product-images-modal',
|
galleryModalSelector = ".js-product-images-modal",
|
||||||
} = {}) {
|
} = {}) {
|
||||||
this.thumbsSliderSelector = thumbsSliderSelector;
|
this.thumbsSliderSelector = thumbsSliderSelector;
|
||||||
this.mainSliderSelector = mainSliderSelector;
|
this.mainSliderSelector = mainSliderSelector;
|
||||||
this.modalSliderSelector = modalSliderSelector;
|
this.modalSliderSelector = modalSliderSelector;
|
||||||
this.galleryModalSelector = galleryModalSelector;
|
this.galleryModalSelector = galleryModalSelector;
|
||||||
this.mainSliderSwiperInstance = null;
|
this.mainSliderSwiperInstance = null;
|
||||||
this.modalSliderSwiperInstance = null;
|
this.modalSliderSwiperInstance = null;
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.mainSliderSwiperInstance = null;
|
|
||||||
this.modalSliderSwiperInstance = null;
|
|
||||||
this.initProductImageSlider();
|
|
||||||
this.initModalGallerySlider();
|
|
||||||
}
|
|
||||||
|
|
||||||
async initProductImageSlider() {
|
|
||||||
const thumbsElem = document.querySelector(this.thumbsSliderSelector);
|
|
||||||
const galleryTopElem = document.querySelector(this.mainSliderSelector);
|
|
||||||
|
|
||||||
if (!thumbsElem && !galleryTopElem) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const galleryThumbs = new prestashop.SwiperSlider(thumbsElem, {
|
init() {
|
||||||
breakpoints: {
|
this.mainSliderSwiperInstance = null;
|
||||||
320: {
|
this.modalSliderSwiperInstance = null;
|
||||||
slidesPerView: 3,
|
this.initProductImageSlider();
|
||||||
},
|
this.initModalGallerySlider();
|
||||||
576: {
|
|
||||||
slidesPerView: 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watchSlidesVisibility: true,
|
|
||||||
watchSlidesProgress: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const galleryThumbsInstance = await galleryThumbs.initSlider();
|
|
||||||
|
|
||||||
const mainSlider = new prestashop.SwiperSlider(galleryTopElem, {
|
|
||||||
spaceBetween: 10,
|
|
||||||
navigation: {
|
|
||||||
nextEl: galleryTopElem.querySelector('.swiper-button-next'),
|
|
||||||
prevEl: galleryTopElem.querySelector('.swiper-button-prev'),
|
|
||||||
},
|
|
||||||
thumbs: {
|
|
||||||
swiper: galleryThumbsInstance,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const mainSliderInstance = await mainSlider.initSlider();
|
|
||||||
|
|
||||||
this.mainSliderSwiperInstance = mainSliderInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
initModalGallerySlider() {
|
|
||||||
const gallerySliderElem = document.querySelector(this.modalSliderSelector);
|
|
||||||
|
|
||||||
if (!gallerySliderElem) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleModalOpen = async () => {
|
syncThumbsHeight() {
|
||||||
if (this.modalSliderSwiperInstance) {
|
const mainImages = document.querySelector(".product-main-images");
|
||||||
gallerySliderElem.style.opacity = 0;
|
const thumbs = document.querySelector(this.thumbsSliderSelector);
|
||||||
|
if (!mainImages || !thumbs) return;
|
||||||
|
|
||||||
// DIRTY HACK
|
if (thumbs.classList.contains("swiper-vertical")) {
|
||||||
setTimeout(() => {
|
const h = mainImages.offsetHeight;
|
||||||
this.modalSliderSwiperInstance.update();
|
if (h > 0) {
|
||||||
this.modalSliderSwiperInstance.slideTo(this.mainSliderSwiperInstance ? this.mainSliderSwiperInstance.activeIndex : 0, 0);
|
thumbs.style.height = h + "px";
|
||||||
gallerySliderElem.style.opacity = 1;
|
}
|
||||||
}, 200);
|
} else {
|
||||||
} else {
|
thumbs.style.height = "";
|
||||||
const modalSlider = new prestashop.SwiperSlider(gallerySliderElem, {
|
}
|
||||||
slidesPerView: 1,
|
}
|
||||||
spaceBetween: 10,
|
|
||||||
initialSlide: this.mainSliderSwiperInstance ? this.mainSliderSwiperInstance.activeIndex : 0,
|
async initProductImageSlider() {
|
||||||
navigation: {
|
const thumbsElem = document.querySelector(this.thumbsSliderSelector);
|
||||||
nextEl: gallerySliderElem.querySelector('.swiper-button-next'),
|
const galleryTopElem = document.querySelector(this.mainSliderSelector);
|
||||||
prevEl: gallerySliderElem.querySelector('.swiper-button-prev'),
|
|
||||||
},
|
if (!thumbsElem && !galleryTopElem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const galleryThumbs = new prestashop.SwiperSlider(thumbsElem, {
|
||||||
|
direction: "horizontal", // default
|
||||||
|
breakpoints: {
|
||||||
|
0: {
|
||||||
|
direction: "horizontal",
|
||||||
|
slidesPerView: 3,
|
||||||
|
},
|
||||||
|
768: {
|
||||||
|
direction: "vertical",
|
||||||
|
slidesPerView: 5,
|
||||||
|
},
|
||||||
|
992: {
|
||||||
|
direction: "horizontal",
|
||||||
|
slidesPerView: 3,
|
||||||
|
},
|
||||||
|
1200: {
|
||||||
|
direction: "vertical",
|
||||||
|
slidesPerView: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watchSlidesVisibility: true,
|
||||||
|
watchSlidesProgress: true,
|
||||||
|
spaceBetween: 10,
|
||||||
});
|
});
|
||||||
|
|
||||||
const modalSliderInstance = await modalSlider.initSlider();
|
const galleryThumbsInstance = await galleryThumbs.initSlider();
|
||||||
|
|
||||||
this.modalSliderSwiperInstance = modalSliderInstance;
|
galleryThumbsInstance.on("resize", () => this.syncThumbsHeight());
|
||||||
}
|
galleryThumbsInstance.on("breakpoint", () => this.syncThumbsHeight());
|
||||||
};
|
|
||||||
|
|
||||||
// TO REFACTO LATER WITH BS5 REMOVE JQUERY!
|
const mainSlider = new prestashop.SwiperSlider(galleryTopElem, {
|
||||||
$(this.galleryModalSelector).on('show.bs.modal', handleModalOpen);
|
spaceBetween: 10,
|
||||||
}
|
navigation: {
|
||||||
|
nextEl: galleryTopElem.querySelector(".swiper-button-next"),
|
||||||
|
prevEl: galleryTopElem.querySelector(".swiper-button-prev"),
|
||||||
|
},
|
||||||
|
thumbs: {
|
||||||
|
swiper: galleryThumbsInstance,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const mainSliderInstance = await mainSlider.initSlider();
|
||||||
|
|
||||||
|
this.mainSliderSwiperInstance = mainSliderInstance;
|
||||||
|
this.syncThumbsHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
initModalGallerySlider() {
|
||||||
|
const gallerySliderElem = document.querySelector(
|
||||||
|
this.modalSliderSelector,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!gallerySliderElem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleModalOpen = async () => {
|
||||||
|
if (this.modalSliderSwiperInstance) {
|
||||||
|
gallerySliderElem.style.opacity = 0;
|
||||||
|
|
||||||
|
// DIRTY HACK
|
||||||
|
setTimeout(() => {
|
||||||
|
this.modalSliderSwiperInstance.update();
|
||||||
|
this.modalSliderSwiperInstance.slideTo(
|
||||||
|
this.mainSliderSwiperInstance
|
||||||
|
? this.mainSliderSwiperInstance.activeIndex
|
||||||
|
: 0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
gallerySliderElem.style.opacity = 1;
|
||||||
|
}, 200);
|
||||||
|
} else {
|
||||||
|
const modalSlider = new prestashop.SwiperSlider(
|
||||||
|
gallerySliderElem,
|
||||||
|
{
|
||||||
|
slidesPerView: 1,
|
||||||
|
spaceBetween: 10,
|
||||||
|
initialSlide: this.mainSliderSwiperInstance
|
||||||
|
? this.mainSliderSwiperInstance.activeIndex
|
||||||
|
: 0,
|
||||||
|
navigation: {
|
||||||
|
nextEl: gallerySliderElem.querySelector(
|
||||||
|
".swiper-button-next",
|
||||||
|
),
|
||||||
|
prevEl: gallerySliderElem.querySelector(
|
||||||
|
".swiper-button-prev",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const modalSliderInstance = await modalSlider.initSlider();
|
||||||
|
|
||||||
|
this.modalSliderSwiperInstance = modalSliderInstance;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TO REFACTO LATER WITH BS5 REMOVE JQUERY!
|
||||||
|
$(this.galleryModalSelector).on("show.bs.modal", handleModalOpen);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ProductGallery;
|
export default ProductGallery;
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
//Add functionality to the custom form in product miniatures
|
||||||
|
|
||||||
|
import useAlertToast from "@js/theme/components/useAlertToast";
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const toast = useAlertToast();
|
||||||
|
|
||||||
|
const parsePositiveInteger = (value, fallback = 1) => {
|
||||||
|
const parsedValue = parseInt(value, 10);
|
||||||
|
return Number.isFinite(parsedValue) && parsedValue > 0
|
||||||
|
? parsedValue
|
||||||
|
: fallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isInvalidStepMultiple = (rawValue, minQuantity, stepQuantity) => {
|
||||||
|
const parsedValue = parseInt(rawValue, 10);
|
||||||
|
|
||||||
|
if (!Number.isFinite(parsedValue) || parsedValue <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (parsedValue - minQuantity) % stepQuantity !== 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeQuantityByStep = (rawValue, minQuantity, stepQuantity) => {
|
||||||
|
const parsedValue = parseInt(rawValue, 10);
|
||||||
|
const safeValue = Number.isFinite(parsedValue)
|
||||||
|
? Math.max(parsedValue, minQuantity)
|
||||||
|
: minQuantity;
|
||||||
|
|
||||||
|
return (
|
||||||
|
minQuantity +
|
||||||
|
Math.ceil((safeValue - minQuantity) / stepQuantity) * stepQuantity
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPackWarningMessage = (input, stepQuantity) => {
|
||||||
|
const messageTemplate = input.getAttribute(
|
||||||
|
"data-pack-message-template",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!messageTemplate) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageTemplate.replace("__PACK__", stepQuantity);
|
||||||
|
};
|
||||||
|
|
||||||
|
let $forms = document.querySelectorAll(".product-miniature__form");
|
||||||
|
|
||||||
|
$forms.forEach(($form) => {
|
||||||
|
let $quantityInput = $form.querySelector("input[name='qty-field']");
|
||||||
|
let $quantityHidden = $form.querySelector("input[name='qty']");
|
||||||
|
let $incrementBtn = $form.querySelector(
|
||||||
|
"button[name='qty-field-plus']",
|
||||||
|
);
|
||||||
|
let $decrementBtn = $form.querySelector(
|
||||||
|
"button[name='qty-field-minus']",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!$quantityInput ||
|
||||||
|
!$quantityHidden ||
|
||||||
|
!$incrementBtn ||
|
||||||
|
!$decrementBtn
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minQuantity = parsePositiveInteger(
|
||||||
|
$quantityInput.getAttribute("min"),
|
||||||
|
parsePositiveInteger($quantityHidden.value, 1),
|
||||||
|
);
|
||||||
|
const stepQuantity = parsePositiveInteger(
|
||||||
|
$quantityInput.getAttribute("step"),
|
||||||
|
minQuantity,
|
||||||
|
);
|
||||||
|
|
||||||
|
$quantityInput.setAttribute("min", minQuantity);
|
||||||
|
$quantityInput.setAttribute("step", stepQuantity);
|
||||||
|
|
||||||
|
function updateHiddenQuantity() {
|
||||||
|
const normalizedQuantity = normalizeQuantityByStep(
|
||||||
|
$quantityInput.value,
|
||||||
|
minQuantity,
|
||||||
|
stepQuantity,
|
||||||
|
);
|
||||||
|
|
||||||
|
$quantityHidden.value = normalizedQuantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeTypedQuantity(showToast = false) {
|
||||||
|
const enteredQuantity = $quantityInput.value;
|
||||||
|
const normalizedQuantity = normalizeQuantityByStep(
|
||||||
|
enteredQuantity,
|
||||||
|
minQuantity,
|
||||||
|
stepQuantity,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
showToast &&
|
||||||
|
isInvalidStepMultiple(
|
||||||
|
enteredQuantity,
|
||||||
|
minQuantity,
|
||||||
|
stepQuantity,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const warningMessage = getPackWarningMessage(
|
||||||
|
$quantityInput,
|
||||||
|
stepQuantity,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (warningMessage) {
|
||||||
|
toast.warning(warningMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseInt($quantityInput.value, 10) !== normalizedQuantity) {
|
||||||
|
$quantityInput.value = normalizedQuantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
$quantityHidden.value = normalizedQuantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
$quantityInput.addEventListener("input", function () {
|
||||||
|
if ($quantityInput.value === "") {
|
||||||
|
$quantityHidden.value = minQuantity;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHiddenQuantity();
|
||||||
|
});
|
||||||
|
|
||||||
|
$incrementBtn.addEventListener("click", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const currentValue = normalizeQuantityByStep(
|
||||||
|
$quantityInput.value,
|
||||||
|
minQuantity,
|
||||||
|
stepQuantity,
|
||||||
|
);
|
||||||
|
|
||||||
|
$quantityInput.value = currentValue + stepQuantity;
|
||||||
|
updateHiddenQuantity();
|
||||||
|
});
|
||||||
|
|
||||||
|
$decrementBtn.addEventListener("click", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const currentValue = normalizeQuantityByStep(
|
||||||
|
$quantityInput.value,
|
||||||
|
minQuantity,
|
||||||
|
stepQuantity,
|
||||||
|
);
|
||||||
|
|
||||||
|
$quantityInput.value = Math.max(
|
||||||
|
minQuantity,
|
||||||
|
currentValue - stepQuantity,
|
||||||
|
);
|
||||||
|
updateHiddenQuantity();
|
||||||
|
});
|
||||||
|
|
||||||
|
$quantityInput.addEventListener("focusout", function () {
|
||||||
|
normalizeTypedQuantity(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
$quantityInput.addEventListener("keyup", function (event) {
|
||||||
|
if (event.keyCode === 13) {
|
||||||
|
normalizeTypedQuantity(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
normalizeTypedQuantity(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -13,6 +13,7 @@ import "@js/theme/components/quickview";
|
|||||||
import "@js/theme/components/product";
|
import "@js/theme/components/product";
|
||||||
import "@js/theme/components/cart/cart";
|
import "@js/theme/components/cart/cart";
|
||||||
import "@js/theme/components/cart/block-cart";
|
import "@js/theme/components/cart/block-cart";
|
||||||
|
import "@js/theme/components/product-miniature-form.js";
|
||||||
|
|
||||||
import usePasswordPolicy from "@js/theme/components/usePasswordPolicy";
|
import usePasswordPolicy from "@js/theme/components/usePasswordPolicy";
|
||||||
import prestashop from "prestashop";
|
import prestashop from "prestashop";
|
||||||
@@ -66,22 +67,6 @@ function initStickyHeader() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync product thumbs height with main images height
|
|
||||||
function syncThumbsHeight() {
|
|
||||||
const mainImages = document.querySelector(".product-main-images");
|
|
||||||
const thumbs = document.querySelector(".product-thumbs.swiper");
|
|
||||||
// Only apply if Swiper is vertical
|
|
||||||
if (mainImages && thumbs && thumbs.classList.contains("swiper-vertical")) {
|
|
||||||
thumbs.style.maxHeight = mainImages.offsetHeight + "px";
|
|
||||||
thumbs.style.height = mainImages.offsetHeight + "px";
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("load", syncThumbsHeight);
|
|
||||||
window.addEventListener("resize", syncThumbsHeight);
|
|
||||||
|
|
||||||
$(() => {
|
$(() => {
|
||||||
initStickyHeader();
|
initStickyHeader();
|
||||||
accLinksTriggerActive();
|
accLinksTriggerActive();
|
||||||
|
|||||||
Reference in New Issue
Block a user