@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/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 +321 -602
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +320 -602
- package/dist/index.js.map +1 -1
- package/dist/installer.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,16 @@
|
|
|
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
|
-
|
|
52
|
-
async function startCamera(video, loadingIndicator, captureButton) {
|
|
53
|
-
console.log("[WeWear VTO] Starting camera...");
|
|
54
|
-
try {
|
|
55
|
-
console.log("[WeWear VTO] Requesting camera access...");
|
|
56
|
-
const stream = await navigator.mediaDevices.getUserMedia(CAMERA_CONSTRAINTS);
|
|
57
|
-
video.srcObject = stream;
|
|
58
|
-
video.onloadedmetadata = () => {
|
|
59
|
-
console.log("[WeWear VTO] Camera stream loaded successfully");
|
|
60
|
-
loadingIndicator.style.display = "none";
|
|
61
|
-
captureButton.style.display = "flex";
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
catch (error) {
|
|
65
|
-
console.error("[WeWear VTO] Camera access error:", error);
|
|
66
|
-
loadingIndicator.innerHTML =
|
|
67
|
-
"Camera access denied. Please allow camera permissions.";
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
function stopCamera(video) {
|
|
71
|
-
const stream = video.srcObject;
|
|
72
|
-
if (stream) {
|
|
73
|
-
const tracks = stream.getTracks();
|
|
74
|
-
tracks.forEach((track) => {
|
|
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"));
|
|
98
|
-
}
|
|
99
|
-
}, IMAGE_SETTINGS.FORMAT, IMAGE_SETTINGS.QUALITY);
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
13
|
|
|
103
|
-
function getCookie(name) {
|
|
104
|
-
var _a;
|
|
105
|
-
if (typeof document === "undefined")
|
|
106
|
-
return null;
|
|
107
|
-
const value = `; ${document.cookie}`;
|
|
108
|
-
const parts = value.split(`; ${name}=`);
|
|
109
|
-
if (parts.length === 2) {
|
|
110
|
-
const part = parts.pop();
|
|
111
|
-
return part ? ((_a = part.split(";").shift()) === null || _a === void 0 ? void 0 : _a.trim()) || null : null;
|
|
112
|
-
}
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
14
|
function getPositionStyles(position) {
|
|
116
15
|
switch (position) {
|
|
117
16
|
case "bottom-left":
|
|
@@ -241,140 +140,18 @@ function createButtonContainer(buttonPosition, hasVirtualTryOn = false, onCamera
|
|
|
241
140
|
}
|
|
242
141
|
function createButton(buttonPosition, onClick, disabled = false) {
|
|
243
142
|
const container = createButtonContainer(buttonPosition, false, onClick);
|
|
244
|
-
const cameraButton = container.querySelector(
|
|
143
|
+
const cameraButton = container.querySelector(".ww-camera-btn");
|
|
245
144
|
if (cameraButton) {
|
|
246
145
|
cameraButton.disabled = disabled;
|
|
247
146
|
if (disabled) {
|
|
248
|
-
cameraButton.style.opacity =
|
|
249
|
-
cameraButton.style.cursor =
|
|
250
|
-
cameraButton.title =
|
|
147
|
+
cameraButton.style.opacity = "0.5";
|
|
148
|
+
cameraButton.style.cursor = "not-allowed";
|
|
149
|
+
cameraButton.title = "Find your ideal size first";
|
|
251
150
|
}
|
|
252
151
|
}
|
|
253
152
|
return container;
|
|
254
153
|
}
|
|
255
154
|
|
|
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 capture button
|
|
308
|
-
const captureButton = document.createElement("button");
|
|
309
|
-
captureButton.innerHTML = "";
|
|
310
|
-
captureButton.style.cssText = `
|
|
311
|
-
position: absolute;
|
|
312
|
-
bottom: 20px;
|
|
313
|
-
left: 50%;
|
|
314
|
-
transform: translateX(-50%);
|
|
315
|
-
width: 60px;
|
|
316
|
-
height: 60px;
|
|
317
|
-
border: 3px solid white;
|
|
318
|
-
background-color: rgba(0, 0, 0, 0.7);
|
|
319
|
-
border-radius: 50%;
|
|
320
|
-
cursor: pointer;
|
|
321
|
-
display: none;
|
|
322
|
-
align-items: center;
|
|
323
|
-
justify-content: center;
|
|
324
|
-
transition: all 0.2s ease;
|
|
325
|
-
`;
|
|
326
|
-
// Create close button
|
|
327
|
-
const closeButton = document.createElement("button");
|
|
328
|
-
closeButton.innerHTML = "×";
|
|
329
|
-
closeButton.style.cssText = `
|
|
330
|
-
position: absolute;
|
|
331
|
-
top: 15px;
|
|
332
|
-
right: 15px;
|
|
333
|
-
width: 40px;
|
|
334
|
-
height: 40px;
|
|
335
|
-
border: none;
|
|
336
|
-
background-color: rgba(0, 0, 0, 0.7);
|
|
337
|
-
color: white;
|
|
338
|
-
font-size: 24px;
|
|
339
|
-
font-weight: bold;
|
|
340
|
-
cursor: pointer;
|
|
341
|
-
border-radius: 50%;
|
|
342
|
-
display: flex;
|
|
343
|
-
align-items: center;
|
|
344
|
-
justify-content: center;
|
|
345
|
-
`;
|
|
346
|
-
// Create loading indicator
|
|
347
|
-
const loadingIndicator = document.createElement("div");
|
|
348
|
-
loadingIndicator.innerHTML = "Initializing camera...";
|
|
349
|
-
loadingIndicator.style.cssText = `
|
|
350
|
-
color: white;
|
|
351
|
-
font-size: 16px;
|
|
352
|
-
position: absolute;
|
|
353
|
-
top: 50%;
|
|
354
|
-
left: 50%;
|
|
355
|
-
transform: translate(-50%, -50%);
|
|
356
|
-
`;
|
|
357
|
-
// Add event listeners
|
|
358
|
-
closeButton.onclick = () => {
|
|
359
|
-
stopCamera(video);
|
|
360
|
-
modal.remove();
|
|
361
|
-
};
|
|
362
|
-
captureButton.onclick = async () => {
|
|
363
|
-
await callbacks.onCapture(video, canvas);
|
|
364
|
-
};
|
|
365
|
-
// Assemble the camera modal
|
|
366
|
-
cameraContainer.appendChild(video);
|
|
367
|
-
cameraContainer.appendChild(canvas);
|
|
368
|
-
cameraContainer.appendChild(captureButton);
|
|
369
|
-
cameraContainer.appendChild(closeButton);
|
|
370
|
-
cameraContainer.appendChild(loadingIndicator);
|
|
371
|
-
modal.appendChild(cameraContainer);
|
|
372
|
-
document.body.appendChild(modal);
|
|
373
|
-
console.log("[WeWear VTO] Camera modal added to DOM");
|
|
374
|
-
// Start camera
|
|
375
|
-
startCamera(video, loadingIndicator, captureButton);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
155
|
/**
|
|
379
156
|
* Creates a loading overlay that can be shown in different containers
|
|
380
157
|
*/
|
|
@@ -427,24 +204,6 @@ function createLoadingOverlay(text = "Processing...") {
|
|
|
427
204
|
`;
|
|
428
205
|
return overlay;
|
|
429
206
|
}
|
|
430
|
-
/**
|
|
431
|
-
* Shows a loading overlay in a modal container
|
|
432
|
-
*/
|
|
433
|
-
function showModalLoading(container) {
|
|
434
|
-
// Remove any existing loading overlays
|
|
435
|
-
removeModalLoading();
|
|
436
|
-
// Show loading in the product area
|
|
437
|
-
showProductLoading(container);
|
|
438
|
-
}
|
|
439
|
-
/**
|
|
440
|
-
* Removes loading overlay from modal
|
|
441
|
-
*/
|
|
442
|
-
function removeModalLoading() {
|
|
443
|
-
const existingOverlay = document.body.querySelector(".ww-loading-overlay");
|
|
444
|
-
if (existingOverlay) {
|
|
445
|
-
existingOverlay.remove();
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
207
|
/**
|
|
449
208
|
* Shows a loading overlay in the product gallery container
|
|
450
209
|
*/
|
|
@@ -469,202 +228,115 @@ function removeProductLoading(container) {
|
|
|
469
228
|
}
|
|
470
229
|
}
|
|
471
230
|
|
|
472
|
-
function
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
image.alt = "Review your photo";
|
|
516
|
-
image.style.cssText = `
|
|
517
|
-
width: 100%;
|
|
518
|
-
height: 100%;
|
|
519
|
-
object-fit: cover;
|
|
520
|
-
border-radius: 12px;
|
|
521
|
-
`;
|
|
522
|
-
// Create close button
|
|
523
|
-
const closeButton = document.createElement("button");
|
|
524
|
-
closeButton.innerHTML = "×";
|
|
525
|
-
closeButton.style.cssText = `
|
|
526
|
-
position: absolute;
|
|
527
|
-
top: 15px;
|
|
528
|
-
right: 15px;
|
|
529
|
-
width: 40px;
|
|
530
|
-
height: 40px;
|
|
531
|
-
border: none;
|
|
532
|
-
background-color: rgba(0, 0, 0, 0.7);
|
|
533
|
-
color: white;
|
|
534
|
-
font-size: 24px;
|
|
535
|
-
font-weight: bold;
|
|
536
|
-
cursor: pointer;
|
|
537
|
-
border-radius: 50%;
|
|
538
|
-
display: flex;
|
|
539
|
-
align-items: center;
|
|
540
|
-
justify-content: center;
|
|
541
|
-
`;
|
|
542
|
-
// Create button container
|
|
543
|
-
const buttonContainer = document.createElement("div");
|
|
544
|
-
buttonContainer.style.cssText = `
|
|
545
|
-
display: flex;
|
|
546
|
-
gap: 15px;
|
|
547
|
-
width: 100%;
|
|
548
|
-
max-width: 500px;
|
|
549
|
-
`;
|
|
550
|
-
// Create retake button
|
|
551
|
-
const retakeButton = document.createElement("button");
|
|
552
|
-
retakeButton.textContent = "Retake";
|
|
553
|
-
retakeButton.style.cssText = `
|
|
554
|
-
flex: 1;
|
|
555
|
-
padding: 12px 24px;
|
|
556
|
-
background-color: rgba(255, 255, 255, 0.9);
|
|
557
|
-
color: black;
|
|
558
|
-
border-radius: 8px;
|
|
559
|
-
border: none;
|
|
560
|
-
font-size: 16px;
|
|
561
|
-
font-weight: normal;
|
|
562
|
-
cursor: pointer;
|
|
563
|
-
`;
|
|
564
|
-
// Create use photo button
|
|
565
|
-
const usePhotoButton = document.createElement("button");
|
|
566
|
-
usePhotoButton.textContent = "Use Photo";
|
|
567
|
-
usePhotoButton.style.cssText = `
|
|
568
|
-
flex: 1;
|
|
569
|
-
padding: 12px 24px;
|
|
570
|
-
background-color: rgba(0, 0, 0, 0.7);
|
|
571
|
-
color: white;
|
|
572
|
-
border-radius: 8px;
|
|
573
|
-
border: none;
|
|
574
|
-
font-size: 16px;
|
|
575
|
-
font-weight: normal;
|
|
576
|
-
cursor: pointer;
|
|
577
|
-
`;
|
|
578
|
-
// Add event listeners
|
|
579
|
-
closeButton.onclick = () => {
|
|
580
|
-
URL.revokeObjectURL(imageUrl); // Clean up
|
|
581
|
-
modal.remove();
|
|
582
|
-
};
|
|
583
|
-
retakeButton.onclick = () => {
|
|
584
|
-
URL.revokeObjectURL(imageUrl); // Clean up
|
|
585
|
-
modal.remove();
|
|
586
|
-
callbacks.onRetake();
|
|
587
|
-
};
|
|
588
|
-
usePhotoButton.onclick = async () => {
|
|
589
|
-
URL.revokeObjectURL(imageUrl); // Clean up
|
|
590
|
-
modal.remove();
|
|
591
|
-
await callbacks.onAccept(imageBlob);
|
|
592
|
-
};
|
|
593
|
-
// Assemble the review modal
|
|
594
|
-
buttonContainer.appendChild(retakeButton);
|
|
595
|
-
buttonContainer.appendChild(usePhotoButton);
|
|
596
|
-
reviewContainer.appendChild(image);
|
|
597
|
-
reviewContainer.appendChild(closeButton);
|
|
598
|
-
modal.appendChild(reviewContainer);
|
|
599
|
-
modal.appendChild(buttonContainer);
|
|
600
|
-
document.body.appendChild(modal);
|
|
601
|
-
console.log("[WeWear VTO] Review modal added to DOM");
|
|
231
|
+
function createPreviewBadge() {
|
|
232
|
+
const badge = document.createElement("div");
|
|
233
|
+
badge.className = CSS_CLASSES.PREVIEW_BADGE;
|
|
234
|
+
badge.innerText = "PREVIEW";
|
|
235
|
+
const styleId = "ww-vto-badge-styles";
|
|
236
|
+
if (!document.getElementById(styleId)) {
|
|
237
|
+
const styles = document.createElement("style");
|
|
238
|
+
styles.id = styleId;
|
|
239
|
+
styles.innerHTML = `
|
|
240
|
+
.${CSS_CLASSES.PREVIEW_BADGE} {
|
|
241
|
+
position: absolute;
|
|
242
|
+
top: 16px;
|
|
243
|
+
right: 16px;
|
|
244
|
+
background-color: rgba(0, 0, 0, 0.6);
|
|
245
|
+
color: white;
|
|
246
|
+
padding: 4px 8px;
|
|
247
|
+
border-radius: 4px;
|
|
248
|
+
font-size: 12px;
|
|
249
|
+
font-weight: 600;
|
|
250
|
+
z-index: 10;
|
|
251
|
+
animation: ww-vto-pulse 2s infinite;
|
|
252
|
+
pointer-events: none;
|
|
253
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
@keyframes ww-vto-pulse {
|
|
257
|
+
0% {
|
|
258
|
+
transform: scale(1);
|
|
259
|
+
opacity: 0.9;
|
|
260
|
+
}
|
|
261
|
+
50% {
|
|
262
|
+
transform: scale(1.05);
|
|
263
|
+
opacity: 1;
|
|
264
|
+
}
|
|
265
|
+
100% {
|
|
266
|
+
transform: scale(1);
|
|
267
|
+
opacity: 0.9;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
`;
|
|
271
|
+
document.head.appendChild(styles);
|
|
272
|
+
}
|
|
273
|
+
return badge;
|
|
602
274
|
}
|
|
603
275
|
|
|
604
276
|
function showAlert(container, message, type) {
|
|
605
277
|
var _a;
|
|
606
278
|
removeAlert(container); // Remove any existing alert
|
|
607
|
-
const alertDiv = document.createElement(
|
|
279
|
+
const alertDiv = document.createElement("div");
|
|
608
280
|
alertDiv.className = `ww-alert ww-alert-${type}`;
|
|
609
|
-
alertDiv.setAttribute(
|
|
610
|
-
alertDiv.setAttribute(
|
|
611
|
-
alertDiv.style.cssText = `
|
|
612
|
-
position: absolute;
|
|
613
|
-
top: 16px;
|
|
614
|
-
left: 50%;
|
|
615
|
-
transform: translateX(-50%) translateY(-10px);
|
|
616
|
-
background: ${
|
|
617
|
-
color: ${
|
|
618
|
-
border: 1px solid ${
|
|
619
|
-
border-radius: 8px;
|
|
620
|
-
padding: 12px 16px;
|
|
621
|
-
z-index: 9999;
|
|
622
|
-
font-size: 15px;
|
|
623
|
-
font-weight: 500;
|
|
624
|
-
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
625
|
-
display: flex;
|
|
626
|
-
align-items: center;
|
|
627
|
-
min-width: 220px;
|
|
628
|
-
max-width: 90%;
|
|
629
|
-
opacity: 0;
|
|
630
|
-
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;
|
|
631
303
|
`;
|
|
632
|
-
alertDiv.innerHTML = `
|
|
633
|
-
<span style="flex: 1; line-height: 1.4;">${message}</span>
|
|
634
|
-
<button type="button" aria-label="Close alert"
|
|
635
|
-
style="
|
|
636
|
-
background: none;
|
|
637
|
-
border: none;
|
|
638
|
-
font-size: 20px;
|
|
639
|
-
color: inherit;
|
|
640
|
-
cursor: pointer;
|
|
641
|
-
margin-left: 12px;
|
|
642
|
-
padding: 0;
|
|
643
|
-
line-height: 1;
|
|
644
|
-
">
|
|
645
|
-
×
|
|
646
|
-
</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>
|
|
647
319
|
`;
|
|
648
320
|
// Close on button click
|
|
649
|
-
(_a = alertDiv.querySelector(
|
|
321
|
+
(_a = alertDiv.querySelector("button")) === null || _a === void 0 ? void 0 : _a.addEventListener("click", () => {
|
|
650
322
|
fadeOutAndRemove(alertDiv);
|
|
651
323
|
});
|
|
652
324
|
container.appendChild(alertDiv);
|
|
653
325
|
// Animate in
|
|
654
326
|
requestAnimationFrame(() => {
|
|
655
|
-
alertDiv.style.opacity =
|
|
656
|
-
alertDiv.style.transform =
|
|
327
|
+
alertDiv.style.opacity = "1";
|
|
328
|
+
alertDiv.style.transform = "translateX(-50%) translateY(0)";
|
|
657
329
|
});
|
|
658
330
|
}
|
|
659
331
|
function fadeOutAndRemove(element) {
|
|
660
|
-
element.style.opacity =
|
|
661
|
-
element.style.transform =
|
|
332
|
+
element.style.opacity = "0";
|
|
333
|
+
element.style.transform = "translateX(-50%) translateY(-10px)";
|
|
662
334
|
setTimeout(() => {
|
|
663
335
|
element.remove();
|
|
664
336
|
}, 300);
|
|
665
337
|
}
|
|
666
338
|
function removeAlert(container) {
|
|
667
|
-
const alert = container.querySelector(
|
|
339
|
+
const alert = container.querySelector(".ww-alert");
|
|
668
340
|
if (alert)
|
|
669
341
|
fadeOutAndRemove(alert);
|
|
670
342
|
}
|
|
@@ -672,10 +344,13 @@ function removeAlert(container) {
|
|
|
672
344
|
class VirtualTryOnWidget {
|
|
673
345
|
constructor(config) {
|
|
674
346
|
this.virtualTryOnImageUrl = null;
|
|
347
|
+
this.originalProductImageUrl = null;
|
|
675
348
|
this.isShowingVirtualTryOn = false;
|
|
676
|
-
this.
|
|
349
|
+
this.lastModelImage = null;
|
|
677
350
|
this.cookieCheckInterval = null;
|
|
678
351
|
this.cameraButton = null;
|
|
352
|
+
this.iframeMessageListener = null;
|
|
353
|
+
this.previewBadge = null;
|
|
679
354
|
this.config = {
|
|
680
355
|
baseUrl: config.baseUrl,
|
|
681
356
|
productPageSelector: config.productPageSelector,
|
|
@@ -704,40 +379,18 @@ class VirtualTryOnWidget {
|
|
|
704
379
|
if (getComputedStyle(container).position === "static") {
|
|
705
380
|
container.style.position = "relative";
|
|
706
381
|
}
|
|
707
|
-
// Check required cookies
|
|
708
|
-
const ww_access_token = getCookie("ww_access_token");
|
|
709
|
-
const ww_user_id = getCookie("ww_user_id");
|
|
710
|
-
const cookiesPresent = !!ww_access_token && !!ww_user_id;
|
|
711
382
|
// Create and add the virtual try-on button
|
|
712
383
|
const button = createButton(this.config.buttonPosition, async () => {
|
|
713
384
|
if (this.cameraButton && !this.cameraButton.disabled) {
|
|
714
385
|
await this.handleTryOnClick();
|
|
715
386
|
}
|
|
716
|
-
}
|
|
387
|
+
});
|
|
717
388
|
// Store reference to camera button for dynamic enable/disable
|
|
718
|
-
this.cameraButton = button.querySelector(
|
|
389
|
+
this.cameraButton = button.querySelector(".ww-camera-btn");
|
|
719
390
|
container.appendChild(button);
|
|
720
391
|
console.log("[WeWear VTO] Widget initialized successfully");
|
|
721
|
-
//
|
|
722
|
-
this.
|
|
723
|
-
const accessToken = getCookie("ww_access_token");
|
|
724
|
-
const userId = getCookie("ww_user_id");
|
|
725
|
-
const present = !!accessToken && !!userId;
|
|
726
|
-
if (this.cameraButton) {
|
|
727
|
-
if (present && this.cameraButton.disabled) {
|
|
728
|
-
this.cameraButton.disabled = false;
|
|
729
|
-
this.cameraButton.style.opacity = '1';
|
|
730
|
-
this.cameraButton.style.cursor = 'pointer';
|
|
731
|
-
this.cameraButton.title = '';
|
|
732
|
-
}
|
|
733
|
-
else if (!present && !this.cameraButton.disabled) {
|
|
734
|
-
this.cameraButton.disabled = true;
|
|
735
|
-
this.cameraButton.style.opacity = '0.5';
|
|
736
|
-
this.cameraButton.style.cursor = 'not-allowed';
|
|
737
|
-
this.cameraButton.title = 'Required cookies missing';
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
}, 2000); // check every 2 seconds
|
|
392
|
+
// Listen for messages from the photo upload page
|
|
393
|
+
this.setupIframeListener();
|
|
741
394
|
}
|
|
742
395
|
catch (error) {
|
|
743
396
|
console.error("[WeWear VTO] Initialization failed:", error);
|
|
@@ -751,152 +404,214 @@ class VirtualTryOnWidget {
|
|
|
751
404
|
console.log("[WeWear VTO] Button clicked, starting try-on process...");
|
|
752
405
|
try {
|
|
753
406
|
// Get required data
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
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;
|
|
758
415
|
console.log("[WeWear VTO] Retrieved data:", {
|
|
759
|
-
ww_user_id,
|
|
760
416
|
ww_product_image,
|
|
761
|
-
ww_access_token,
|
|
762
417
|
});
|
|
763
|
-
// Validate required data
|
|
764
|
-
if (!ww_user_id) {
|
|
765
|
-
console.warn("[WeWear VTO] Missing required cookie: ww_user_id");
|
|
766
|
-
return;
|
|
767
|
-
}
|
|
768
418
|
if (!ww_product_image) {
|
|
769
419
|
console.warn("[WeWear VTO] Product image not found:", this.config.productImageSelector);
|
|
770
420
|
return;
|
|
771
421
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
// Show camera capture modal
|
|
777
|
-
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());
|
|
778
426
|
}
|
|
779
427
|
catch (error) {
|
|
780
428
|
console.error("[WeWear VTO] Try-on request failed:", error);
|
|
781
429
|
}
|
|
782
430
|
}
|
|
783
431
|
/**
|
|
784
|
-
* Shows
|
|
785
|
-
* @private
|
|
786
|
-
*/
|
|
787
|
-
showCameraModalWithCallbacks(ww_access_token, ww_user_id, ww_product_image) {
|
|
788
|
-
const callbacks = {
|
|
789
|
-
onCapture: async (video, canvas) => {
|
|
790
|
-
try {
|
|
791
|
-
// Capture image from video
|
|
792
|
-
const imageBlob = await captureImageFromVideo(video, canvas);
|
|
793
|
-
// Stop camera and show review modal
|
|
794
|
-
stopCamera(video);
|
|
795
|
-
// Show review modal instead of immediately processing
|
|
796
|
-
this.showReviewModalWithCallbacks(imageBlob, ww_access_token, ww_user_id, ww_product_image);
|
|
797
|
-
}
|
|
798
|
-
catch (error) {
|
|
799
|
-
console.error("[WeWear VTO] Image capture error:", error);
|
|
800
|
-
}
|
|
801
|
-
},
|
|
802
|
-
};
|
|
803
|
-
showCameraModal(callbacks);
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* Shows review modal with appropriate callbacks
|
|
432
|
+
* Shows a modal with an iframe for the photo upload page
|
|
807
433
|
* @private
|
|
808
434
|
*/
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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();
|
|
818
477
|
};
|
|
819
|
-
|
|
478
|
+
modal.appendChild(iframe);
|
|
479
|
+
modal.appendChild(closeButton);
|
|
480
|
+
document.body.appendChild(modal);
|
|
820
481
|
}
|
|
821
482
|
/**
|
|
822
|
-
*
|
|
483
|
+
* Sets up listener for messages from the iframe
|
|
823
484
|
* @private
|
|
824
485
|
*/
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
this.
|
|
832
|
-
|
|
833
|
-
ww_access_token,
|
|
834
|
-
ww_user_id,
|
|
835
|
-
ww_product_image,
|
|
836
|
-
};
|
|
837
|
-
// Call the API with the accepted image
|
|
838
|
-
const result = await callVirtualTryOnApi(this.config.baseUrl, ww_access_token, ww_user_id, ww_product_image, imageBlob);
|
|
839
|
-
// Remove modal loading and close modal
|
|
840
|
-
removeModalLoading();
|
|
841
|
-
removeElements(`.${CSS_CLASSES.MODAL}`);
|
|
842
|
-
if (result === null || result === void 0 ? void 0 : result.imageUrl) {
|
|
843
|
-
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;
|
|
844
494
|
}
|
|
845
|
-
|
|
846
|
-
|
|
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;
|
|
847
505
|
}
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
console.error("[WeWear VTO] Error processing accepted image:", error);
|
|
851
|
-
// Remove loading on error
|
|
852
|
-
removeModalLoading();
|
|
853
|
-
}
|
|
506
|
+
};
|
|
507
|
+
window.addEventListener("message", this.iframeMessageListener);
|
|
854
508
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
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.");
|
|
862
515
|
return;
|
|
863
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
|
+
}
|
|
864
526
|
try {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
const
|
|
868
|
-
|
|
869
|
-
|
|
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
|
+
}
|
|
870
548
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
this.showVirtualTryOnImage(container);
|
|
880
|
-
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;
|
|
881
557
|
}
|
|
882
558
|
}
|
|
883
|
-
|
|
884
|
-
|
|
559
|
+
if (status.status === "FAILED") {
|
|
560
|
+
console.error("[WeWear VTO] VTO process failed:", status.message);
|
|
885
561
|
if (container instanceof HTMLElement) {
|
|
886
|
-
|
|
887
|
-
showAlert(container, result.error.message, result.error.type);
|
|
562
|
+
showAlert(container, status.message, "error");
|
|
888
563
|
}
|
|
889
564
|
}
|
|
890
565
|
}
|
|
891
566
|
catch (error) {
|
|
892
|
-
console.error("[WeWear VTO] Error
|
|
893
|
-
|
|
894
|
-
|
|
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 {
|
|
895
573
|
if (container instanceof HTMLElement) {
|
|
896
574
|
removeProductLoading(container);
|
|
897
575
|
}
|
|
576
|
+
removeElements(`.${CSS_CLASSES.MODAL}`);
|
|
898
577
|
}
|
|
899
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
|
+
}
|
|
900
615
|
/**
|
|
901
616
|
* Replaces the product image in the gallery with the virtual try-on result
|
|
902
617
|
* @private
|
|
@@ -916,8 +631,9 @@ class VirtualTryOnWidget {
|
|
|
916
631
|
container.setAttribute("data-ww-original-content", container.innerHTML);
|
|
917
632
|
}
|
|
918
633
|
// Capture original image dimensions before replacement to prevent layout shift
|
|
919
|
-
const originalImg = container.querySelector(
|
|
920
|
-
if (originalImg &&
|
|
634
|
+
const originalImg = container.querySelector("img");
|
|
635
|
+
if (originalImg &&
|
|
636
|
+
!container.hasAttribute("data-ww-original-dimensions")) {
|
|
921
637
|
const computedStyle = window.getComputedStyle(originalImg);
|
|
922
638
|
const rect = originalImg.getBoundingClientRect();
|
|
923
639
|
container.setAttribute("data-ww-original-width", computedStyle.width);
|
|
@@ -958,6 +674,7 @@ class VirtualTryOnWidget {
|
|
|
958
674
|
else {
|
|
959
675
|
this.showVirtualTryOnImage(container);
|
|
960
676
|
}
|
|
677
|
+
removeProductLoading(container);
|
|
961
678
|
this.updateButtonContainer(container);
|
|
962
679
|
}, this.isShowingVirtualTryOn);
|
|
963
680
|
container.appendChild(buttonContainer);
|
|
@@ -971,44 +688,46 @@ class VirtualTryOnWidget {
|
|
|
971
688
|
console.warn("[WeWear VTO] No virtual try-on image URL available");
|
|
972
689
|
return;
|
|
973
690
|
}
|
|
974
|
-
//
|
|
975
|
-
|
|
976
|
-
|
|
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
|
+
});
|
|
977
697
|
// Get stored original dimensions to prevent layout shift
|
|
978
|
-
const originalWidth = container.getAttribute("data-ww-original-width") ||
|
|
979
|
-
const originalHeight = container.getAttribute("data-ww-original-height") ||
|
|
980
|
-
const originalRectWidth = container.getAttribute("data-ww-original-rect-width") ||
|
|
981
|
-
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") || "";
|
|
982
702
|
const image = document.createElement("img");
|
|
983
703
|
image.src = this.virtualTryOnImageUrl;
|
|
984
704
|
image.alt = "Virtual Try-On Result";
|
|
985
705
|
// Use original dimensions to prevent layout shift
|
|
986
|
-
let widthStyle =
|
|
987
|
-
let heightStyle =
|
|
706
|
+
let widthStyle = "100%";
|
|
707
|
+
let heightStyle = "100%";
|
|
988
708
|
// Prefer computed style dimensions, fallback to bounding rect, then container fill
|
|
989
|
-
if (originalWidth && originalWidth !==
|
|
709
|
+
if (originalWidth && originalWidth !== "auto" && originalWidth !== "0px") {
|
|
990
710
|
widthStyle = originalWidth;
|
|
991
711
|
}
|
|
992
|
-
else if (originalRectWidth && originalRectWidth !==
|
|
712
|
+
else if (originalRectWidth && originalRectWidth !== "0") {
|
|
993
713
|
widthStyle = `${originalRectWidth}px`;
|
|
994
714
|
}
|
|
995
|
-
if (originalHeight &&
|
|
715
|
+
if (originalHeight &&
|
|
716
|
+
originalHeight !== "auto" &&
|
|
717
|
+
originalHeight !== "0px") {
|
|
996
718
|
heightStyle = originalHeight;
|
|
997
719
|
}
|
|
998
|
-
else if (originalRectHeight && originalRectHeight !==
|
|
720
|
+
else if (originalRectHeight && originalRectHeight !== "0") {
|
|
999
721
|
heightStyle = `${originalRectHeight}px`;
|
|
1000
722
|
}
|
|
1001
|
-
image.style.cssText = `
|
|
1002
|
-
width: ${widthStyle};
|
|
1003
|
-
height: ${heightStyle};
|
|
1004
|
-
object-fit: cover;
|
|
1005
|
-
border-radius: 8px;
|
|
723
|
+
image.style.cssText = `
|
|
724
|
+
width: ${widthStyle};
|
|
725
|
+
height: ${heightStyle};
|
|
726
|
+
object-fit: cover;
|
|
727
|
+
border-radius: 8px;
|
|
1006
728
|
`;
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
existingButtons.forEach((btn) => {
|
|
1010
|
-
container.appendChild(btn);
|
|
1011
|
-
});
|
|
729
|
+
// Prepend the image to ensure buttons are rendered on top
|
|
730
|
+
container.prepend(image);
|
|
1012
731
|
this.isShowingVirtualTryOn = true;
|
|
1013
732
|
}
|
|
1014
733
|
/**
|
|
@@ -1016,16 +735,18 @@ class VirtualTryOnWidget {
|
|
|
1016
735
|
* @private
|
|
1017
736
|
*/
|
|
1018
737
|
showOriginalImage(container) {
|
|
1019
|
-
const
|
|
1020
|
-
if (
|
|
1021
|
-
//
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
existingButtons.forEach((btn) => {
|
|
1027
|
-
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
|
+
}
|
|
1028
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));
|
|
1029
750
|
}
|
|
1030
751
|
this.isShowingVirtualTryOn = false;
|
|
1031
752
|
}
|
|
@@ -1044,10 +765,20 @@ class VirtualTryOnWidget {
|
|
|
1044
765
|
this.cookieCheckInterval = null;
|
|
1045
766
|
}
|
|
1046
767
|
this.cameraButton = null;
|
|
768
|
+
// Remove message listener
|
|
769
|
+
if (this.iframeMessageListener) {
|
|
770
|
+
window.removeEventListener("message", this.iframeMessageListener);
|
|
771
|
+
this.iframeMessageListener = null;
|
|
772
|
+
}
|
|
1047
773
|
// Reset state
|
|
1048
774
|
this.virtualTryOnImageUrl = null;
|
|
1049
775
|
this.isShowingVirtualTryOn = false;
|
|
1050
|
-
this.
|
|
776
|
+
this.lastModelImage = null;
|
|
777
|
+
this.originalProductImageUrl = null;
|
|
778
|
+
if (this.previewBadge) {
|
|
779
|
+
this.previewBadge.remove();
|
|
780
|
+
this.previewBadge = null;
|
|
781
|
+
}
|
|
1051
782
|
console.log("[WeWear VTO] Widget destroyed successfully");
|
|
1052
783
|
}
|
|
1053
784
|
catch (error) {
|
|
@@ -1089,18 +820,6 @@ function initVirtualTryOn(config) {
|
|
|
1089
820
|
console.error("[WeWear VTO] Initialization error:", error);
|
|
1090
821
|
}
|
|
1091
822
|
}
|
|
1092
|
-
function destroyVirtualTryOn() {
|
|
1093
|
-
try {
|
|
1094
|
-
if (widgetInstance) {
|
|
1095
|
-
widgetInstance.destroy();
|
|
1096
|
-
widgetInstance = null;
|
|
1097
|
-
console.log("[WeWear VTO] Widget instance destroyed");
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
catch (error) {
|
|
1101
|
-
console.error("[WeWear VTO] Error destroying widget:", error);
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
823
|
function getWidgetInstance() {
|
|
1105
824
|
return widgetInstance;
|
|
1106
825
|
}
|
|
@@ -1108,5 +827,5 @@ if (typeof window !== "undefined") {
|
|
|
1108
827
|
initVirtualTryOn();
|
|
1109
828
|
}
|
|
1110
829
|
|
|
1111
|
-
export { VirtualTryOnWidget,
|
|
830
|
+
export { VirtualTryOnWidget, getWidgetInstance, initVirtualTryOn };
|
|
1112
831
|
//# sourceMappingURL=index.esm.js.map
|