@wewear/virtual-try-on 1.3.7 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,120 +4,19 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.WeWearVirtualTryOn = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
- async function callVirtualTryOnApi(baseUrl, ww_access_token, ww_user_id, ww_product_image, ww_image) {
8
- const formData = new FormData();
9
- formData.append("ww_user_id", ww_user_id);
10
- formData.append("ww_product_image", ww_product_image);
11
- formData.append("ww_image", ww_image, "captured-image.jpg");
12
- formData.append("ww_access_token", ww_access_token);
13
- const response = await fetch(`${baseUrl}/api/virtual-try-on`, {
14
- method: "POST",
15
- body: formData,
16
- });
17
- if (!response.ok) {
18
- console.error("[WeWear VTO] API request failed:", response.status, response.statusText);
19
- if (response.status === 500) {
20
- return { "imageUrl": null, "error": { message: `Service unavailable. Please try again later.`, type: 'error' } };
21
- }
22
- else if (response.status === 503) {
23
- return { "imageUrl": null, "error": { message: `Service overloaded. Please try again later.`, type: 'warning' } };
24
- }
25
- return { "imageUrl": null, "error": { message: `${response.statusText}`, type: 'warning' } };
26
- }
27
- const result = await response.json();
28
- console.log("[WeWear VTO] API response:", result);
29
- return result;
30
- }
31
-
32
7
  /** CSS class names for consistent styling */
33
8
  const CSS_CLASSES = {
34
9
  BUTTON_CONTAINER: "wewear-vto-button-container",
35
10
  BUTTON: "wewear-vto-button",
36
11
  MODAL: "wewear-vto-modal",
12
+ PREVIEW_BADGE: "ww-vto-preview-badge",
37
13
  };
38
14
  /** Z-index values for proper layering */
39
15
  const Z_INDEX = {
40
16
  BUTTON: 10,
41
17
  MODAL: 99999,
42
18
  };
43
- /** Camera constraints for optimal capture */
44
- const CAMERA_CONSTRAINTS = {
45
- video: {
46
- width: { ideal: 1280 },
47
- height: { ideal: 720 },
48
- facingMode: "user", // Front-facing camera
49
- },
50
- audio: false,
51
- };
52
- /** Image capture settings */
53
- const IMAGE_SETTINGS = {
54
- FORMAT: "image/jpeg",
55
- QUALITY: 0.8,
56
- };
57
-
58
- async function startCamera(video, loadingIndicator, captureButton) {
59
- console.log("[WeWear VTO] Starting camera...");
60
- try {
61
- console.log("[WeWear VTO] Requesting camera access...");
62
- const stream = await navigator.mediaDevices.getUserMedia(CAMERA_CONSTRAINTS);
63
- video.srcObject = stream;
64
- video.onloadedmetadata = () => {
65
- console.log("[WeWear VTO] Camera stream loaded successfully");
66
- loadingIndicator.style.display = "none";
67
- captureButton.style.display = "flex";
68
- };
69
- }
70
- catch (error) {
71
- console.error("[WeWear VTO] Camera access error:", error);
72
- loadingIndicator.innerHTML =
73
- "Camera access denied. Please allow camera permissions.";
74
- }
75
- }
76
- function stopCamera(video) {
77
- const stream = video.srcObject;
78
- if (stream) {
79
- const tracks = stream.getTracks();
80
- tracks.forEach((track) => {
81
- track.stop();
82
- });
83
- video.srcObject = null;
84
- }
85
- }
86
- async function captureImageFromVideo(video, canvas) {
87
- // Set canvas dimensions to match video
88
- canvas.width = video.videoWidth;
89
- canvas.height = video.videoHeight;
90
- // Draw video frame to canvas
91
- const ctx = canvas.getContext("2d");
92
- if (!ctx) {
93
- throw new Error("Could not get canvas context");
94
- }
95
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
96
- // Convert canvas to blob
97
- return new Promise((resolve, reject) => {
98
- canvas.toBlob((blob) => {
99
- if (blob) {
100
- resolve(blob);
101
- }
102
- else {
103
- reject(new Error("Failed to create blob from canvas"));
104
- }
105
- }, IMAGE_SETTINGS.FORMAT, IMAGE_SETTINGS.QUALITY);
106
- });
107
- }
108
19
 
