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