@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.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(
|
|
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 =
|
|
255
|
-
cameraButton.style.cursor =
|
|
256
|
-
cameraButton.title =
|
|
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
|
|
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
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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(
|
|
285
|
+
const alertDiv = document.createElement("div");
|
|
614
286
|
alertDiv.className = `ww-alert ww-alert-${type}`;
|
|
615
|
-
alertDiv.setAttribute(
|
|
616
|
-
alertDiv.setAttribute(
|
|
617
|
-
alertDiv.style.cssText = `
|
|
618
|
-
position: absolute;
|
|
619
|
-
top: 16px;
|
|
620
|
-
left: 50%;
|
|
621
|
-
transform: translateX(-50%) translateY(-10px);
|
|
622
|
-
background: ${
|
|
623
|
-
color: ${
|
|
624
|
-
border: 1px solid ${
|
|
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
|
-
×
|
|
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
|
+
×
|
|
324
|
+
</button>
|
|
653
325
|
`;
|
|
654
326
|
// Close on button click
|
|
655
|
-
(_a = alertDiv.querySelector(
|
|
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 =
|
|
662
|
-
alertDiv.style.transform =
|
|
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 =
|
|
667
|
-
element.style.transform =
|
|
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(
|
|
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.
|
|
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
|
-
}
|
|
393
|
+
});
|
|
723
394
|
// Store reference to camera button for dynamic enable/disable
|
|
724
|
-
this.cameraButton = button.querySelector(
|
|
395
|
+
this.cameraButton = button.querySelector(".ww-camera-btn");
|
|
725
396
|
container.appendChild(button);
|
|
726
397
|
console.log("[WeWear VTO] Widget initialized successfully");
|
|
727
|
-
//
|
|
728
|
-
this.
|
|
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
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
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
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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
|
|
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
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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
|
-
|
|
484
|
+
modal.appendChild(iframe);
|
|
485
|
+
modal.appendChild(closeButton);
|
|
486
|
+
document.body.appendChild(modal);
|
|
826
487
|
}
|
|
827
488
|
/**
|
|
828
|
-
*
|
|
489
|
+
* Sets up listener for messages from the iframe
|
|
829
490
|
* @private
|
|
830
491
|
*/
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
this.
|
|
838
|
-
|
|
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
|
-
|
|
852
|
-
|
|
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
|
-
|
|
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
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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
|
-
|
|
872
|
-
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
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
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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
|
-
|
|
890
|
-
|
|
565
|
+
if (status.status === "FAILED") {
|
|
566
|
+
console.error("[WeWear VTO] VTO process failed:", status.message);
|
|
891
567
|
if (container instanceof HTMLElement) {
|
|
892
|
-
|
|
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
|
|
899
|
-
|
|
900
|
-
|
|
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(
|
|
926
|
-
if (originalImg &&
|
|
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
|
-
//
|
|
981
|
-
|
|
982
|
-
|
|
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 =
|
|
993
|
-
let heightStyle =
|
|
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 !==
|
|
715
|
+
if (originalWidth && originalWidth !== "auto" && originalWidth !== "0px") {
|
|
996
716
|
widthStyle = originalWidth;
|
|
997
717
|
}
|
|
998
|
-
else if (originalRectWidth && originalRectWidth !==
|
|
718
|
+
else if (originalRectWidth && originalRectWidth !== "0") {
|
|
999
719
|
widthStyle = `${originalRectWidth}px`;
|
|
1000
720
|
}
|
|
1001
|
-
if (originalHeight &&
|
|
721
|
+
if (originalHeight &&
|
|
722
|
+
originalHeight !== "auto" &&
|
|
723
|
+
originalHeight !== "0px") {
|
|
1002
724
|
heightStyle = originalHeight;
|
|
1003
725
|
}
|
|
1004
|
-
else if (originalRectHeight && originalRectHeight !==
|
|
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
|
-
|
|
1014
|
-
|
|
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
|
|
1026
|
-
if (
|
|
1027
|
-
//
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
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.
|
|
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
|
|