109
- function getCookie(name) {
110
- var _a;
111
- if (typeof document === "undefined")
112
- return null;
113
- const value = `; ${document.cookie}`;
114
- const parts = value.split(`; ${name}=`);
115
- if (parts.length === 2) {
116
- const part = parts.pop();
117
- return part ? ((_a = part.split(";").shift()) === null || _a === void 0 ? void 0 : _a.trim()) || null : null;
118
- }
119
- return null;
120
- }
121
20
  function getPositionStyles(position) {
122
21
  switch (position) {
123
22
  case "bottom-left":
@@ -247,140 +146,18 @@
247
146
  }
248
147
  function createButton(buttonPosition, onClick, disabled = false) {
249
148
  const container = createButtonContainer(buttonPosition, false, onClick);
250
- const cameraButton = container.querySelector('.ww-camera-btn');
149
+ const cameraButton = container.querySelector(".ww-camera-btn");
251
150
  if (cameraButton) {
252
151
  cameraButton.disabled = disabled;
253
152
  if (disabled) {
254
- cameraButton.style.opacity = '0.5';
255
- cameraButton.style.cursor = 'not-allowed';
256
- cameraButton.title = 'Find your ideal size first';
153
+ cameraButton.style.opacity = "0.5";
154
+ cameraButton.style.cursor = "not-allowed";
155
+ cameraButton.title = "Find your ideal size first";
257
156
  }
258
157
  }
259
158
  return container;
260
159
  }
261
160
 
262
- function showCameraModal(callbacks) {
263
- console.log("[WeWear VTO] Opening camera modal...");
264
- // Remove any existing modals first
265
- removeElements(`.${CSS_CLASSES.MODAL}`);
266
- // Create modal container
267
- const modal = document.createElement("div");
268
- modal.className = CSS_CLASSES.MODAL;
269
- modal.style.cssText = `
270
- position: fixed;
271
- top: 0;
272
- left: 0;
273
- width: 100%;
274
- height: 100%;
275
- background-color: rgba(0, 0, 0, 0.95);
276
- display: flex;
277
- flex-direction: column;
278
- align-items: center;
279
- justify-content: center;
280
- z-index: ${Z_INDEX.MODAL};
281
- padding: 20px;
282
- box-sizing: border-box;
283
- `;
284
- // Create camera container
285
- const cameraContainer = document.createElement("div");
286
- cameraContainer.style.cssText = `
287
- position: relative;
288
- width: 100%;
289
- max-width: 500px;
290
- height: 70vh;
291
- background-color: #000;
292
- border-radius: 12px;
293
- overflow: hidden;
294
- display: flex;
295
- flex-direction: column;
296
- align-items: center;
297
- justify-content: center;
298
- `;
299
- // Create video element
300
- const video = document.createElement("video");
301
- video.autoplay = true;
302
- video.playsInline = true;
303
- video.muted = true;
304
- video.style.cssText = `
305
- width: 100%;
306
- height: 100%;
307
- object-fit: cover;
308
- border-radius: 12px;
309
- `;
310
- // Create canvas for capturing
311
- const canvas = document.createElement("canvas");
312
- canvas.style.display = "none";
313
- // Create capture button
314
- const captureButton = document.createElement("button");
315
- captureButton.innerHTML = "";
316
- captureButton.style.cssText = `
317
- position: absolute;
318
- bottom: 20px;
319
- left: 50%;
320
- transform: translateX(-50%);
321
- width: 60px;
322
- height: 60px;
323
- border: 3px solid white;
324
- background-color: rgba(0, 0, 0, 0.7);
325
- border-radius: 50%;
326
- cursor: pointer;
327
- display: none;
328
- align-items: center;
329
- justify-content: center;
330
- transition: all 0.2s ease;
331
- `;
332
- // Create close button
333
- const closeButton = document.createElement("button");
334
- closeButton.innerHTML = "×";
335
- closeButton.style.cssText = `
336
- position: absolute;
337
- top: 15px;
338
- right: 15px;
339
- width: 40px;
340
- height: 40px;
341
- border: none;
342
- background-color: rgba(0, 0, 0, 0.7);
343
- color: white;
344
- font-size: 24px;
345
- font-weight: bold;
346
- cursor: pointer;
347
- border-radius: 50%;
348
- display: flex;
349
- align-items: center;
350
- justify-content: center;
351
- `;
352
- // Create loading indicator
353
- const loadingIndicator = document.createElement("div");
354
- loadingIndicator.innerHTML = "Initializing camera...";
355
- loadingIndicator.style.cssText = `
356
- color: white;
357
- font-size: 16px;
358
- position: absolute;
359
- top: 50%;
360
- left: 50%;
361
- transform: translate(-50%, -50%);
362
- `;
363
- // Add event listeners
364
- closeButton.onclick = () => {
365
- stopCamera(video);
366
- modal.remove();
367
- };
368
- captureButton.onclick = async () => {
369
- await callbacks.onCapture(video, canvas);
370
- };
371
- // Assemble the camera modal
372
- cameraContainer.appendChild(video);
373
- cameraContainer.appendChild(canvas);
374
- cameraContainer.appendChild(captureButton);
375
- cameraContainer.appendChild(closeButton);
376
- cameraContainer.appendChild(loadingIndicator);
377
- modal.appendChild(cameraContainer);
378
- document.body.appendChild(modal);
379
- console.log("[WeWear VTO] Camera modal added to DOM");
380
- // Start camera
381
- startCamera(video, loadingIndicator, captureButton);
382
- }
383
-
384
161
  /**
385
162
  * Creates a loading overlay that can be shown in different containers
386
163
  */
@@ -433,24 +210,6 @@
433
210
  `;
434
211
  return overlay;
435
212
  }
436
- /**
437
- * Shows a loading overlay in a modal container
438
- */
439
- function showModalLoading(container) {
440
- // Remove any existing loading overlays
441
- removeModalLoading();
442
- // Show loading in the product area
443
- showProductLoading(container);
444
- }
445
- /**
446
- * Removes loading overlay from modal
447
- */
448
- function removeModalLoading() {
449
- const existingOverlay = document.body.querySelector(".ww-loading-overlay");
450
- if (existingOverlay) {
451
- existingOverlay.remove();
452
- }
453
- }
454
213
  /**
455
214
  * Shows a loading overlay in the product gallery container
456
215
  */
@@ -475,202 +234,115 @@
475
234
  }
476
235
  }
477
236
 
478
- function showReviewModal(imageBlob, callbacks) {
479
- console.log("[WeWear VTO] Opening review modal...");
480
- // Remove any existing modals first
481
- removeElements(`.${CSS_CLASSES.MODAL}`);
482
- // Create image URL for preview
483
- const imageUrl = URL.createObjectURL(imageBlob);
484
- // Create modal container
485
- const modal = document.createElement("div");
486
- modal.className = CSS_CLASSES.MODAL;
487
- modal.style.cssText = `
488
- position: fixed;
489
- top: 0;
490
- left: 0;
491
- width: 100%;
492
- height: 100%;
493
- background-color: rgba(0, 0, 0, 0.95);
494
- display: flex;
495
- flex-direction: column;
496
- align-items: center;
497
- justify-content: center;
498
- z-index: ${Z_INDEX.MODAL};
499
- padding: 20px;
500
- box-sizing: border-box;
501
- gap: 20px;
502
- `;
503
- // Create review container
504
- const reviewContainer = document.createElement("div");
505
- reviewContainer.style.cssText = `
506
- position: relative;
507
- width: 100%;
508
- max-width: 500px;
509
- height: 63vh;
510
- background-color: #000;
511
- border-radius: 12px;
512
- overflow: hidden;
513
- display: flex;
514
- flex-direction: column;
515
- align-items: center;
516
- justify-content: center;
517
- `;
518
- // Create image element
519
- const image = document.createElement("img");
520
- image.src = imageUrl;
521
- image.alt = "Review your photo";
522
- image.style.cssText = `
523
- width: 100%;
524
- height: 100%;
525
- object-fit: cover;
526
- border-radius: 12px;
527
- `;
528
- // Create close button
529
- const closeButton = document.createElement("button");
530
- closeButton.innerHTML = "×";
531
- closeButton.style.cssText = `
532
- position: absolute;
533
- top: 15px;
534
- right: 15px;
535
- width: 40px;
536
- height: 40px;
537
- border: none;
538
- background-color: rgba(0, 0, 0, 0.7);
539
- color: white;
540
- font-size: 24px;
541
- font-weight: bold;
542
- cursor: pointer;
543
- border-radius: 50%;
544
- display: flex;
545
- align-items: center;
546
- justify-content: center;
547
- `;
548
- // Create button container
549
- const buttonContainer = document.createElement("div");
550
- buttonContainer.style.cssText = `
551
- display: flex;
552
- gap: 15px;
553
- width: 100%;
554
- max-width: 500px;
555
- `;
556
- // Create retake button
557
- const retakeButton = document.createElement("button");
558
- retakeButton.textContent = "Retake";
559
- retakeButton.style.cssText = `
560
- flex: 1;
561
- padding: 12px 24px;
562
- background-color: rgba(255, 255, 255, 0.9);
563
- color: black;
564
- border-radius: 8px;
565
- border: none;
566
- font-size: 16px;
567
- font-weight: normal;
568
- cursor: pointer;
569
- `;
570
- // Create use photo button
571
- const usePhotoButton = document.createElement("button");
572
- usePhotoButton.textContent = "Use Photo";
573
- usePhotoButton.style.cssText = `
574
- flex: 1;
575
- padding: 12px 24px;
576
- background-color: rgba(0, 0, 0, 0.7);
577
- color: white;
578
- border-radius: 8px;
579
- border: none;
580
- font-size: 16px;
581
- font-weight: normal;
582
- cursor: pointer;
583
- `;
584
- // Add event listeners
585
- closeButton.onclick = () => {
586
- URL.revokeObjectURL(imageUrl); // Clean up
587
- modal.remove();
588
- };
589
- retakeButton.onclick = () => {
590
- URL.revokeObjectURL(imageUrl); // Clean up
591
- modal.remove();
592
- callbacks.onRetake();
593
- };
594
- usePhotoButton.onclick = async () => {
595
- URL.revokeObjectURL(imageUrl); // Clean up
596
- modal.remove();
597
- await callbacks.onAccept(imageBlob);
598
- };
599
- // Assemble the review modal
600
- buttonContainer.appendChild(retakeButton);
601
- buttonContainer.appendChild(usePhotoButton);
602
- reviewContainer.appendChild(image);
603
- reviewContainer.appendChild(closeButton);
604
- modal.appendChild(reviewContainer);
605
- modal.appendChild(buttonContainer);
606
- document.body.appendChild(modal);
607
- console.log("[WeWear VTO] Review modal added to DOM");
237
+ function createPreviewBadge() {
238
+ const badge = document.createElement("div");
239
+ badge.className = CSS_CLASSES.PREVIEW_BADGE;
240
+ badge.innerText = "PREVIEW";
241
+ const styleId = "ww-vto-badge-styles";
242
+ if (!document.getElementById(styleId)) {
243
+ const styles = document.createElement("style");
244
+ styles.id = styleId;
245
+ styles.innerHTML = `
246
+ .${CSS_CLASSES.PREVIEW_BADGE} {
247
+ position: absolute;
248
+ top: 16px;
249
+ right: 16px;
250
+ background-color: rgba(0, 0, 0, 0.6);
251
+ color: white;
252
+ padding: 4px 8px;
253
+ border-radius: 4px;
254
+ font-size: 12px;
255
+ font-weight: 600;
256
+ z-index: 10;
257
+ animation: ww-vto-pulse 2s infinite;
258
+ pointer-events: none;
259
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
260
+ }
261
+
262
+ @keyframes ww-vto-pulse {
263
+ 0% {
264
+ transform: scale(1);
265
+ opacity: 0.9;
266
+ }
267
+ 50% {
268
+ transform: scale(1.05);
269
+ opacity: 1;
270
+ }
271
+ 100% {
272
+ transform: scale(1);
273
+ opacity: 0.9;
274
+ }
275
+ }
276
+ `;
277
+ document.head.appendChild(styles);
278
+ }
279
+ return badge;
608
280
  }
609
281
 
610
282
  function showAlert(container, message, type) {
611
283
  var _a;
612
284
  removeAlert(container); // Remove any existing alert
613
- const alertDiv = document.createElement('div');
285
+ const alertDiv = document.createElement("div");
614
286
  alertDiv.className = `ww-alert ww-alert-${type}`;
615
- alertDiv.setAttribute('role', type === 'error' ? 'alert' : 'status');
616
- alertDiv.setAttribute('aria-live', 'assertive');
617
- alertDiv.style.cssText = `
618
- position: absolute;
619
- top: 16px;
620
- left: 50%;
621
- transform: translateX(-50%) translateY(-10px);
622
- background: ${type === 'error' ? '#FEE2E2' : '#FEF9C3'};
623
- color: ${type === 'error' ? '#B91C1C' : '#92400E'};
624
- border: 1px solid ${type === 'error' ? '#F87171' : '#FBBF24'};
625
- border-radius: 8px;
626
- padding: 12px 16px;
627
- z-index: 9999;
628
- font-size: 15px;
629
- font-weight: 500;
630
- box-shadow: 0 4px 12px rgba(0,0,0,0.1);
631
- display: flex;
632
- align-items: center;
633
- min-width: 220px;
634
- max-width: 90%;
635
- opacity: 0;
636
- transition: opacity 0.3s ease, transform 0.3s ease;
287
+ alertDiv.setAttribute("role", "alert" );
288
+ alertDiv.setAttribute("aria-live", "assertive");
289
+ alertDiv.style.cssText = `
290
+ position: absolute;
291
+ top: 16px;
292
+ left: 50%;
293
+ transform: translateX(-50%) translateY(-10px);
294
+ background: ${"#FEE2E2" };
295
+ color: ${"#B91C1C" };
296
+ border: 1px solid ${"#F87171" };
297
+ border-radius: 8px;
298
+ padding: 12px 16px;
299
+ z-index: 9999;
300
+ font-size: 15px;
301
+ font-weight: 500;
302
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
303
+ display: flex;
304
+ align-items: center;
305
+ min-width: 220px;
306
+ max-width: 90%;
307
+ opacity: 0;
308
+ transition: opacity 0.3s ease, transform 0.3s ease;
637
309
  `;
638
- alertDiv.innerHTML = `
639
- <span style="flex: 1; line-height: 1.4;">${message}</span>
640
- <button type="button" aria-label="Close alert"
641
- style="
642
- background: none;
643
- border: none;
644
- font-size: 20px;
645
- color: inherit;
646
- cursor: pointer;
647
- margin-left: 12px;
648
- padding: 0;
649
- line-height: 1;
650
- ">
651
- &times;
652
- </button>
310
+ alertDiv.innerHTML = `
311
+ <span style="flex: 1; line-height: 1.4;">${message}</span>
312
+ <button type="button" aria-label="Close alert"
313
+ style="
314
+ background: none;
315
+ border: none;
316
+ font-size: 20px;
317
+ color: inherit;
318
+ cursor: pointer;
319
+ margin-left: 12px;
320
+ padding: 0;
321
+ line-height: 1;
322
+ ">
323
+ &times;
324
+ </button>
653
325
  `;
654
326
  // Close on button click
655
- (_a = alertDiv.querySelector('button')) === null || _a === void 0 ? void 0 : _a.addEventListener('click', () => {
327
+ (_a = alertDiv.querySelector("button")) === null || _a === void 0 ? void 0 : _a.addEventListener("click", () => {
656
328
  fadeOutAndRemove(alertDiv);
657
329
  });
658
330
  container.appendChild(alertDiv);
659
331
  // Animate in
660
332
  requestAnimationFrame(() => {
661
- alertDiv.style.opacity = '1';
662
- alertDiv.style.transform = 'translateX(-50%) translateY(0)';
333
+ alertDiv.style.opacity = "1";
334
+ alertDiv.style.transform = "translateX(-50%) translateY(0)";
663
335
  });
664
336
  }
665
337
  function fadeOutAndRemove(element) {
666
- element.style.opacity = '0';
667
- element.style.transform = 'translateX(-50%) translateY(-10px)';
338
+ element.style.opacity = "0";
339
+ element.style.transform = "translateX(-50%) translateY(-10px)";
668
340
  setTimeout(() => {
669
341
  element.remove();
670
342
  }, 300);
671
343
  }
672
344
  function removeAlert(container) {
673
- const alert = container.querySelector('.ww-alert');
345
+ const alert = container.querySelector(".ww-alert");
674
346
  if (alert)
675
347
  fadeOutAndRemove(alert);
676
348
  }
@@ -678,10 +350,13 @@
678
350
  class VirtualTryOnWidget {
679
351
  constructor(config) {
680
352
  this.virtualTryOnImageUrl = null;
353
+ this.originalProductImageUrl = null;
681
354
  this.isShowingVirtualTryOn = false;
682
- this.lastApiParams = null;
355
+ this.lastModelImage = null;
683
356
  this.cookieCheckInterval = null;
684
357
  this.cameraButton = null;
358
+ this.iframeMessageListener = null;
359
+ this.previewBadge = null;
685
360
  this.config = {
686
361
  baseUrl: config.baseUrl,
687
362
  productPageSelector: config.productPageSelector,
@@ -710,40 +385,18 @@
710
385
  if (getComputedStyle(container).position === "static") {
711
386
  container.style.position = "relative";
712
387
  }
713
- // Check required cookies
714
- const ww_access_token = getCookie("ww_access_token");
715
- const ww_user_id = getCookie("ww_user_id");
716
- const cookiesPresent = !!ww_access_token && !!ww_user_id;
717
388
  // Create and add the virtual try-on button
718
389
  const button = createButton(this.config.buttonPosition, async () => {
719
390
  if (this.cameraButton && !this.cameraButton.disabled) {
720
391
  await this.handleTryOnClick();
721
392
  }
722
- }, !cookiesPresent);
393
+ });
723
394
  // Store reference to camera button for dynamic enable/disable
724
- this.cameraButton = button.querySelector('.ww-camera-btn');
395
+ this.cameraButton = button.querySelector(".ww-camera-btn");
725
396
  container.appendChild(button);
726
397
  console.log("[WeWear VTO] Widget initialized successfully");
727
- // Periodically check cookies and update button state
728
- this.cookieCheckInterval = window.setInterval(() => {
729
- const accessToken = getCookie("ww_access_token");
730
- const userId = getCookie("ww_user_id");
731
- const present = !!accessToken && !!userId;
732
- if (this.cameraButton) {
733
- if (present && this.cameraButton.disabled) {
734
- this.cameraButton.disabled = false;
735
- this.cameraButton.style.opacity = '1';
736
- this.cameraButton.style.cursor = 'pointer';
737
- this.cameraButton.title = '';
738
- }
739
- else if (!present && !this.cameraButton.disabled) {
740
- this.cameraButton.disabled = true;
741
- this.cameraButton.style.opacity = '0.5';
742
- this.cameraButton.style.cursor = 'not-allowed';
743
- this.cameraButton.title = 'Required cookies missing';
744
- }
745
- }
746
- }, 2000); // check every 2 seconds
398
+ // Listen for messages from the photo upload page
399
+ this.setupIframeListener();
747
400
  }
748
401
  catch (error) {
749
402
  console.error("[WeWear VTO] Initialization failed:", error);
@@ -757,152 +410,214 @@
757
410
  console.log("[WeWear VTO] Button clicked, starting try-on process...");
758
411
  try {
759
412
  // Get required data
760
- const ww_access_token = getCookie("ww_access_token");
761
- const ww_user_id = getCookie("ww_user_id");
762
- const productImageElement = document.querySelector(this.config.productImageSelector);
763
- const ww_product_image = (productImageElement === null || productImageElement === void 0 ? void 0 : productImageElement.src) || (productImageElement === null || productImageElement === void 0 ? void 0 : productImageElement.getAttribute('data-src')) || '';
413
+ if (!this.originalProductImageUrl) {
414
+ const productImageElement = document.querySelector(this.config.productImageSelector);
415
+ this.originalProductImageUrl =
416
+ (productImageElement === null || productImageElement === void 0 ? void 0 : productImageElement.src) ||
417
+ (productImageElement === null || productImageElement === void 0 ? void 0 : productImageElement.getAttribute("data-src")) ||
418
+ "";
419
+ }
420
+ const ww_product_image = this.originalProductImageUrl;
764
421
  console.log("[WeWear VTO] Retrieved data:", {
765
- ww_user_id,
766
422
  ww_product_image,
767
- ww_access_token,
768
423
  });
769
- // Validate required data
770
- if (!ww_user_id) {
771
- console.warn("[WeWear VTO] Missing required cookie: ww_user_id");
772
- return;
773
- }
774
424
  if (!ww_product_image) {
775
425
  console.warn("[WeWear VTO] Product image not found:", this.config.productImageSelector);
776
426
  return;
777
427
  }
778
- if (!ww_access_token) {
779
- console.warn("[WeWear VTO] Missing required cookie: ww_access_token");
780
- return;
781
- }
782
- // Show camera capture modal
783
- this.showCameraModalWithCallbacks(ww_access_token, ww_user_id, ww_product_image);
428
+ // Open the photo upload page in a modal
429
+ const photoUploadUrl = new URL(`${this.config.baseUrl}`);
430
+ photoUploadUrl.searchParams.append("ww_product_image", ww_product_image);
431
+ this.showPhotoUploadModal(photoUploadUrl.toString());
784
432
  }
785
433
  catch (error) {
786
434
  console.error("[WeWear VTO] Try-on request failed:", error);
787
435
  }
788
436
  }
789
437
  /**
790
- * Shows camera modal with appropriate callbacks
791
- * @private
792
- */
793
- showCameraModalWithCallbacks(ww_access_token, ww_user_id, ww_product_image) {
794
- const callbacks = {
795
- onCapture: async (video, canvas) => {
796
- try {
797
- // Capture image from video
798
- const imageBlob = await captureImageFromVideo(video, canvas);
799
- // Stop camera and show review modal
800
- stopCamera(video);
801
- // Show review modal instead of immediately processing
802
- this.showReviewModalWithCallbacks(imageBlob, ww_access_token, ww_user_id, ww_product_image);
803
- }
804
- catch (error) {
805
- console.error("[WeWear VTO] Image capture error:", error);
806
- }
807
- },
808
- };
809
- showCameraModal(callbacks);
810
- }
811
- /**
812
- * Shows review modal with appropriate callbacks
438
+ * Shows a modal with an iframe for the photo upload page
813
439
  * @private
814
440
  */
815
- showReviewModalWithCallbacks(imageBlob, ww_access_token, ww_user_id, ww_product_image) {
816
- const callbacks = {
817
- onRetake: () => {
818
- // Reopen camera modal
819
- this.showCameraModalWithCallbacks(ww_access_token, ww_user_id, ww_product_image);
820
- },
821
- onAccept: async (acceptedImageBlob) => {
822
- await this.processAcceptedImage(acceptedImageBlob, ww_access_token, ww_user_id, ww_product_image, document.querySelector(this.config.gallerySelector));
823
- },
441
+ showPhotoUploadModal(url) {
442
+ // Remove existing modals
443
+ removeElements(`.${CSS_CLASSES.MODAL}`);
444
+ const modal = document.createElement("div");
445
+ modal.className = CSS_CLASSES.MODAL;
446
+ modal.style.cssText = `
447
+ position: fixed;
448
+ top: 0;
449
+ left: 0;
450
+ width: 100%;
451
+ height: 100%;
452
+ background-color: rgba(0, 0, 0, 0.5);
453
+ display: flex;
454
+ justify-content: center;
455
+ align-items: center;
456
+ z-index: 1000;
457
+ `;
458
+ const iframe = document.createElement("iframe");
459
+ iframe.src = url;
460
+ iframe.style.cssText = `
461
+ width: 90%;
462
+ height: 90%;
463
+ max-width: 800px;
464
+ max-height: 600px;
465
+ border: none;
466
+ border-radius: 8px;
467
+ `;
468
+ const closeButton = document.createElement("button");
469
+ closeButton.innerText = "X";
470
+ closeButton.style.cssText = `
471
+ position: absolute;
472
+ top: 10px;
473
+ right: 10px;
474
+ background: white;
475
+ border: none;
476
+ border-radius: 50%;
477
+ width: 30px;
478
+ height: 30px;
479
+ cursor: pointer;
480
+ `;
481
+ closeButton.onclick = () => {
482
+ modal.remove();
824
483
  };
825
- showReviewModal(imageBlob, callbacks);
484
+ modal.appendChild(iframe);
485
+ modal.appendChild(closeButton);
486
+ document.body.appendChild(modal);
826
487
  }
827
488
  /**
828
- * Processes the accepted image by calling the API
489
+ * Sets up listener for messages from the iframe
829
490
  * @private
830
491
  */
831
- async processAcceptedImage(imageBlob, ww_access_token, ww_user_id, ww_product_image, container) {
832
- try {
833
- console.log("[WeWear VTO] Processing accepted image...");
834
- // Show loading in the review modal first
835
- showModalLoading(container);
836
- // Store the API parameters for potential refresh
837
- this.lastApiParams = {
838
- imageBlob,
839
- ww_access_token,
840
- ww_user_id,
841
- ww_product_image,
842
- };
843
- // Call the API with the accepted image
844
- const result = await callVirtualTryOnApi(this.config.baseUrl, ww_access_token, ww_user_id, ww_product_image, imageBlob);
845
- // Remove modal loading and close modal
846
- removeModalLoading();
847
- removeElements(`.${CSS_CLASSES.MODAL}`);
848
- if (result === null || result === void 0 ? void 0 : result.imageUrl) {
849
- this.replaceProductImage(result.imageUrl);
492
+ setupIframeListener() {
493
+ // Remove existing listener if any
494
+ if (this.iframeMessageListener) {
495
+ window.removeEventListener("message", this.iframeMessageListener);
496
+ }
497
+ this.iframeMessageListener = (event) => {
498
+ if (event.origin !== new URL(this.config.baseUrl).origin) {
499
+ return;
850
500
  }
851
- else if (result === null || result === void 0 ? void 0 : result.error) {
852
- showAlert(container, result.error.message, result.error.type);
501
+ switch (event.data.type) {
502
+ case "VTO_IMAGE_SELECTED":
503
+ if (event.data.image) {
504
+ this.lastModelImage = event.data.image;
505
+ this.startVirtualTryOn();
506
+ }
507
+ break;
508
+ case "CLOSE_MODAL":
509
+ removeElements(`.${CSS_CLASSES.MODAL}`);
510
+ break;
853
511
  }
854
- }
855
- catch (error) {
856
- console.error("[WeWear VTO] Error processing accepted image:", error);
857
- // Remove loading on error
858
- removeModalLoading();
859
- }
512
+ };
513
+ window.addEventListener("message", this.iframeMessageListener);
860
514
  }
861
- /**
862
- * Recalls the virtual try-on API with the same parameters
863
- * @private
864
- */
865
- async refreshVirtualTryOn() {
866
- if (!this.lastApiParams) {
867
- console.warn("[WeWear VTO] No previous API parameters available for refresh");
515
+ async startVirtualTryOn(isRefresh = false) {
516
+ if (isRefresh) {
517
+ console.log("[WeWear VTO] Refreshing virtual try-on with previous parameters...");
518
+ }
519
+ if (!this.lastModelImage || !this.originalProductImageUrl) {
520
+ console.warn("[WeWear VTO] Missing required data to start virtual try-on.");
868
521
  return;
869
522
  }
523
+ // Hide the modal and show loading indicator
524
+ const modal = document.querySelector(`.${CSS_CLASSES.MODAL}`);
525
+ if (modal && modal instanceof HTMLElement) {
526
+ modal.style.display = "none";
527
+ }
528
+ const container = document.querySelector(this.config.gallerySelector);
529
+ if (container instanceof HTMLElement) {
530
+ showProductLoading(container, "Generating your virtual try-on...");
531
+ }
870
532
  try {
871
- console.log("[WeWear VTO] Refreshing virtual try-on with previous parameters...");
872
- // Show loading in the product area
873
- const container = document.querySelector(this.config.gallerySelector);
874
- if (container instanceof HTMLElement) {
875
- showProductLoading(container, "Generating new virtual try-on...");
533
+ const submitResponse = await this.callVtoApi(this.lastModelImage, this.originalProductImageUrl);
534
+ let status = await this.fetchJobStatus(submitResponse.job_id);
535
+ const previews = [];
536
+ while (status.status !== "COMPLETED" && status.status !== "FAILED") {
537
+ await new Promise((r) => setTimeout(r, 3000));
538
+ status = await this.fetchJobStatus(submitResponse.job_id);
539
+ for (let i = previews.length; i < status.previews_available; i++) {
540
+ try {
541
+ const previewUrl = await this.fetchJobImage(submitResponse.job_id, i);
542
+ previews.push(previewUrl);
543
+ this.replaceProductImage(previewUrl);
544
+ if (!this.previewBadge && container instanceof HTMLElement) {
545
+ this.previewBadge = createPreviewBadge();
546
+ container.appendChild(this.previewBadge);
547
+ }
548
+ }
549
+ catch (e) {
550
+ if (!(e instanceof Error && e.message === "202_PROCESSING"))
551
+ throw e;
552
+ }
553
+ }
876
554
  }
877
- // Call the API with the stored parameters
878
- const result = await callVirtualTryOnApi(this.config.baseUrl, this.lastApiParams.ww_access_token, this.lastApiParams.ww_user_id, this.lastApiParams.ww_product_image, this.lastApiParams.imageBlob);
879
- if (result === null || result === void 0 ? void 0 : result.imageUrl) {
880
- this.virtualTryOnImageUrl = result.imageUrl;
881
- // Find the gallery container and update the image
882
- const container = document.querySelector(this.config.gallerySelector);
883
- if (container instanceof HTMLElement) {
884
- removeProductLoading(container);
885
- this.showVirtualTryOnImage(container);
886
- this.updateButtonContainer(container);
555
+ if (status.status === "COMPLETED") {
556
+ const finalImage = await this.fetchJobImage(submitResponse.job_id);
557
+ if (finalImage) {
558
+ this.replaceProductImage(finalImage);
559
+ }
560
+ if (this.previewBadge) {
561
+ this.previewBadge.remove();
562
+ this.previewBadge = null;
887
563
  }
888
564
  }
889
- else if (result === null || result === void 0 ? void 0 : result.error) {
890
- const container = document.querySelector(this.config.gallerySelector);
565
+ if (status.status === "FAILED") {
566
+ console.error("[WeWear VTO] VTO process failed:", status.message);
891
567
  if (container instanceof HTMLElement) {
892
- removeProductLoading(container);
893
- showAlert(container, result.error.message, result.error.type);
568
+ showAlert(container, status.message, "error");
894
569
  }
895
570
  }
896
571
  }
897
572
  catch (error) {
898
- console.error("[WeWear VTO] Error refreshing virtual try-on:", error);
899
- // Remove loading on error
900
- const container = document.querySelector(this.config.gallerySelector);
573
+ console.error("[WeWear VTO] Error during virtual try-on process:", error);
574
+ if (container instanceof HTMLElement) {
575
+ showAlert(container, "An unexpected error occurred.", "error");
576
+ }
577
+ }
578
+ finally {
901
579
  if (container instanceof HTMLElement) {
902
580
  removeProductLoading(container);
903
581
  }
582
+ removeElements(`.${CSS_CLASSES.MODAL}`);
904
583
  }
905
584
  }
585
+ async callVtoApi(modelImage, productImage) {
586
+ const formData = new FormData();
587
+ formData.append("model_image", modelImage, "model_image.png");
588
+ formData.append("ww_product_image", productImage);
589
+ const res = await fetch(`${this.config.baseUrl}/api/vto`, {
590
+ method: "POST",
591
+ body: formData,
592
+ });
593
+ if (!res.ok) {
594
+ const errorText = await res.text();
595
+ console.error("[WeWear VTO] API submission failed:", res.status, errorText);
596
+ throw new Error(`API submission failed: ${res.status}`);
597
+ }
598
+ return res.json();
599
+ }
600
+ async fetchJobStatus(jobId) {
601
+ const res = await fetch(`${this.config.baseUrl}/api/vto/status?job_id=${jobId}`);
602
+ if (!res.ok)
603
+ throw new Error(`Status check failed: ${res.status}`);
604
+ return res.json();
605
+ }
606
+ async fetchJobImage(jobId, previewIndex) {
607
+ let url = `${this.config.baseUrl}/api/vto/image?job_id=${jobId}`;
608
+ if (previewIndex !== undefined)
609
+ url += `&preview_index=${previewIndex}`;
610
+ const res = await fetch(url);
611
+ if (res.status === 202)
612
+ throw new Error("202_PROCESSING");
613
+ if (!res.ok)
614
+ throw new Error(`Image fetch failed: ${res.status}`);
615
+ const blob = await res.blob();
616
+ return URL.createObjectURL(blob);
617
+ }
618
+ async refreshVirtualTryOn() {
619
+ await this.startVirtualTryOn(true);
620
+ }
906
621
  /**
907
622
  * Replaces the product image in the gallery with the virtual try-on result
908
623
  * @private
@@ -922,8 +637,9 @@
922
637
  container.setAttribute("data-ww-original-content", container.innerHTML);
923
638
  }
924
639
  // Capture original image dimensions before replacement to prevent layout shift
925
- const originalImg = container.querySelector('img');
926
- if (originalImg && !container.hasAttribute("data-ww-original-dimensions")) {
640
+ const originalImg = container.querySelector("img");
641
+ if (originalImg &&
642
+ !container.hasAttribute("data-ww-original-dimensions")) {
927
643
  const computedStyle = window.getComputedStyle(originalImg);
928
644
  const rect = originalImg.getBoundingClientRect();
929
645
  container.setAttribute("data-ww-original-width", computedStyle.width);
@@ -964,6 +680,7 @@
964
680
  else {
965
681
  this.showVirtualTryOnImage(container);
966
682
  }
683
+ removeProductLoading(container);
967
684
  this.updateButtonContainer(container);
968
685
  }, this.isShowingVirtualTryOn);
969
686
  container.appendChild(buttonContainer);
@@ -977,44 +694,46 @@
977
694
  console.warn("[WeWear VTO] No virtual try-on image URL available");
978
695
  return;
979
696
  }
980
- // Clear existing content except buttons
981
- const existingButtons = container.querySelectorAll(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
982
- container.innerHTML = "";
697
+ // Remove all direct children except for the button container
698
+ Array.from(container.children).forEach((child) => {
699
+ if (!child.classList.contains(CSS_CLASSES.BUTTON_CONTAINER)) {
700
+ child.remove();
701
+ }
702
+ });
983
703
  // Get stored original dimensions to prevent layout shift
984
- const originalWidth = container.getAttribute("data-ww-original-width") || '';
985
- const originalHeight = container.getAttribute("data-ww-original-height") || '';
986
- const originalRectWidth = container.getAttribute("data-ww-original-rect-width") || '';
987
- const originalRectHeight = container.getAttribute("data-ww-original-rect-height") || '';
704
+ const originalWidth = container.getAttribute("data-ww-original-width") || "";
705
+ const originalHeight = container.getAttribute("data-ww-original-height") || "";
706
+ const originalRectWidth = container.getAttribute("data-ww-original-rect-width") || "";
707
+ const originalRectHeight = container.getAttribute("data-ww-original-rect-height") || "";
988
708
  const image = document.createElement("img");
989
709
  image.src = this.virtualTryOnImageUrl;
990
710
  image.alt = "Virtual Try-On Result";
991
711
  // Use original dimensions to prevent layout shift
992
- let widthStyle = '100%';
993
- let heightStyle = '100%';
712
+ let widthStyle = "100%";
713
+ let heightStyle = "100%";
994
714
  // Prefer computed style dimensions, fallback to bounding rect, then container fill
995
- if (originalWidth && originalWidth !== 'auto' && originalWidth !== '0px') {
715
+ if (originalWidth && originalWidth !== "auto" && originalWidth !== "0px") {
996
716
  widthStyle = originalWidth;
997
717
  }
998
- else if (originalRectWidth && originalRectWidth !== '0') {
718
+ else if (originalRectWidth && originalRectWidth !== "0") {
999
719
  widthStyle = `${originalRectWidth}px`;
1000
720
  }
1001
- if (originalHeight && originalHeight !== 'auto' && originalHeight !== '0px') {
721
+ if (originalHeight &&
722
+ originalHeight !== "auto" &&
723
+ originalHeight !== "0px") {
1002
724
  heightStyle = originalHeight;
1003
725
  }
1004
- else if (originalRectHeight && originalRectHeight !== '0') {
726
+ else if (originalRectHeight && originalRectHeight !== "0") {
1005
727
  heightStyle = `${originalRectHeight}px`;
1006
728
  }
1007
- image.style.cssText = `
1008
- width: ${widthStyle};
1009
- height: ${heightStyle};
1010
- object-fit: cover;
1011
- border-radius: 8px;
729
+ image.style.cssText = `
730
+ width: ${widthStyle};
731
+ height: ${heightStyle};
732
+ object-fit: cover;
733
+ border-radius: 8px;
1012
734
  `;
1013
- container.appendChild(image);
1014
- // Re-add buttons
1015
- existingButtons.forEach((btn) => {
1016
- container.appendChild(btn);
1017
- });
735
+ // Prepend the image to ensure buttons are rendered on top
736
+ container.prepend(image);
1018
737
  this.isShowingVirtualTryOn = true;
1019
738
  }
