From 0030ce89287cb520791eae64542c9307f8e2cbcb Mon Sep 17 00:00:00 2001 From: "pawel.gaca" Date: Thu, 13 Nov 2025 12:20:16 +0100 Subject: [PATCH] swiperScrollbar + omnibusTooltip --- README.md | 9 +- klasa.js | 255 ++++++++++++++++++++++++++++++++++++++++++++++++----- ramka.txt | 21 ++--- style.css | 56 ------------ style.less | 125 ++++++++++++++++++++++++++ 5 files changed, 371 insertions(+), 95 deletions(-) delete mode 100644 style.css create mode 100644 style.less diff --git a/README.md b/README.md index 41ab67a..3142ea0 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ Funkcje js składające się na customowe ramki rekomendacji ## UWAGI PRZEDWDROŻENIOWE ## - kod zawiera **app_shop.fn.idmGetOmnibusDetails** który jest przerobionym kodem idosella app_shop.fn.getOmnibusDetails używanym w zwykłych ramkach rekomendacji +- kod zawiera FrontendComponents/helper-functions/tooltip i FrontendComponents/helper-functions/swiper-scrollbar +- kod używa naszego idmSetHeight, ale jest to wstawione jako metoda klasowa, nie jako coś osobnego. ### Pliki ### - **style.css** - style wstawiane do css @@ -54,8 +56,9 @@ Warto gdzieś później zapisać nową nazwę klasy np w opisie komponentu, albo - Wystąpił błąd z inicjalizacją. Proszę odśwież stronę - Nie znaleziono kontenera - Nie znaleziono metody graphql +- Najniższa cena -#### Przykład #### +#### Przykłady UŻYCIA #### ##### Jedna ramka - obiekt ###### ``` new IdmHotspot({ @@ -100,6 +103,8 @@ new IdmHotspot({ * - false = nieaktywny * - object = konfiguracja Swiper * @property {Function} options.callbackFn - Funkcja callback która dzieje się po wywołaniu wszystkiego włącznie ze swiperem + * @property {boolean} options.swiperScrollbar - Czy włączać scrollbar w swiperze - DO DZIAŁANIA WYMAGA WŁĄCZONEGO SWIPERA + * @property {boolean} options.omnibusTooltip - Czy wyświetlać omnibusa w formie tooltip * * @type {Hotspot[]} */ @@ -125,6 +130,8 @@ new IdmHotspot({ idmInsertHotspotElement(document.getElementByid("idmBlogHotspot1")); ``` + +Żeby zmienić resztę ustawień trzeba zmieniać defaultowe ustawienia! #### Wszystkie możliwe dane HTML #### ``` /** diff --git a/klasa.js b/klasa.js index 65603d6..90c5b20 100644 --- a/klasa.js +++ b/klasa.js @@ -8,6 +8,8 @@ const idmHotspotTextObject = { []: , []: , []: , + []: , + []: , []: , []: , []: , @@ -278,7 +280,7 @@ app_shop.fn.idmGetOmnibusDetails = (options) => { // Aktywny kod rabatowy if (app_shop.vars.omnibus?.rebateCodeActivate && sizeData.price?.rebateCodeActive) { classes.add.push('--omnibus-code'); - activeLabel.rebateCodeActive = `${idmHotspotTextObject["Kod rabatowy"]}`; + activeLabel.rebateCodeActive = `${omnibusDetailsTxt?.['Kod rabatowy']}`; classes.remove = classes.remove.filter((item) => item !== '--omnibus-code'); } // Skrócona wersja omnibusa, gdy aktywny kod rabatowy @@ -301,19 +303,13 @@ app_shop.fn.idmGetOmnibusDetails = (options) => { } // label okazja if ((!higher || newDate) && !activeLabel?.rebateCodeActive) { - activeLabel.bargain = `${idmHotspotTextObject["Okazja"]}`; + activeLabel.bargain = `${omnibusDetailsTxt?.['Okazja']}`; } // label promocja if (Object.keys(activeLabel)?.length === 0) { - activeLabel.bargain = `${idmHotspotTextObject["Promocja"]}`; + activeLabel.bargain = `${omnibusDetailsTxt?.['Promocja']}`; } - // // labele zones - // if(productData.zones.find(zone => zone ==="bestseller")) activeLabel.bestseller = `${idmHotspotTextObject["Bestseller"]}`; - // if(productData.zones.find(zone => zone ==="news")) activeLabel.news = `${idmHotspotTextObject["Nowość"]}`; - - - let omnibusPercentSign = ''; if (higher) { omnibusPercentSign = '-'; @@ -325,7 +321,7 @@ app_shop.fn.idmGetOmnibusDetails = (options) => { price: omnibusPrice, visible: true, percent: omnibusPercent, - html: `${idmHotspotTextObject['Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki']}: ${omnibusPrice}${omnibusPercent}`, + html: `${omnibusDetailsTxt['Najniższa cena produktu w okresie 30 dni przed wprowadzeniem obniżki']}: ${omnibusPrice}${omnibusPercent}`, }; const max = (maxPrice) ? { @@ -333,7 +329,7 @@ app_shop.fn.idmGetOmnibusDetails = (options) => { price: maxPrice, visible: true, percent: `-${sizeData.price.youSavePercent}%`, - html: `${idmHotspotTextObject['Cena regularna']}: + html: `${omnibusDetailsTxt['Cena regularna']}: ${maxPrice}-${sizeData.price.youSavePercent}%`, }, } : {}; @@ -343,7 +339,7 @@ app_shop.fn.idmGetOmnibusDetails = (options) => { price: beforeRebatePrice, visible: !!classes.add.includes('--omnibus-code'), percent: `-${sizeData.price.beforeRebateDetails?.youSavePercent}%`, - html: `${idmHotspotTextObject['Cena bez kodu']}: + html: `${omnibusDetailsTxt['Cena bez kodu']}: ${beforeRebatePrice} -${sizeData.price.beforeRebateDetails?.youSavePercent}%`, }, @@ -354,12 +350,17 @@ app_shop.fn.idmGetOmnibusDetails = (options) => { date: newDate, price: maxPrice, visible: !!classes.add.includes('--omnibus-new-price'), - html: `${idmHotspotTextObject['Cena nadchodząca od']} + html: `${omnibusDetailsTxt['Cena nadchodząca od']} ${newDate}: ${maxPrice}`, }, } : {}; - + const omnibusLabel = { + omnibusLabel: { + name: Object.keys(activeLabel)?.[0] || '', + html: activeLabel[Object.keys(activeLabel)?.[0]], + }, + }; return { classes, @@ -367,7 +368,7 @@ app_shop.fn.idmGetOmnibusDetails = (options) => { ...max, ...beforeRebate, ...newPriceEffectiveUntil, - activeLabel, + ...omnibusLabel, }; }; @@ -409,7 +410,9 @@ class IdmHotspot{ lazy: true, addToBasket: true, // true, false, "range" swiper: true, - callbackFn: ()=>{} + callbackFn: ()=>{}, + swiperScrollbar: false, + omnibusTooltip: false, } } /** @@ -523,7 +526,7 @@ class IdmHotspot{ */ markupProduct(prod){ // IDM DO POPRAWKI - const prodExchangedData = app_shop.fn?.idmGetOmnibusDetails({productData: prod}); + const prodExchangedData = app_shop?.fn?.getOmnibusDetails?.({productData: prod}) || app_shop.fn?.idmGetOmnibusDetails({productData: prod}); // markup pojedynczego produktu let singleMarkup = ""; @@ -611,11 +614,39 @@ class IdmHotspot{ ${price.value === 0 ? `${idmHotspotTextObject["Cena na telefon"]}` : ""} ${prodExchangedData?.beforeRebate?.visible ? `${prodExchangedData?.beforeRebate?.html}` : ""} ${prodExchangedData?.newPriceEffectiveUntil?.visible ? `${prodExchangedData?.newPriceEffectiveUntil?.html}` : ""} - ${prodExchangedData?.omnibus?.visible ? `${prodExchangedData?.omnibus?.html}` : ""} + ${this.markupOmnibus(prodExchangedData?.omnibus)} ${prodExchangedData?.max?.visible ? `${prodExchangedData?.max?.html}` : ""} `; } + /** + * Tworzy znacznik HTML dla omnibusa. + * + * @param {object} omnibus - dane omnibusa + * @param {string} omnibus.html - kod html zwykłego omnibusa + * @param {string} omnibus.percent - % przeceny omnibusa + * @param {string} omnibus.price - cena omnibusa + * @param {boolean} omnibus.visible - czy omnibus jest widoczny + * @returns {string} Zwraca HTML dla sekcji omnibusa lub pusty string, jeśli niewidoczny. + */ + + markupOmnibus(omnibus){ + if(!omnibus?.visible) return ""; + + if(!this.options.omnibusTooltip) return `${omnibus?.html}`; + else return ` + + ${idmHotspotTextObject[]}: + ${omnibus.price} + ${omnibus.percent} + + +

