diff --git a/README.md b/README.md index fc8383a..c78c8ff 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ new IdmHotspot({ * @property {object} source - Dane źródłowe dla hotspotu. + * @property {string} [source.link] - link do listingu z którego mają być pobierane produkty (NIEZALECANE) * @property {string} [source.hotspotType] - Typ hotspotu (np. "promotion"). * @property {number[]} [source.productsId] - Tablica ID produktów. * @property {number} [source.productsMenu] - Identyfikator menu produktów. @@ -162,6 +163,7 @@ new IdmHotspot({ * @property {string} id - Identyfikator elementu (np. "idmBlogHotspot1"). * @property {string} class - Klasy CSS używane do stylowania. * + * @attribute {string} data-link - link do listingu z którego mają być pobierane produkty (NIEZALECANE) * @attribute {string} data-hotspots-type - Typ hotspotu (np. "promotion"). * @attribute {number[]} data-products-id - Lista ID produktów (rozdzielona przecinkami). * @attribute {number} data-products-menu - Identyfikator menu produktów. diff --git a/klasa.js b/klasa.js index 669d930..5f8db15 100644 --- a/klasa.js +++ b/klasa.js @@ -438,6 +438,13 @@ class IdmHotspot{ // DOMYŚLNE OPCJE HOTSPOTA // ============================ static idmDefaultHotspotOptions = { + cssVariables: { + version: { + columnDesktop: 5, + columnTablet: 3, + columnMobile: 4, + } + }, options: { lazy: true, devMode: false, @@ -467,7 +474,7 @@ class IdmHotspot{ * Konstruktor * @param {object} object - Dane konfiguracyjne hotspotu */ - constructor({id, title, classes, placement, source, query, options = {}, hotspotEl, products}){ + constructor({id, title, classes, placement, source, query, options = {}, hotspotEl, products, cssVariables}){ this.id = id || ""; this.title = title || ""; this.classes = classes || ""; @@ -484,6 +491,10 @@ class IdmHotspot{ ...IdmHotspot.idmDefaultHotspotOptions.options, ...options, }; + this.cssVariables = { + ...IdmHotspot.idmDefaultHotspotOptions.cssVariables, + ...cssVariables, + }; // // this.hotspots = {}; @@ -568,15 +579,21 @@ class IdmHotspot{ async getHotspotData(){ if(this.products) return; try{ - const res = await fetch(`/graphql/v1/`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: this.query.graphFn(this.query.string), - }); - const data = await res.json(); - const products = data?.data?.[this.query.graphFn === IDM_HOTSPOTS_GQL ? "hotspots" : "products"]?.products; + let products; + if(this.source?.link){ + products = await this.xmlGetData(); + }else{ + const res = await fetch(`/graphql/v1/`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: this.query.graphFn(this.query.string), + }); + const data = await res.json(); + products = data?.data?.[this.query.graphFn === IDM_HOTSPOTS_GQL ? "hotspots" : "products"]?.products; + } + if(!products || !products.length) throw new Error(idmHotspotTextObject["Nie znaleziono produktów"]); this.products = products; @@ -671,7 +688,14 @@ class IdmHotspot{ markupVersions(prod){ if(!this.options?.selectVersion || !prod.group?.versions || prod.group?.versions?.length === 1 ) return ""; - const MAX_VERION_AMOUNT = 5; + let MAX_VERSION_AMOUNT = 5; + if(app_shop.vars.view > 2){ + MAX_VERSION_AMOUNT = this.cssVariables.version.columnDesktop; + }else if(app_shop.vars.view === 2){ + MAX_VERSION_AMOUNT = this.cssVariables.version.columnTablet; + }else if(app_shop.vars.view === 1){ + MAX_VERSION_AMOUNT = this.cssVariables.version.columnMobile; + } const sortedVersions = prod.group.versions.sort(function (a, b) { if (a.name < b.name) { @@ -686,9 +710,9 @@ class IdmHotspot{ return `
${sortedVersions.reduce((acc, val, index)=>{ - if(index + 1 > MAX_VERION_AMOUNT) return acc; + if(index + 1 > MAX_VERSION_AMOUNT) return acc; - if(index + 1 === MAX_VERION_AMOUNT && prod.group.versions.length > MAX_VERION_AMOUNT) return acc + `+${(prod.group.versions.length - MAX_VERION_AMOUNT) + 1}`; + if(index + 1 === MAX_VERSION_AMOUNT && prod.group.versions.length > MAX_VERSION_AMOUNT) return acc + `+${(prod.group.versions.length - MAX_VERSION_AMOUNT) + 1}`; return acc + `${val.name}`; },"")} @@ -1112,6 +1136,24 @@ class IdmHotspot{ observer.observe(this.hotspotEl); } + // ======================================================== + // USTAWIANIE ZMIENNYCH CSS DLA RAMKI + // ======================================================== + cssSetAllVariables(){ + this.cssVariableVersionColumnCount(); + } + + cssVariableVersionColumnCount(){ + this.cssSetVariable("--version-desktop-columns", this.cssVariables?.version?.columnDesktop || 5) + this.cssSetVariable("--version-tablet-columns", this.cssVariables?.version?.columnTablet || 3) + this.cssSetVariable("--version-mobile-columns", this.cssVariables?.version?.columnMobile || 4) + } + + + cssSetVariable(name, value){ + this.hotspotEl.style.setProperty(name, value); + } + // ======================================================== // FUNKCJE POMOCNICZE // ======================================================== @@ -1196,6 +1238,338 @@ class IdmHotspot{ prodEl.classList.remove("--loading"); } } + + // ======================================================== + // XML to GraphQL + // ======================================================== + xmlGetGrossNetPrices({priceNode, name}){ + return { + gross: { + value: priceNode?.getAttribute(`${name}`) !== null ? +priceNode.getAttribute(`${name}`) : undefined, + formatted: priceNode?.getAttribute(`${name}_formatted`) ?? undefined, + }, + net: { + value: priceNode?.getAttribute(`${name}_net`) !== null ? +priceNode.getAttribute(`${name}_net`) : undefined, + formatted: priceNode?.getAttribute(`${name}_net_formatted`) ?? undefined, + } + } + } + + xmlGetPriceFromNode(priceNode){ + const priceObj = { + price: { + gross: { + value: priceNode?.getAttribute("value") !== null ? +priceNode.getAttribute("value") : undefined, + formatted: priceNode?.getAttribute("price_formatted") ?? undefined, + }, + net: { + value: priceNode?.getAttribute("price_net") !== null ? +priceNode.getAttribute("price_net") : undefined, + formatted: priceNode?.getAttribute("price_net_formatted") ?? undefined, + } + }, + rebateCodeActive: priceNode?.getAttribute("rebate_code_active") === "y" ? true : false, + omnibusPrice: { + ...this.xmlGetGrossNetPrices({priceNode, name: "omnibus_price"}) + }, + // depositPrice: {}, + // depositPriceUnit: {}, + // totalDepositPrice: {}, + // totalDepositPriceUnit: {}, + omnibusPriceDetails: { + // unit: {}, + youSave: { + ...this.xmlGetGrossNetPrices({priceNode, name: "omnibus_yousave"}) + }, + youSavePercent: priceNode?.getAttribute("price_net") !== null ? +priceNode.getAttribute("omnibus_yousave_percent") : undefined, + omnibusPriceIsHigherThanSellingPrice: priceNode?.getAttribute("omnibus_price_is_higher_than_selling_price") === "true" ? true : false, + // newPriceEffectiveUntil: {}, + }, + tax: { + // worth: {}, + vatPercent: priceNode?.getAttribute("tax") !== null ? +priceNode.getAttribute("tax") : undefined, + // vatString: "" + }, + beforeRebate: { + ...this.xmlGetGrossNetPrices({priceNode, name: "beforerebate"}) + }, + // beforeRebateDetails: { + // youSave: { + + // }, + // youSavePercent: "", + // unit: {} + // }, + // crossedPrice: {}, + youSave: { + ...this.xmlGetGrossNetPrices({priceNode, name: "yousave"}) + }, + youSavePercent: priceNode?.getAttribute("yousave_percent") !== null ? +priceNode.getAttribute("yousave_percent") : undefined, + // unit: {}, + max: { + ...this.xmlGetGrossNetPrices({priceNode, name: "maxprice"}) + }, + // maxPriceUnit: {}, + // suggested: {}, + unitConvertedPrice: { + ...this.xmlGetGrossNetPrices({priceNode, name: "unit_converted_price"}) + }, + // rebateNumber: {}, + lastPriceChangeDate: priceNode?.getAttribute("last_price_change_date") ?? undefined, + // advancePrice: {}, + promotionDuration: { + promotionTill: { + date: { + date: priceNode?.getAttribute("last_price_change_date")?.split("-")?.[2] ?? undefined, + month: priceNode?.getAttribute("last_price_change_date")?.split("-")?.[1] ?? undefined, + year: priceNode?.getAttribute("last_price_change_date")?.split("-")?.[0] ?? undefined, + // weekDay: "", + // formatted: "" + }, + time: { + hour: priceNode?.getAttribute("promotiontillhour")?.split(":")?.[0] ?? undefined, + minutes: priceNode?.getAttribute("promotiontillhour")?.split(":")?.[1] ?? undefined, + seconds: priceNode?.getAttribute("promotiontillhour")?.split(":")?.[2] ?? undefined, + }, + // timestamp: "" + }, + // discountTill: {}, + // distinguishedTill: {}, + // specialTill: {}, + }, + // subscriptionPrice: {}, + }; + + return priceObj; + } + + xmlGetTraitsFromNode(node){ + const awardedParameters = []; + + node.querySelectorAll(":scope > trait").forEach(trait=>{ + const currParameter = awardedParameters.find(param=>param.id === trait.getAttribute("groupid")) || { + id: trait.getAttribute("groupid") ?? undefined, + name: trait.getAttribute("groupdescription") ?? undefined, + // description: "", + values: [], + // contextValue: "", + // search: { + // icon: "", + // }, + } + + currParameter.values.push({ + id: trait.getAttribute("traitid") ?? undefined, + name: trait.getAttribute("traitid") ?? undefined, + link: trait.getAttribute("link") ?? undefined, + // description: "", + // search: { + // icon: "" + // } + }) + + + if(currParameter.values.length === 1) awardedParameters.push(currParameter); + }); + + + return awardedParameters; + } + + async xmlGetData(){ + if(!this.source?.link) return null; + try{ + const res = await fetch(`${this.source?.link}${this.source?.link.includes("?") ? "&getProductXML=t" : "?getProductXML=t"}`); + const str = await res.text(); + const xml = new window.DOMParser().parseFromString(str, "text/xml"); + const now = new Date(); + + const allProducts = xml.querySelectorAll("product"); + if(allProducts.length === 0) throw new Error("Nie znaleziono produktów"); + + const data = []; + + + allProducts.forEach(prod=>{ + + // NODES + const priceNode = prod.querySelector(":scope >price"); + const firmNode = prod.querySelector(":scope > firm"); + const categoryNode = prod.querySelector(":scope > category"); + const seriesNode = prod.querySelector(":scope > series"); + const commentsNode = prod.querySelector(":scope > comments"); + const sizesNode = prod.querySelector(":scope > sizes"); + const versionsNode = prod.querySelector(":scope > versions"); + + // STREFY + const zones = []; + + if(prod.getAttribute("promo") === "yes") zones.push("promotion"); + if(prod.getAttribute("discount") === "yes") zones.push("discount"); + if(prod.getAttribute("distinguished") === "yes") zones.push("distinguished"); + if(prod.getAttribute("special") === "yes") zones.push("special"); + if(prod.getAttribute("new") === "yes") zones.push("new"); + if(prod.getAttribute("bestseller") === "yes") zones.push("bestseller"); + if(prod.getAttribute("subscription") === "yes") zones.push("subscription"); + + // ZDJĘCIA + const enclosuresImages = []; + prod.querySelectorAll(":scope > enclosures > images > enclosure").forEach(img=>{ + enclosuresImages.push({ + position: img.getAttribute("position") ?? undefined, + type: img.getAttribute("type") ?? undefined, + typeSecond: img.getAttribute("type_second") ?? undefined, + url: img.getAttribute("url") ?? undefined, + urlSecond: img.getAttribute("url_second") ?? undefined, + width: img.getAttribute("width") ?? undefined, + height: img.getAttribute("height") ?? undefined, + iconUrl: img.getAttribute("icon") ?? undefined, + iconUrlSecond: img.getAttribute("icon_second") ?? undefined, + iconWidth: img.getAttribute("icon_width") ?? undefined, + iconHeight: img.getAttribute("icon_height") ?? undefined, + mediumUrl: img.getAttribute("medium") ?? undefined, + mediumUrlSecond: img.getAttribute("medium_second") ?? undefined, + mediumWidth: img.getAttribute("medium_width") ?? undefined, + mediumHeight: img.getAttribute("medium_height") ?? undefined, + }) + }) + + // SIZES + const sizes = []; + sizesNode.querySelectorAll(":scope > size").forEach(size=>{ + const availabilityNode = size.querySelector(":scope > availability"); + const shippingTimeNode = availabilityNode.querySelector(":scope > shipping_time"); + const sizePriceNode = size.querySelector(":scope > price"); + const weightNode = size.querySelector(":scope > weight"); + + sizes.push({ + id: size?.getAttribute("type") ?? undefined, + name: size?.getAttribute("name") ?? undefined, + code: size?.getAttribute("code") ?? undefined, + codeProducer: size?.getAttribute("code_producer") ?? undefined, + codeExtern: size?.getAttribute("code_extern") ?? undefined, + amount: size?.getAttribute("amount") !== null ? +size.getAttribute("amount") : undefined, + amount_mo: size?.getAttribute("amount_mo") !== null ? +size.getAttribute("amount_mo") : undefined, + amount_mw: size?.getAttribute("amount_mw") !== null ? +size.getAttribute("amount_mw") : undefined, + amount_mp: size?.getAttribute("amount_mp") !== null ? +size.getAttribute("amount_mp") : undefined, + weight: weightNode?.getAttribute("g") !== null ? +weightNode.getAttribute("g") : undefined, + availability: { + visible: availabilityNode?.getAttribute("visible") === "y" ? true : false, + description: availabilityNode?.getAttribute("status_description") ?? undefined, + status: availabilityNode?.getAttribute("status") ?? undefined, + icon: availabilityNode?.getAttribute("status_gfx") ?? undefined, + deliveryDate: shippingTimeNode?.getAttribute("today") === "true" ? `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate().toString().padStart(2, 0)}` : availabilityNode?.getAttribute("delivery_date"), + minimumStockOfProduct: availabilityNode?.getAttribute("minimum_stock_of_product") ?? undefined, + // descriptionTel: "", + // iconTel: "", + }, + shipping: { + today: shippingTimeNode?.getAttribute("today") === "true" ? true : false, + }, + price: this.xmlGetPriceFromNode(sizePriceNode), + // amountWholesale: "", + }) + }) + + // VERSIONS + const versions = []; + versionsNode.querySelectorAll(":scope > version").forEach(ver=>{ + versions.push({ + id: ver?.getAttribute("id") !== null ? +ver?.getAttribute("id") : undefined, + name: ver?.getAttribute("name") ?? undefined, + icon: ver?.getAttribute("gfx") ?? undefined, + iconSecond: ver?.getAttribute("gfx_second") ?? undefined, + iconSmall: ver?.getAttribute("gfx_small") ?? undefined, + iconSmallSecond: ver?.getAttribute("gfx_small_second") ?? undefined, + productIcon: ver?.getAttribute("icon") ?? undefined, + productIconSecond: ver?.getAttribute("icon_second") ?? undefined, + productIconSmall: ver?.getAttribute("icon_small") ?? undefined, + productIconSmallSecond: ver?.getAttribute("icon_small_second") ?? undefined, + link: ver?.getAttribute("link") ?? undefined, + // parameterValues: [], + }); + }); + + // DATA + const typeParts = prod.getAttribute("product_type"); + data.push({ + id: prod?.getAttribute("id") !== null ? +prod?.getAttribute("id") : undefined, + type: typeParts[1] === "item" ? typeParts[0] : typeParts[1], + code: prod?.getAttribute("code") ?? undefined, + name: prod.querySelector(":scope > name")?.textContent ?? undefined, + versionName: prod.querySelector(":scope > versions")?.getAttribute("name") ?? undefined, + description: prod.querySelector(":scope > description")?.textContent ?? undefined, + // longDescription: prod.querySelector("vlongdescription")?.textContent ?? undefined, + // longDescriptionSections: "", + link: prod?.getAttribute("link"), + zones, + icon: prod.querySelector(":scope > icon")?.textContent ?? undefined, + iconSecond: prod.querySelector(":scope > icon_second")?.textContent ?? undefined, + iconSmall: prod.querySelector(":scope > icon_small")?.textContent ?? undefined, + iconSmallSecond: prod.querySelector(":scope > icon_small_second")?.textContent ?? undefined, + price: this.xmlGetPriceFromNode(priceNode), + unit: { + id: sizesNode?.getAttribute("unit_id") ?? undefined, + // name: "", + singular: sizesNode?.getAttribute("unit_single") !== null ? +sizesNode?.getAttribute("unit_single") : undefined, + plural: sizesNode?.getAttribute("unit_plural") ?? undefined, + fraction: sizesNode?.getAttribute("unit_fraction") ?? undefined, + sellBy: sizesNode?.getAttribute("unit_sellby") !== null ? +sizesNode?.getAttribute("unit_sellby") : undefined, + precision: sizesNode?.getAttribute("unit_precision") !== null ? +sizesNode?.getAttribute("unit_precision") : undefined, + unitConvertedFormat: sizesNode?.getAttribute("unit_converted_format") ?? undefined, + }, + producer: { + id: firmNode?.getAttribute("id") !== null ? +firmNode?.getAttribute("id") : undefined, + name: firmNode?.getAttribute("name") ?? undefined, + link: firmNode?.getAttribute("productslink") ?? undefined, + searchIcons: { + icon: firmNode?.getAttribute("icon") ?? undefined, + }, + // projectorIcons: {}, + }, + category: { + id: categoryNode?.getAttribute("id") !== null ? +categoryNode?.getAttribute("id") : undefined, + name: categoryNode?.getAttribute("name") ?? undefined, + link: categoryNode?.getAttribute("productslink") ?? undefined + }, + group: { + id: versionsNode?.getAttribute("id") !== null ? +versionsNode?.getAttribute("id") : undefined, + name: versionsNode?.getAttribute("name") ?? undefined, + displayAll: versionsNode?.getAttribute("display_all") === "true" ? true : false, + link: versionsNode?.getAttribute("link") ?? undefined, + versions, + groupParameters: this.xmlGetTraitsFromNode(versionsNode.querySelector(":scope > groupParameters")) + }, + opinion: { + rating: commentsNode?.getAttribute("avg") !== null ? +commentsNode?.getAttribute("avg") : undefined, + count: commentsNode?.getAttribute("count") !== null ? +commentsNode?.getAttribute("count") : undefined, + link: commentsNode?.getAttribute("link") ?? undefined + }, + enclosuresImages, + // enclosuresAttachments: [], + series: { + id: seriesNode?.getAttribute("id") !== null ? +seriesNode?.getAttribute("id") : undefined, + name: seriesNode?.getAttribute("name") ?? undefined, + link: seriesNode?.getAttribute("link") ?? undefined + }, + awardedParameters: this.xmlGetTraitsFromNode(prod.querySelector(":scope > traits")), + // parameteresWithContext: [], + sizes, + points: priceNode?.getAttribute("points") !== null ? +priceNode?.getAttribute("points") : undefined, + pointsReceive: priceNode?.getAttribute("points_recive") !== null ? +priceNode?.getAttribute("points_recive") : undefined, + // subscription: {}, + // bundled: [], + // responsibleEntity: {}, + }) + }) + + return data; + }catch(err){ + console.error(err); + return null; + } + } + + // ======================================================== // INICJALIZACJA // ======================================================== @@ -1225,6 +1599,10 @@ class IdmHotspot{ this.initEvents(); console.log(`Initialized hotspot #${this.id}`); + // Ustawienie wszystkich zmiennych CSS + this.cssSetAllVariables(); + + // ca if(typeof this.options?.callbackFn === "function") this.options?.callbackFn(); }catch(err){ console.error(idmHotspotTextObject["Wystąpił błąd z inicjalizacją. Proszę odśwież stronę"], err); @@ -1243,7 +1621,7 @@ class IdmHotspot{ this.initExternalFunctions(); try{ if(!this.products){ - if(!this?.query?.graphFn || !this?.query?.string) this.setQueryData(); + if((!this?.query?.graphFn || !this?.query?.string) && !this.source?.link) this.setQueryData(); // pobranie danych o produktach await this.getHotspotData(); @@ -1371,109 +1749,110 @@ class IdmHotspot{ */ 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; + constructor(swiper, selector) { + this.swiper = swiper?.slider?.slider ?? swiper?.slider ?? swiper; + this.selector = selector; + this.scrollbarEl = null; + this.progressEl = null; 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); -} + 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 // ======================================================== @@ -1511,6 +1890,7 @@ function idmShowTooltip(tooltipEl){ function idmHideTooltipTimer(tooltipEl){ return setTimeout(() => idmHideTooltip(tooltipEl), 1500); } + function idmHideTooltip(tooltipEl){ const tooltipContentEl = tooltipEl.querySelector(".idm_tooltip__content"); if (!tooltipContentEl) return; @@ -1526,7 +1906,6 @@ function idmHideTooltip(tooltipEl){ delete tooltipEl._onScroll; } - document.addEventListener("DOMContentLoaded", ()=>{ document.body.addEventListener("click", e=>{ const tooltipEl = e.target.closest(".idm_tooltip"); @@ -1582,8 +1961,8 @@ async function idmPrepareHotspotObject(selectedContainerEl){ selectedContainerEl.classList.add("--init"); const source = {}; - - if(selectedContainerEl.dataset?.hotspotsType) source.hotspotsType = selectedContainerEl.dataset.hotspotsType; + if(selectedContainerEl.dataset?.link) source.link = selectedContainerEl.dataset.link; + else if(selectedContainerEl.dataset?.hotspotsType) source.hotspotsType = selectedContainerEl.dataset.hotspotsType; else { if(selectedContainerEl.dataset?.productsId) source.productsId = selectedContainerEl.dataset.productsId.split(","); if(selectedContainerEl.dataset?.productsMenu) source.productsMenu = selectedContainerEl.dataset.productsMenu; @@ -1593,7 +1972,6 @@ async function idmPrepareHotspotObject(selectedContainerEl){ if(selectedContainerEl.dataset?.priceFrom && selectedContainerEl.dataset?.priceTo) source.priceRange = {from: +selectedContainerEl.dataset.priceFrom, to: +selectedContainerEl.dataset.priceTo}; } - if(Object.keys(source).length === 0){ console.error(); selectedContainerEl?.remove(); @@ -1611,7 +1989,6 @@ async function idmPrepareHotspotObject(selectedContainerEl){ new IdmHotspot(idmHotspotObj) } - document.querySelectorAll(".hotspot__wrapper.idm__hotspot").forEach(currentHotspot=>{ idmPrepareHotspotObject(currentHotspot) })