1// src/utils/canvas-carousel-detector.js 2class CANVASCarouselDetector { 3 constructor() { 4 this.detectedCarousels = new Map(); 5 this.observer = null; 6 this.Swiper = null; 7 } 8 9 10 async init() { 11 try { 12 13 await this.loadSwiper(); 14 15 16 if (document.readyState === 'loading') { 17 document.addEventListener('DOMContentLoaded', () => this.startDetection()); 18 } else { 19 this.startDetection(); 20 } 21 } catch (error) { 22 console.error('â Failed to initialize CANVAS Carousel Detector:', error); 23 } 24 } 25 26 27 async loadSwiper() { 28 return new Promise((resolve, reject) => { 29 30 if (window.Swiper) { 31 this.Swiper = window.Swiper; 32 //console.log('Swiper already available globally'); 33 resolve(); 34 return; 35 } 36 37 38 const script = document.createElement('script'); 39 script.type = 'module'; 40 script.innerHTML = ` 41 import Swiper from 'https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.mjs'; 42 window.Swiper = Swiper; 43 window.SwiperLoaded = true; 44 `; 45 46 script.onload = () => { 47 48 const checkSwiper = () => { 49 if (window.Swiper && window.SwiperLoaded) { 50 this.Swiper = window.Swiper; 51 //console.log('Swiper loaded from CDN'); 52 resolve(); 53 } else { 54 setTimeout(checkSwiper, 100); 55 } 56 }; 57 checkSwiper(); 58 }; 59 60 script.onerror = () => { 61 console.error('â Failed to load Swiper from CDN'); 62 reject(new Error('Failed to load Swiper')); 63 }; 64 65 document.head.appendChild(script); 66 67 if (!document.querySelector('link[href*="swiper"]')) { 68 const link = document.createElement('link'); 69 link.rel = 'stylesheet'; 70 link.href = 'https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css'; 71 document.head.appendChild(link); 72 } 73 }); 74 } 75 76 startDetection() { 77 this.scanForCarousels(); 78 79 this.setupMutationObserver(); 80 81 //console.log('CANVAS Carousel Detector initialized'); 82 } 83 84 scanForCarousels() { 85 const carousels = document.querySelectorAll('.carousel.slide'); 86 87 carousels.forEach(carousel => { 88 if (!this.detectedCarousels.has(carousel.id)) { 89 this.convertCarousel(carousel); 90 } 91 }); 92 } 93 94 setupMutationObserver() { 95 this.observer = new MutationObserver((mutations) => { 96 mutations.forEach((mutation) => { 97 mutation.addedNodes.forEach((node) => { 98 if (node.nodeType === Node.ELEMENT_NODE) { 99 if (node.classList && node.classList.contains('carousel') && node.classList.contains('slide')) { 100 this.convertCarousel(node); 101 } 102 103 const childCarousels = node.querySelectorAll && node.querySelectorAll('.carousel.slide'); 104 if (childCarousels && childCarousels.length > 0) { 105 childCarousels.forEach(carousel => this.convertCarousel(carousel)); 106 } 107 } 108 }); 109 }); 110 }); 111 112 this.observer.observe(document.body, { 113 childList: true, 114 subtree: true 115 }); 116 } 117 118 convertCarousel(carousel) { 119 if (!carousel.id || this.detectedCarousels.has(carousel.id)) { 120 return; 121 } 122 123 //console.log('Converting CANVAS carousel:', carousel.id); 124 125 try { 126 const settings = this.extractCarouselSettings(carousel); 127 128 const swiperStructure = this.createSwiperStructure(carousel, settings); 129 130 carousel.parentNode.replaceChild(swiperStructure, carousel); 131 132 setTimeout(() => { 133 const swiper = this.initializeSwiper(swiperStructure, settings); 134 135 if (swiper) { 136 this.detectedCarousels.set(carousel.id, { 137 originalElement: carousel, 138 swiperElement: swiperStructure, 139 swiperInstance: swiper, 140 settings: settings 141 }); 142 143 //console.log('Successfully converted carousel:', carousel.id); 144 } 145 }, 200); 146 147 } catch (error) { 148 console.error('â Failed to convert carousel:', carousel.id, error); 149 } 150 } 151 152 extractCarouselSettings(carousel) { 153 const settings = { 154 id: carousel.id, 155 autoplay: carousel.dataset.interval && carousel.dataset.interval !== 'false', 156 interval: parseInt(carousel.dataset.interval) || 3000, 157 pause: carousel.dataset.pause === 'true', 158 wrap: carousel.dataset.wrap === 'true', 159 effect: carousel.classList.contains('carousel-fade') ? 'fade' : 'slide', 160 width: carousel.dataset.width || '100%', 161 height: carousel.dataset.height || 'auto', 162 showIndicators: !!carousel.querySelector('.carousel-indicators'), 163 showControls: !!carousel.querySelector('.carousel-control') 164 }; 165 166 return settings; 167 } 168 169 createSwiperStructure(carousel, settings) { 170 171 const swiperContainer = document.createElement('div'); 172 swiperContainer.className = 'swiper canvas-converted-carousel'; 173 swiperContainer.id = settings.id; 174 swiperContainer.style.width = settings.width; 175 swiperContainer.style.height = settings.height; 176 177 const swiperWrapper = document.createElement('div'); 178 swiperWrapper.className = 'swiper-wrapper'; 179 180 const carouselItems = carousel.querySelectorAll('.carousel-inner .item');
181 carouselItems.forEach((item, index) => { 182 const slide = document.createElement('div'); 183 slide.className = 'swiper-slide'; 184 185 slide.innerHTML = item.innerHTML; 186 187 if (item.style.cssText) { 188 slide.style.cssText = item.style.cssText; 189 } 190 191 swiperWrapper.appendChild(slide); 192 }); 193 194 swiperContainer.appendChild(swiperWrapper); 195 196 if (settings.showIndicators) { 197 const pagination = document.createElement('div'); 198 pagination.className = 'swiper-pagination'; 199 pagination.id = `${settings.id}-pagination`; 200 swiperContainer.appendChild(pagination); 201 } 202 203 if (settings.showControls) { 204 const prevButton = document.createElement('div'); 205 prevButton.className = 'swiper-button-prev'; 206 prevButton.id = `${settings.id}-prev`; 207 208 const nextButton = document.createElement('div'); 209 nextButton.className = 'swiper-button-next'; 210 nextButton.id = `${settings.id}-next`; 211 212 swiperContainer.appendChild(prevButton); 213 swiperContainer.appendChild(nextButton); 214 } 215 216 return swiperContainer; 217 } 218 219 initializeSwiper(swiperElement, settings) { 220 if (!this.Swiper) { 221 console.error('â Swiper not available'); 222 return null; 223 } 224 225 const slideCount = swiperElement.querySelectorAll('.swiper-slide').length; 226 227 const enableLoop = settings.wrap && 228 slideCount > 2 && 229 settings.effect !== 'fade'; 230 231 const swiperConfig = { 232 slidesPerView: 1, 233 spaceBetween: 0, 234 loop: enableLoop, 235 speed: 600, 236 grabCursor: true, 237 watchOverflow: true, 238 centeredSlides: false, 239 initialSlide: 0, 240 effect: settings.effect === 'fade' ? 'fade' : 'slide', 241 fadeEffect: settings.effect === 'fade' ? { 242 crossFade: true 243 } : undefined, 244 245 slideToClickedSlide: false, 246 touchRatio: 1, 247 touchAngle: 45, 248 shortSwipes: true, 249 longSwipes: true, 250 251 navigation: settings.showControls ? { 252 nextEl: `#${settings.id}-next`, 253 prevEl: `#${settings.id}-prev`, 254 hideOnClick: false, 255 } : false, 256 257 pagination: settings.showIndicators ? { 258 el: `#${settings.id}-pagination`, 259 clickable: true, 260 type: 'bullets', 261 dynamicBullets: false, 262 hideOnClick: false, 263 } : false, 264 265 autoplay: settings.autoplay ? { 266 delay: settings.interval, 267 pauseOnMouseEnter: settings.pause, 268 disableOnInteraction: false, 269 stopOnLastSlide: !enableLoop, 270 reverseDirection: false, 271 } : false, 272 273 // Events 274 on: { 275 init: (swiper) => { 276 setTimeout(() => { 277 swiper.update(); 278 if (swiper.pagination) { 279 swiper.pagination.render(); 280 swiper.pagination.update(); 281 } 282 }, 100); 283 }, 284 slideChange: (swiper) => { 285 286 const actualIndex = enableLoop ? swiper.realIndex : swiper.activeIndex; 287 //console.log(`Slide changed in ${settings.id}: activeIndex=${swiper.activeIndex}, realIndex=${actualIndex}`); 288 289 const event = new CustomEvent('canvas-carousel-slide-change', { 290 detail: { 291 id: settings.id, 292 activeIndex: swiper.activeIndex, 293 realIndex: actualIndex, 294 swiper: swiper 295 } 296 }); 297 document.dispatchEvent(event); 298 }, 299 navigationPrev: (swiper) => { 300 //console.log(`Previous navigation clicked for ${settings.id}, going to: ${enableLoop ? swiper.realIndex : swiper.activeIndex}`); 301 }, 302 navigationNext: (swiper) => { 303 //console.log(`Next navigation clicked for ${settings.id}, going to: ${enableLoop ? swiper.realIndex : swiper.activeIndex}`); 304 }, 305 paginationClick: (swiper, event) => { 306 // console.log(`Pagination clicked for ${settings.id}`); 307 } 308 } 309 }; 310 311 // Initialize Swiper 312 let swiper; 313 try { 314 swiper = new this.Swiper(swiperElement, swiperConfig); 315 316 if (settings.showControls) { 317 const prevBtn = document.getElementById(`${settings.id}-prev`); 318 const nextBtn = document.getElementById(`${settings.id}-next`); 319 320 if (prevBtn && nextBtn) { 321 322 prevBtn.replaceWith(prevBtn.cloneNode(true)); 323 nextBtn.replaceWith(nextBtn.cloneNode(true)); 324 325 const freshPrevBtn = document.getElementById(`${settings.id}-prev`); 326 const freshNextBtn = document.getElementById(`${settings.id}-next`); 327 328 freshPrevBtn.addEventListener('click', (e) => { 329 e.preventDefault(); 330 e.stopPropagation(); 331 //console.log('Manual prev click'); 332 333 if (enableLoop) { 334 swiper.slidePrev(); 335 } else { 336 const currentIndex = swiper.activeIndex; 337 const targetIndex = currentIndex === 0 ? slideCount - 1 : currentIndex - 1; 338 swiper.slideTo(targetIndex); 339 } 340 }); 341 342 freshNextBtn.addEventListener('click', (e) => { 343 e.preventDefault(); 344 e.stopPropagation(); 345 //console.log('Manual next click'); 346 347 if (enableLoop) { 348 swiper.slideNext(); 349 } else { 350 const currentIndex = swiper.activeIndex; 351 const targetIndex = currentIndex === slideCount - 1 ? 0 : currentIndex + 1; 352 swiper.slideTo(targetIndex); 353 } 354 }); 355 } else { 356 console.warn(`Navigation buttons not found for ${settings.id}`); 357 } 358 } 359 360 361 if (settings.showIndicators) { 362 const pagination = document.getElementById(`${settings.id}-pagination`); 363 if (pagination) { 364 //console.log(`Pagination found for ${settings.id}`); 365 366 setTimeout(() => { 367 const bullets = pagination.querySelectorAll('.swiper-pagination-bullet');
368 bullets.forEach((bullet, index) => { 369 bullet.addEventListener('click', (e) => { 370 e.preventDefault(); 371 e.stopPropagation(); 372 // console.log(`Manual pagination click to slide ${index}`); 373 swiper.slideTo(index); 374 }); 375 }); 376 }, 200); 377 } else { 378 console.warn(`Pagination not found for ${settings.id}`); 379 } 380 } 381 382 } catch (error) { 383 console.error('â Failed to initialize Swiper:', error); 384 return null; 385 } 386 387 return swiper; 388 } 389 390 getCarousel(carouselId) { 391 return this.detectedCarousels.get(carouselId) || null; 392 } 393 394 getAllCarousels() { 395 return this.detectedCarousels; 396 } 397 398 destroyCarousel(carouselId) { 399 const carouselData = this.detectedCarousels.get(carouselId); 400 if (carouselData && carouselData.swiperInstance) { 401 carouselData.swiperInstance.destroy(true, true); 402 this.detectedCarousels.delete(carouselId); 403 //console.log('Destroyed carousel:', carouselId); 404 } 405 } 406 407 destroy() { 408 // Destroy all Swiper instances 409 this.detectedCarousels.forEach((carouselData, id) => { 410 if (carouselData.swiperInstance) { 411 carouselData.swiperInstance.destroy(true, true); 412 } 413 }); 414 415 // Clear the map 416 this.detectedCarousels.clear(); 417 418 // Disconnect observer 419 if (this.observer) { 420 this.observer.disconnect(); 421 this.observer = null; 422 } 423 424 //console.log('CANVAS Carousel Detector destroyed'); 425 } 426} 427 428const canvasCarouselDetector = new CANVASCarouselDetector(); 429 430window.canvasCarouselDetector = canvasCarouselDetector; 431 432export default canvasCarouselDetector;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.