${idmHotspotTextObject[]}

+
+
+ `; + } + markupAddToBasket(prod){ let markup = ""; if(!this.options.addToBasket) return markup; @@ -847,7 +878,7 @@ class IdmHotspot{ if (selector) adjustAllHeights(selector) if (selectors?.length) selectors.forEach(adjustAllHeights) } - // ======================================================== + // ======================================================== // INICJALIZACJA // ======================================================== @@ -929,6 +960,8 @@ class IdmHotspot{ options: this.options.swiper, }); await selectedSwiper.init(); + + if(this.options.swiperScrollbar) new IdmSwiperProgress(selectedSwiper, `#${this.id} .swiper`); } }catch(err){ console.error(idmHotspotTextObject["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"], err); @@ -945,7 +978,7 @@ class IdmHotspot{ } initSingleEvent(prodEl){ // DODAWANIE DO KOSZYKA - if(this?.options?.addToBasket){ + if(this.options?.addToBasket){ const addToBasketEl = prodEl.querySelector("form.add_to_basket"); if(!addToBasketEl) return; addToBasketEl.addEventListener("submit", this.handleAddToBasket); @@ -956,6 +989,14 @@ class IdmHotspot{ addToBasketEl.querySelector(".idm-products-banner__qty-input")?.addEventListener("input",this.handleQuantityInputChange); } } + // Tooltip + if(this.options?.omnibusTooltip){ + const tooltipEl = prodEl.querySelector(".idm_tooltip"); + + tooltipEl.addEventListener("click", ()=>{ + this.showTooltip(tooltipEl); + }) + } } /** @@ -981,6 +1022,178 @@ class IdmHotspot{ } } +/* +============================================================== + SWIPER PASEK +============================================================== +*/ + +class IdmSwiperProgress { +constructor(swiper, selector) { + this.swiper = swiper?.slider?.slider ?? swiper?.slider ?? swiper; + this.selector = selector; + this.scrollbarEl = null; + this.progressEl = null; + this.isDragging = false; + + this.init(); +} + +init() { + const el = document.querySelector(this.selector); + if (!el || el.querySelector(".idm-scrollbar")) return; + + el.insertAdjacentHTML( + "beforeend", + `
` + ); + + this.scrollbarEl = el.querySelector(".idm-scrollbar"); + this.progressEl = this.scrollbarEl.querySelector(".idm-progress"); + + this.updateBarWidth(); + this.addDragTracking(); + + this.swiper.on("progress", () => this.updateProgress()); + this.swiper.on("breakpoint", () => {this.updateBarWidth()}); +} + +updateBarWidth() { + const { slidesPerGroup, slidesPerView } = this.swiper.params; + const totalSlides = this.swiper.slides.length; + + const progressWidth = 100 / (((totalSlides - slidesPerView) / slidesPerGroup) + 1); + this.progressEl.style.width = `${progressWidth}%`; + + if (progressWidth >= 100 || progressWidth <= 0) this.scrollbarEl.style.display = "none"; + else this.scrollbarEl.style.display = ""; +} + +updateProgress() { + const progress = this.swiper.progress; + const { slidesPerGroup, slidesPerView } = this.swiper.params; + const totalSlides = this.swiper.slides.length; + + const progressWidth = 100 / (((totalSlides - slidesPerView) / slidesPerGroup) + 1); + const newLeft = (100 - progressWidth) * progress; + this.progressEl.style.left = `${Math.min( + 100 - progressWidth, + Math.max(0, newLeft) + )}%`; +} + +addDragTracking() { + const handle = this.progressEl; + let grabOffset = 0; + let scrollbarWidth = 0; + let handleWidth = 0; + + const startDrag = (e) => { + this.isDragging = true; + this.scrollbarEl.classList.add("--drag-start"); + + const rect = this.scrollbarEl.getBoundingClientRect(); + const handleRect = handle.getBoundingClientRect(); + scrollbarWidth = rect.width; + handleWidth = handleRect.width; + + const clientX = e.touches ? e.touches[0].clientX : e.clientX; + grabOffset = clientX - handleRect.left; + + document.addEventListener("mousemove", handleDrag); + document.addEventListener("mouseup", stopDrag); + document.addEventListener("touchmove", handleDrag); + document.addEventListener("touchend", stopDrag); + }; + + const handleDrag = (e) => { + if (!this.isDragging) return; + const clientX = e.touches ? e.touches[0].clientX : e.clientX; + const rect = this.scrollbarEl.getBoundingClientRect(); + let newLeftPx = clientX - rect.left - grabOffset; + const maxLeft = scrollbarWidth - handleWidth; + newLeftPx = Math.max(0, Math.min(maxLeft, newLeftPx)); + const progress = newLeftPx / maxLeft; + this.swiper.setProgress(progress, 0); + }; + + const stopDrag = () => { + if (!this.isDragging) return; + this.isDragging = false; + document.removeEventListener("mousemove", handleDrag); + document.removeEventListener("mouseup", stopDrag); + document.removeEventListener("touchmove", handleDrag); + document.removeEventListener("touchend", stopDrag); + this.scrollbarEl.classList.remove("--drag-start"); + this.swiper.slideReset(400); + }; + + handle.addEventListener("mousedown", startDrag); + handle.addEventListener("touchstart", startDrag); +} +} +// ======================================================== +// TOOLTIP +// ======================================================== +function idmShowTooltip(tooltipEl){ + const tooltipContentEl = tooltipEl.querySelector(".idm_tooltip__content"); + if(!tooltipContentEl) return; + + tooltipContentEl.classList.add("--visible"); + + // Logika pokazywania się i chowania tooltipa + let timeoutVar; + + function onMouseLeave() { + timeoutVar = idmHideTooltipTimer(tooltipEl); + } + + function onMouseEnter() { + clearTimeout(timeoutVar); + } + + function onScroll() { + idmHideTooltip(tooltipEl); + } + + // Store references for later removal + tooltipEl._onMouseLeave = onMouseLeave; + tooltipEl._onMouseEnter = onMouseEnter; + tooltipEl._onScroll = onScroll; + + tooltipEl.addEventListener("mouseleave", onMouseLeave); + tooltipEl.addEventListener("mouseenter", onMouseEnter); + document.addEventListener("scroll", onScroll); +} + +function idmHideTooltipTimer(tooltipEl){ + return setTimeout(() => idmHideTooltip(tooltipEl), 1500); +} +function idmHideTooltip(tooltipEl){ + const tooltipContentEl = tooltipEl.querySelector(".idm_tooltip__content"); + if (!tooltipContentEl) return; + + tooltipContentEl.classList.remove("--visible"); + + tooltipEl.removeEventListener("mouseleave", tooltipEl._onMouseLeave); + tooltipEl.removeEventListener("mouseenter", tooltipEl._onMouseEnter); + document.removeEventListener("scroll", tooltipEl._onScroll); + + delete tooltipEl._onMouseLeave; + delete tooltipEl._onMouseEnter; + delete tooltipEl._onScroll; +} + + +document.addEventListener("DOMContentLoaded", ()=>{ + document.body.addEventListener("click", e=>{ + const tooltipEl = e.target.closest(".idm_tooltip"); + if(!e.target.closest(".idm_tooltip__info_icon") || !tooltipEl) return; + + e.preventDefault(); + idmShowTooltip(tooltipEl); + }); +}); // new IdmHotspot({ // id: "idmTestHotspot1", // title: "tescik", @@ -1031,8 +1244,6 @@ async function idmPrepareHotspotObject(selectedContainerEl){ if(selectedContainerEl?.dataset?.lazy) idmHotspotObj.options = {lazy: selectedContainerEl?.dataset?.lazy === "true" ? true : false}; - - new IdmHotspot(idmHotspotObj) } diff --git a/ramka.txt b/ramka.txt index 188ec8c..44df444 100644 --- a/ramka.txt +++ b/ramka.txt @@ -1,12 +1,11 @@ 1. Ramka - -- ulubione? + porównywarka? (wymagają zmiany w komponencie idosella) -- zakres cen????????????? - wybór rozmiaru/wersji?? -- Wybór kolorystyczny +- ulubione? + porównywarka? (wymagają zmiany w komponencie idosella) + +- zakres cen????????????? +- Wybór kolorystyczny??? - AAAAA - banner na hotspocie -- Pasek jak na Pasiastym Parzystnokopytnym Kosmetyku @@ -15,14 +14,4 @@ Stara ramka - getProductXML=t -- slick - - -bramka z hotspots jeszcze nie działa bad request - - - -Get-Content 1graphQL.js,2funkcje.js,3markup.js,4init.js,5ainsertHotspotHTML.js,5binsertHotspotObject.js | Set-Content bundle.js - - -Get-Content sklad/1graphQL.js,sklad/2funkcje.js,sklad/3markup.js,sklad/4init.js,sklad/5ainsertHotspotHTML.js,sklad/5binsertHotspotObject.js | Set-Content bundle.js \ No newline at end of file +- slick \ No newline at end of file diff --git a/style.css b/style.css deleted file mode 100644 index 5a1beea..0000000 --- a/style.css +++ /dev/null @@ -1,56 +0,0 @@ -.idm__hotspot .add_to_basket{ - display: flex; -} -.idm__hotspot .add_to_basket.--range{ - flex-direction: column; -} -.idm__hotspot .idm-products-banner__qty{ - display: flex; - justify-content: space-between; - width: 100%; - align-items: center; - gap: 1rem; -} -.idm__hotspot .idm-products-banner__qty-input{ - height: 3rem; - text-align: center; - border: 1px solid #ccc; - width: 60%; - max-width: unset; - min-width: unset; -} -.idm__hotspot .idm-products-banner__qty button{ - background: #000; - height: 3rem; - width: 3rem; - color: #fff; - border-radius: 0.5rem; - min-width: 3rem; -} - @keyframes idm-skeleton-loading { - to { - background-position-x: -200%; - } - } -.idm__hotspot.idm-loading{ - position: relative; - overflow: hidden; - - transition: none; - border-radius: 8px; - background: #eee; - background: linear-gradient(110deg, #ececec 8%, #f5f5f5 18%, #ececec 33%); - background-size: 200% 100%; - animation: 1.5s idm-skeleton-loading linear infinite; - width: 100%; - height: 50rem; -} -.idm__hotspot.idm-loading .hotspot{ - opacity: 0; -} -@media (max-width: 756px) { - .idm__hotspot.idm-loading{ - width: 100%; - height: 50rem; - } -} \ No newline at end of file diff --git a/style.less b/style.less new file mode 100644 index 0000000..01b9231 --- /dev/null +++ b/style.less @@ -0,0 +1,125 @@ +.idm__hotspot .add_to_basket{ + display: flex; +} +.idm__hotspot .add_to_basket.--range{ + flex-direction: column; +} +.idm__hotspot .idm-products-banner__qty{ + display: flex; + justify-content: space-between; + width: 100%; + align-items: center; + gap: 1rem; +} +.idm__hotspot .idm-products-banner__qty-input{ + height: 3rem; + text-align: center; + border: 1px solid #ccc; + width: 60%; + max-width: unset; + min-width: unset; +} +.idm__hotspot .idm-products-banner__qty button{ + background: #000; + height: 3rem; + width: 3rem; + color: #fff; + border-radius: 0.5rem; + min-width: 3rem; +} + @keyframes idm-skeleton-loading { + to { + background-position-x: -200%; + } + } +.idm__hotspot.idm-loading{ + position: relative; + overflow: hidden; + + transition: none; + border-radius: 8px; + background: #eee; + background: linear-gradient(110deg, #ececec 8%, #f5f5f5 18%, #ececec 33%); + background-size: 200% 100%; + animation: 1.5s idm-skeleton-loading linear infinite; + width: 100%; + height: 50rem; +} +.idm__hotspot.idm-loading .hotspot{ + opacity: 0; +} +@media (max-width: 756px) { + .idm__hotspot.idm-loading{ + width: 100%; + height: 50rem; + } +} + +/* SWIPER PROGRESS */ +.idm-scrollbar{ + flex: 1; + height: 2px!important; + position: relative!important; + margin-top: 2rem; + cursor: grab; +} +.idm-scrollbar.--drag-start .idm-progress{ + transition: none; +} +.idm-progress{ + position: absolute; + z-index: 10; + height: calc(100% + 2px); + background-color: #0d0d0d; + left: 0; + top: -1px; + transition: all 0.3s; + border-radius: 5px; +} + + + +/* tooltip */ +.idm_tooltip{ + --tooltip-background: #111; + --tooltip-border: #999; + --tooltip-color: #fff; + + position: relative; + &__info_icon{ + color: var(--tooltip-border); + display: inline-block; + &:before{ + content: ""; + display: block; + width: 1.2rem; + height: 1.2rem; + margin-left: 0.5rem; + cursor: pointer; + vertical-align: bottom; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' class='idm_tooltip__info_icon' viewBox='0 0 16 16'%3E%3Cpath d='M8,0a8,8,0,1,0,8,8A8,8,0,0,0,8,0ZM8,14.667A6.667,6.667,0,1,1,14.667,8,6.667,6.667,0,0,1,8,14.667Z' fill='currentColor'%3E%3C/path%3E%3Cpath d='M11.333,10h-.667a.667.667,0,1,0,0,1.333h.667v4a.667.667,0,0,0,1.333,0v-4A1.333,1.333,0,0,0,11.333,10Z' transform='translate(-3.333 -3.333)' fill='currentColor'%3E%3C/path%3E%3Ccircle cx='1' cy='1' r='1' transform='translate(7 3.333)' fill='currentColor'%3E%3C/circle%3E%3C/svg%3E"); + background-size: cover; + } + } + &__content{ + position: absolute; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s; + border-radius: 5px; + background: var(--tooltip-background); + color: var(--tooltip-color); + border: 1px solid var(--tooltip-border); + bottom: 150%; + padding: 0.5rem 1rem; + @media @tablet{ + &.--one-line{ + white-space: nowrap; + } + } + &.--visible{ + opacity: 1; + pointer-events: auto; + } + } +}