1020
739
  /**
@@ -1022,16 +741,18 @@
1022
741
  * @private
1023
742
  */
1024
743
  showOriginalImage(container) {
1025
- const originalContent = container.getAttribute("data-ww-original-content");
1026
- if (originalContent) {
1027
- // Store existing buttons
1028
- const existingButtons = container.querySelectorAll(`.${CSS_CLASSES.BUTTON_CONTAINER}`);
1029
- // Restore original content
1030
- container.innerHTML = originalContent;
1031
- // Re-add buttons
1032
- existingButtons.forEach((btn) => {
1033
- container.appendChild(btn);
744
+ const originalContentHTML = container.getAttribute("data-ww-original-content");
745
+ if (originalContentHTML) {
746
+ // Remove all direct children except for the button container
747
+ Array.from(container.children).forEach((child) => {
748
+ if (!child.classList.contains(CSS_CLASSES.BUTTON_CONTAINER)) {
749
+ child.remove();
750
+ }
1034
751
  });
752
+ // Parse the original content and prepend it
753
+ const tempDiv = document.createElement("div");
754
+ tempDiv.innerHTML = originalContentHTML;
755
+ container.prepend(...Array.from(tempDiv.children));
1035
756
  }
1036
757
  this.isShowingVirtualTryOn = false;
1037
758
  }
@@ -1050,10 +771,20 @@
1050
771
  this.cookieCheckInterval = null;
1051
772
  }
1052
773
  this.cameraButton = null;
774
+ // Remove message listener
775
+ if (this.iframeMessageListener) {
776
+ window.removeEventListener("message", this.iframeMessageListener);
777
+ this.iframeMessageListener = null;
778
+ }
1053
779
  // Reset state
1054
780
  this.virtualTryOnImageUrl = null;
1055
781
  this.isShowingVirtualTryOn = false;
1056
- this.lastApiParams = null;
782
+ this.lastModelImage = null;
783
+ this.originalProductImageUrl = null;
784
+ if (this.previewBadge) {
785
+ this.previewBadge.remove();
786
+ this.previewBadge = null;
787
+ }
1057
788
  console.log("[WeWear VTO] Widget destroyed successfully");
1058
789
  }
1059
790
  catch (error) {
@@ -1095,18 +826,6 @@
1095
826
  console.error("[WeWear VTO] Initialization error:", error);
1096
827
  }
1097
828
  }
1098
- function destroyVirtualTryOn() {
1099
- try {
1100
- if (widgetInstance) {
1101
- widgetInstance.destroy();
1102
- widgetInstance = null;
1103
- console.log("[WeWear VTO] Widget instance destroyed");
1104
- }
1105
- }
1106
- catch (error) {
1107
- console.error("[WeWear VTO] Error destroying widget:", error);
1108
- }
1109
- }
1110
829
  function getWidgetInstance() {
1111
830
  return widgetInstance;
1112
831
  }
@@ -1115,7 +834,6 @@
1115
834
  }
1116
835
 
1117
836
  exports.VirtualTryOnWidget = VirtualTryOnWidget;
1118
- exports.destroyVirtualTryOn = destroyVirtualTryOn;
1119
837
  exports.getWidgetInstance = getWidgetInstance;
1120
838
  exports.initVirtualTryOn = initVirtualTryOn;
1121
839