@wewear/virtual-try-on 1.4.0 → 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 -636
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +320 -636
- 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
19
|
|
|
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
|
-
|
|
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,174 +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 face overlay
|
|
314
|
-
const faceOverlay = document.createElement("div");
|
|
315
|
-
faceOverlay.style.cssText = `
|
|
316
|
-
position: absolute;
|
|
317
|
-
top: 0;
|
|
318
|
-
left: 0;
|
|
319
|
-
width: 100%;
|
|
320
|
-
height: 100%;
|
|
321
|
-
pointer-events: none;
|
|
322
|
-
display: flex;
|
|
323
|
-
align-items: center;
|
|
324
|
-
justify-content: center;
|
|
325
|
-
z-index: 2;
|
|
326
|
-
`;
|
|
327
|
-
// SVG face outline (simple oval)
|
|
328
|
-
faceOverlay.innerHTML = `
|
|
329
|
-
<svg width="380" height="520" viewBox="0 0 380 520" fill="none" xmlns="http://www.w3.org/2000/svg" style="opacity:0.7;">
|
|
330
|
-
<ellipse cx="190" cy="240" rx="160" ry="220" stroke="rgba(0, 0, 0, 0.7)" stroke-width="6" fill="none" />
|
|
331
|
-
</svg>
|
|
332
|
-
`;
|
|
333
|
-
// Create capture button
|
|
334
|
-
const captureButton = document.createElement("button");
|
|
335
|
-
captureButton.innerHTML = "";
|
|
336
|
-
captureButton.style.cssText = `
|
|
337
|
-
position: absolute;
|
|
338
|
-
bottom: 20px;
|
|
339
|
-
left: 50%;
|
|
340
|
-
transform: translateX(-50%);
|
|
341
|
-
width: 60px;
|
|
342
|
-
height: 60px;
|
|
343
|
-
border: 3px solid white;
|
|
344
|
-
background-color: rgba(0, 0, 0, 0.7);
|
|
345
|
-
border-radius: 50%;
|
|
346
|
-
cursor: pointer;
|
|
347
|
-
display: flex;
|
|
348
|
-
align-items: center;
|
|
349
|
-
justify-content: center;
|
|
350
|
-
transition: all 0.2s ease;
|
|
351
|
-
`;
|
|
352
|
-
// Create close button
|
|
353
|
-
const closeButton = document.createElement("button");
|
|
354
|
-
closeButton.innerHTML = "×";
|
|
355
|
-
closeButton.style.cssText = `
|
|
356
|
-
position: absolute;
|
|
357
|
-
top: 15px;
|
|
358
|
-
right: 15px;
|
|
359
|
-
width: 40px;
|
|
360
|
-
height: 40px;
|
|
361
|
-
border: none;
|
|
362
|
-
background-color: rgba(0, 0, 0, 0.7);
|
|
363
|
-
color: white;
|
|
364
|
-
font-size: 24px;
|
|
365
|
-
font-weight: bold;
|
|
366
|
-
cursor: pointer;
|
|
367
|
-
border-radius: 50%;
|
|
368
|
-
display: flex;
|
|
369
|
-
align-items: center;
|
|
370
|
-
justify-content: center;
|
|
371
|
-
`;
|
|
372
|
-
// Create loading indicator
|
|
373
|
-
const loadingIndicator = document.createElement("div");
|
|
374
|
-
loadingIndicator.innerHTML = "Initializing camera...";
|
|
375
|
-
loadingIndicator.style.cssText = `
|
|
376
|
-
color: white;
|
|
377
|
-
font-size: 16px;
|
|
378
|
-
position: absolute;
|
|
379
|
-
top: 50%;
|
|
380
|
-
left: 50%;
|
|
381
|
-
transform: translate(-50%, -50%);
|
|
382
|
-
`;
|
|
383
|
-
// Add event listeners
|
|
384
|
-
closeButton.onclick = () => {
|
|
385
|
-
stopCamera(video);
|
|
386
|
-
modal.remove();
|
|
387
|
-
};
|
|
388
|
-
captureButton.onclick = async () => {
|
|
389
|
-
await callbacks.onCapture(video, canvas);
|
|
390
|
-
};
|
|
391
|
-
// Assemble the camera modal
|
|
392
|
-
cameraContainer.appendChild(video);
|
|
393
|
-
cameraContainer.appendChild(faceOverlay);
|
|
394
|
-
cameraContainer.appendChild(canvas);
|
|
395
|
-
cameraContainer.appendChild(captureButton);
|
|
396
|
-
cameraContainer.appendChild(closeButton);
|
|
397
|
-
modal.appendChild(cameraContainer);
|
|
398
|
-
// Add instruction text outside the camera box
|
|
399
|
-
const instructionText = document.createElement("div");
|
|
400
|
-
instructionText.style.cssText = `
|
|
401
|
-
width: 100%;
|
|
402
|
-
max-width: 500px;
|
|
403
|
-
margin: 18px auto 0 auto;
|
|
404
|
-
text-align: center;
|
|
405
|
-
color: #fff;
|
|
406
|
-
font-size: 18px;
|
|
407
|
-
font-weight: bold;
|
|
408
|
-
text-shadow: 0 2px 8px #000;
|
|
409
|
-
`;
|
|
410
|
-
instructionText.innerText = "Please align your face inside the outline for better results.";
|
|
411
|
-
modal.appendChild(instructionText);
|
|
412
|
-
document.body.appendChild(modal);
|
|
413
|
-
console.log("[WeWear VTO] Camera modal added to DOM");
|
|
414
|
-
// Start camera
|
|
415
|
-
startCamera(video, loadingIndicator, captureButton);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
161
|
/**
|
|
419
162
|
* Creates a loading overlay that can be shown in different containers
|
|
420
163
|
*/
|
|
@@ -467,24 +210,6 @@
|
|
|
467
210
|
`;
|
|
468
211
|
return overlay;
|
|
469
212
|
}
|
|
470
|
-
/**
|
|
471
|
-
* Shows a loading overlay in a modal container
|
|
472
|
-
*/
|
|
473
|
-
function showModalLoading(container) {
|
|
474
|
-
// Remove any existing loading overlays
|
|
475
|
-
removeModalLoading();
|
|
476
|
-
// Show loading in the product area
|
|
477
|
-
showProductLoading(container);
|
|
478
|
-
}
|
|
479
|
-
/**
|
|
480
|
-
* Removes loading overlay from modal
|
|
481
|
-
*/
|
|
482
|
-
function removeModalLoading() {
|
|
483
|
-
const existingOverlay = document.body.querySelector(".ww-loading-overlay");
|
|
484
|
-
if (existingOverlay) {
|
|
485
|
-
existingOverlay.remove();
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
213
|
/**
|
|
489
214
|
* Shows a loading overlay in the product gallery container
|
|
490
215
|
*/
|
|
@@ -509,202 +234,115 @@
|
|
|
509
234
|
}
|
|
510
235
|
}
|
|
511
236
|
|
|
512
|
-
function
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
image.alt = "Review your photo";
|
|
556
|
-
image.style.cssText = `
|
|
557
|
-
width: 100%;
|
|
558
|
-
height: 100%;
|
|
559
|
-
object-fit: cover;
|
|
560
|
-
border-radius: 12px;
|
|
561
|
-
`;
|
|
562
|
-
// Create close button
|
|
563
|
-
const closeButton = document.createElement("button");
|
|
564
|
-
closeButton.innerHTML = "×";
|
|
565
|
-
closeButton.style.cssText = `
|
|
566
|
-
position: absolute;
|
|
567
|
-
top: 15px;
|
|
568
|
-
right: 15px;
|
|
569
|
-
width: 40px;
|
|
570
|
-
height: 40px;
|
|
571
|
-
border: none;
|
|
572
|
-
background-color: rgba(0, 0, 0, 0.7);
|
|
573
|
-
color: white;
|
|
574
|
-
font-size: 24px;
|
|
575
|
-
font-weight: bold;
|
|
576
|
-
cursor: pointer;
|
|
577
|
-
border-radius: 50%;
|
|
578
|
-
display: flex;
|
|
579
|
-
align-items: center;
|
|
580
|
-
justify-content: center;
|
|
581
|
-
`;
|
|
582
|
-
// Create button container
|
|
583
|
-
const buttonContainer = document.createElement("div");
|
|
584
|
-
buttonContainer.style.cssText = `
|
|
585
|
-
display: flex;
|
|
586
|
-
gap: 15px;
|
|
587
|
-
width: 100%;
|
|
588
|
-
max-width: 500px;
|
|
589
|
-
`;
|
|
590
|
-
// Create retake button
|
|
591
|
-
const retakeButton = document.createElement("button");
|
|
592
|
-
retakeButton.textContent = "Retake";
|
|
593
|
-
retakeButton.style.cssText = `
|
|
594
|
-
flex: 1;
|
|
595
|
-
padding: 12px 24px;
|
|
596
|
-
background-color: rgba(255, 255, 255, 0.9);
|
|
597
|
-
color: black;
|
|
598
|
-
border-radius: 8px;
|
|
599
|
-
border: none;
|
|
600
|
-
font-size: 16px;
|
|
601
|
-
font-weight: normal;
|
|
602
|
-
cursor: pointer;
|
|
603
|
-
`;
|
|
604
|
-
// Create use photo button
|
|
605
|
-
const usePhotoButton = document.createElement("button");
|
|
606
|
-
usePhotoButton.textContent = "Use Photo";
|
|
607
|
-
usePhotoButton.style.cssText = `
|
|
608
|
-
flex: 1;
|
|
609
|
-
padding: 12px 24px;
|
|
610
|
-
background-color: rgba(0, 0, 0, 0.7);
|
|
611
|
-
color: white;
|
|
612
|
-
border-radius: 8px;
|
|
613
|
-
border: none;
|
|
614
|
-
font-size: 16px;
|
|
615
|
-
font-weight: normal;
|
|
616
|
-
cursor: pointer;
|
|
617
|
-
`;
|
|
618
|
-
// Add event listeners
|
|
619
|
-
closeButton.onclick = () => {
|
|
620
|
-
URL.revokeObjectURL(imageUrl); // Clean up
|
|
621
|
-
modal.remove();
|
|
622
|
-
};
|
|
623
|
-
retakeButton.onclick = () => {
|
|
624
|
-
URL.revokeObjectURL(imageUrl); // Clean up
|
|
625
|
-
modal.remove();
|
|
626
|
-
callbacks.onRetake();
|
|
627
|
-
};
|
|
628
|
-
usePhotoButton.onclick = async () => {
|
|
629
|
-
URL.revokeObjectURL(imageUrl); // Clean up
|
|
630
|
-
modal.remove();
|
|
631
|
-
await callbacks.onAccept(imageBlob);
|
|
632
|
-
};
|
|
633
|
-
// Assemble the review modal
|
|
634
|
-
buttonContainer.appendChild(retakeButton);
|
|
635
|
-
buttonContainer.appendChild(usePhotoButton);
|
|
636
|
-
reviewContainer.appendChild(image);
|
|
637
|
-
reviewContainer.appendChild(closeButton);
|
|
638
|
-
modal.appendChild(reviewContainer);
|
|
639
|
-
modal.appendChild(buttonContainer);
|
|
640
|
-
document.body.appendChild(modal);
|
|
641
|
-
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;
|
|
642
280
|
}
|
|
643
281
|
|
|
644
282
|
function showAlert(container, message, type) {
|
|
645
283
|
var _a;
|
|
646
284
|
removeAlert(container); // Remove any existing alert
|
|
647
|
-
const alertDiv = document.createElement(
|
|
285
|
+
const alertDiv = document.createElement("div");
|
|
648
286
|
alertDiv.className = `ww-alert ww-alert-${type}`;
|
|
649
|
-
alertDiv.setAttribute(
|
|
650
|
-
alertDiv.setAttribute(
|
|
651
|
-
alertDiv.style.cssText = `
|
|
652
|
-
position: absolute;
|
|
653
|
-
top: 16px;
|
|
654
|
-
left: 50%;
|
|
655
|
-
transform: translateX(-50%) translateY(-10px);
|
|
656
|
-
background: ${
|
|
657
|
-
color: ${
|
|
658
|
-
border: 1px solid ${
|
|
659
|
-
border-radius: 8px;
|
|
660
|
-
padding: 12px 16px;
|
|
661
|
-
z-index: 9999;
|
|
662
|
-
font-size: 15px;
|
|
663
|
-
font-weight: 500;
|
|
664
|
-
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
665
|
-
display: flex;
|
|
666
|
-
align-items: center;
|
|
667
|
-
min-width: 220px;
|
|
668
|
-
max-width: 90%;
|
|
669
|
-
opacity: 0;
|
|
670
|
-
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;
|
|
671
309
|
`;
|
|
672
|
-
alertDiv.innerHTML = `
|
|
673
|
-
<span style="flex: 1; line-height: 1.4;">${message}</span>
|
|
674
|
-
<button type="button" aria-label="Close alert"
|
|
675
|
-
style="
|
|
676
|
-
background: none;
|
|
677
|
-
border: none;
|
|
678
|
-
font-size: 20px;
|
|
679
|
-
color: inherit;
|
|
680
|
-
cursor: pointer;
|
|
681
|
-
margin-left: 12px;
|
|
682
|
-
padding: 0;
|
|
683
|
-
line-height: 1;
|
|
684
|
-
">
|
|
685
|
-
×
|
|
686
|
-
</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>
|
|
687
325
|
`;
|
|
688
326
|
// Close on button click
|
|
689
|
-
(_a = alertDiv.querySelector(
|
|
327
|
+
(_a = alertDiv.querySelector("button")) === null || _a === void 0 ? void 0 : _a.addEventListener("click", () => {
|
|
690
328
|
fadeOutAndRemove(alertDiv);
|
|
691
329
|
});
|
|
692
330
|
container.appendChild(alertDiv);
|
|
693
331
|
// Animate in
|
|
694
332
|
requestAnimationFrame(() => {
|
|
695
|
-
alertDiv.style.opacity =
|
|
696
|
-
alertDiv.style.transform =
|
|
333
|
+
alertDiv.style.opacity = "1";
|
|
334
|
+
alertDiv.style.transform = "translateX(-50%) translateY(0)";
|
|
697
335
|
});
|
|
698
336
|
}
|
|
699
337
|
function fadeOutAndRemove(element) {
|
|
700
|
-
element.style.opacity =
|
|
701
|
-
element.style.transform =
|
|
338
|
+
element.style.opacity = "0";
|
|
339
|
+
element.style.transform = "translateX(-50%) translateY(-10px)";
|
|
702
340
|
setTimeout(() => {
|
|
703
341
|
element.remove();
|
|
704
342
|
}, 300);
|
|
705
343
|
}
|
|
706
344
|
function removeAlert(container) {
|
|
707
|
-
const alert = container.querySelector(
|
|
345
|
+
const alert = container.querySelector(".ww-alert");
|
|
708
346
|
if (alert)
|
|
709
347
|
fadeOutAndRemove(alert);
|
|
710
348
|
}
|
|
@@ -712,10 +350,13 @@
|
|
|
712
350
|
class VirtualTryOnWidget {
|
|
713
351
|
constructor(config) {
|
|
714
352
|
this.virtualTryOnImageUrl = null;
|
|
353
|
+
this.originalProductImageUrl = null;
|
|
715
354
|
this.isShowingVirtualTryOn = false;
|
|
716
|
-
this.
|
|
355
|
+
this.lastModelImage = null;
|
|
717
356
|
this.cookieCheckInterval = null;
|
|
718
357
|
this.cameraButton = null;
|
|
358
|
+
this.iframeMessageListener = null;
|
|
359
|
+
this.previewBadge = null;
|
|
719
360
|
this.config = {
|
|
720
361
|
baseUrl: config.baseUrl,
|
|
721
362
|
productPageSelector: config.productPageSelector,
|
|
@@ -744,40 +385,18 @@
|
|
|
744
385
|
if (getComputedStyle(container).position === "static") {
|
|
745
386
|
container.style.position = "relative";
|
|
746
387
|
}
|
|
747
|
-
// Check required cookies
|
|
748
|
-
const ww_access_token = getCookie("ww_access_token");
|
|
749
|
-
const ww_user_id = getCookie("ww_user_id");
|
|
750
|
-
const cookiesPresent = !!ww_access_token && !!ww_user_id;
|
|
751
388
|
// Create and add the virtual try-on button
|
|
752
389
|
const button = createButton(this.config.buttonPosition, async () => {
|
|
753
390
|
if (this.cameraButton && !this.cameraButton.disabled) {
|
|
754
391
|
await this.handleTryOnClick();
|
|
755
392
|
}
|
|
756
|
-
}
|
|
393
|
+
});
|
|
757
394
|
// Store reference to camera button for dynamic enable/disable
|
|
758
|
-
this.cameraButton = button.querySelector(
|
|
395
|
+
this.cameraButton = button.querySelector(".ww-camera-btn");
|
|
759
396
|
container.appendChild(button);
|
|
760
397
|
console.log("[WeWear VTO] Widget initialized successfully");
|
|
761
|
-
//
|
|
762
|
-
this.
|
|
763
|
-
const accessToken = getCookie("ww_access_token");
|
|
764
|
-
const userId = getCookie("ww_user_id");
|
|
765
|
-
const present = !!accessToken && !!userId;
|
|
766
|
-
if (this.cameraButton) {
|
|
767
|
-
if (present && this.cameraButton.disabled) {
|
|
768
|
-
this.cameraButton.disabled = false;
|
|
769
|
-
this.cameraButton.style.opacity = '1';
|
|
770
|
-
this.cameraButton.style.cursor = 'pointer';
|
|
771
|
-
this.cameraButton.title = '';
|
|
772
|
-
}
|
|
773
|
-
else if (!present && !this.cameraButton.disabled) {
|
|
774
|
-
this.cameraButton.disabled = true;
|
|
775
|
-
this.cameraButton.style.opacity = '0.5';
|
|
776
|
-
this.cameraButton.style.cursor = 'not-allowed';
|
|
777
|
-
this.cameraButton.title = 'Required cookies missing';
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}, 2000); // check every 2 seconds
|
|
398
|
+
// Listen for messages from the photo upload page
|
|
399
|
+
this.setupIframeListener();
|
|
781
400
|
}
|
|
782
401
|
catch (error) {
|
|
783
402
|
console.error("[WeWear VTO] Initialization failed:", error);
|
|
@@ -791,152 +410,214 @@
|
|
|
791
410
|
console.log("[WeWear VTO] Button clicked, starting try-on process...");
|
|
792
411
|
try {
|
|
793
412
|
// Get required data
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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;
|
|
798
421
|
console.log("[WeWear VTO] Retrieved data:", {
|
|
799
|
-
ww_user_id,
|
|
800
422
|
ww_product_image,
|
|
801
|
-
ww_access_token,
|
|
802
423
|
});
|
|
803
|
-
// Validate required data
|
|
804
|
-
if (!ww_user_id) {
|
|
805
|
-
console.warn("[WeWear VTO] Missing required cookie: ww_user_id");
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
424
|
if (!ww_product_image) {
|
|
809
425
|
console.warn("[WeWear VTO] Product image not found:", this.config.productImageSelector);
|
|
810
426
|
return;
|
|
811
427
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
// Show camera capture modal
|
|
817
|
-
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());
|
|
818
432
|
}
|
|
819
433
|
catch (error) {
|
|
820
434
|
console.error("[WeWear VTO] Try-on request failed:", error);
|
|
821
435
|
}
|
|
822
436
|
}
|
|
823
437
|
/**
|
|
824
|
-
* Shows
|
|
438
|
+
* Shows a modal with an iframe for the photo upload page
|
|
825
439
|
* @private
|
|
826
440
|
*/
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
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();
|
|
858
483
|
};
|
|
859
|
-
|
|
484
|
+
modal.appendChild(iframe);
|
|
485
|
+
modal.appendChild(closeButton);
|
|
486
|
+
document.body.appendChild(modal);
|
|
860
487
|
}
|
|
861
488
|
/**
|
|
862
|
-
*
|
|
489
|
+
* Sets up listener for messages from the iframe
|
|
863
490
|
* @private
|
|
864
491
|
*/
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
this.
|
|
872
|
-
|
|
873
|
-
ww_access_token,
|
|
874
|
-
ww_user_id,
|
|
875
|
-
ww_product_image,
|
|
876
|
-
};
|
|
877
|
-
// Call the API with the accepted image
|
|
878
|
-
const result = await callVirtualTryOnApi(this.config.baseUrl, ww_access_token, ww_user_id, ww_product_image, imageBlob);
|
|
879
|
-
// Remove modal loading and close modal
|
|
880
|
-
removeModalLoading();
|
|
881
|
-
removeElements(`.${CSS_CLASSES.MODAL}`);
|
|
882
|
-
if (result === null || result === void 0 ? void 0 : result.imageUrl) {
|
|
883
|
-
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;
|
|
884
500
|
}
|
|
885
|
-
|
|
886
|
-
|
|
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;
|
|
887
511
|
}
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
console.error("[WeWear VTO] Error processing accepted image:", error);
|
|
891
|
-
// Remove loading on error
|
|
892
|
-
removeModalLoading();
|
|
893
|
-
}
|
|
512
|
+
};
|
|
513
|
+
window.addEventListener("message", this.iframeMessageListener);
|
|
894
514
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
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.");
|
|
902
521
|
return;
|
|
903
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
|
+
}
|
|
904
532
|
try {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
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
|
+
}
|
|
910
554
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
this.showVirtualTryOnImage(container);
|
|
920
|
-
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;
|
|
921
563
|
}
|
|
922
564
|
}
|
|
923
|
-
|
|
924
|
-
|
|
565
|
+
if (status.status === "FAILED") {
|
|
566
|
+
console.error("[WeWear VTO] VTO process failed:", status.message);
|
|
925
567
|
if (container instanceof HTMLElement) {
|
|
926
|
-
|
|
927
|
-
showAlert(container, result.error.message, result.error.type);
|
|
568
|
+
showAlert(container, status.message, "error");
|
|
928
569
|
}
|
|
929
570
|
}
|
|
930
571
|
}
|
|
931
572
|
catch (error) {
|
|
932
|
-
console.error("[WeWear VTO] Error
|
|
933
|
-
|
|
934
|
-
|
|
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 {
|
|
935
579
|
if (container instanceof HTMLElement) {
|
|
936
580
|
removeProductLoading(container);
|
|
937
581
|
}
|
|
582
|
+
removeElements(`.${CSS_CLASSES.MODAL}`);
|
|
938
583
|
}
|
|
939
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
|
+
}
|
|
940
621
|
/**
|
|
941
622
|
* Replaces the product image in the gallery with the virtual try-on result
|
|
942
623
|
* @private
|
|
@@ -956,8 +637,9 @@
|
|
|
956
637
|
container.setAttribute("data-ww-original-content", container.innerHTML);
|
|
957
638
|
}
|
|
958
639
|
// Capture original image dimensions before replacement to prevent layout shift
|
|
959
|
-
const originalImg = container.querySelector(
|
|
960
|
-
if (originalImg &&
|
|
640
|
+
const originalImg = container.querySelector("img");
|
|
641
|
+
if (originalImg &&
|
|
642
|
+
!container.hasAttribute("data-ww-original-dimensions")) {
|
|
961
643
|
const computedStyle = window.getComputedStyle(originalImg);
|
|
962
644
|
const rect = originalImg.getBoundingClientRect();
|
|
963
645
|
container.setAttribute("data-ww-original-width", computedStyle.width);
|
|
@@ -998,6 +680,7 @@
|
|
|
998
680
|
else {
|
|
999
681
|
this.showVirtualTryOnImage(container);
|
|
1000
682
|
}
|
|
683
|
+
removeProductLoading(container);
|
|
1001
684
|
this.updateButtonContainer(container);
|
|
1002
685
|
}, this.isShowingVirtualTryOn);
|
|
1003
686
|
container.appendChild(buttonContainer);
|
|
@@ -1011,44 +694,46 @@
|
|
|
1011
694
|
console.warn("[WeWear VTO] No virtual try-on image URL available");
|
|
1012
695
|
return;
|
|
1013
696
|
}
|
|
1014
|
-
//
|
|
1015
|
-
|
|
1016
|
-
|
|
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
|
+
});
|
|
1017
703
|
// Get stored original dimensions to prevent layout shift
|
|
1018
|
-
const originalWidth = container.getAttribute("data-ww-original-width") ||
|
|
1019
|
-
const originalHeight = container.getAttribute("data-ww-original-height") ||
|
|
1020
|
-
const originalRectWidth = container.getAttribute("data-ww-original-rect-width") ||
|
|
1021
|
-
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") || "";
|
|
1022
708
|
const image = document.createElement("img");
|
|
1023
709
|
image.src = this.virtualTryOnImageUrl;
|
|
1024
710
|
image.alt = "Virtual Try-On Result";
|
|
1025
711
|
// Use original dimensions to prevent layout shift
|
|
1026
|
-
let widthStyle =
|
|
1027
|
-
let heightStyle =
|
|
712
|
+
let widthStyle = "100%";
|
|
713
|
+
let heightStyle = "100%";
|
|
1028
714
|
// Prefer computed style dimensions, fallback to bounding rect, then container fill
|
|
1029
|
-
if (originalWidth && originalWidth !==
|
|
715
|
+
if (originalWidth && originalWidth !== "auto" && originalWidth !== "0px") {
|
|
1030
716
|
widthStyle = originalWidth;
|
|
1031
717
|
}
|
|
1032
|
-
else if (originalRectWidth && originalRectWidth !==
|
|
718
|
+
else if (originalRectWidth && originalRectWidth !== "0") {
|
|
1033
719
|
widthStyle = `${originalRectWidth}px`;
|
|
1034
720
|
}
|
|
1035
|
-
if (originalHeight &&
|
|
721
|
+
if (originalHeight &&
|
|
722
|
+
originalHeight !== "auto" &&
|
|
723
|
+
originalHeight !== "0px") {
|
|
1036
724
|
heightStyle = originalHeight;
|
|
1037
725
|
}
|
|
1038
|
-
else if (originalRectHeight && originalRectHeight !==
|
|
726
|
+
else if (originalRectHeight && originalRectHeight !== "0") {
|
|
1039
727
|
heightStyle = `${originalRectHeight}px`;
|
|
1040
728
|
}
|
|
1041
|
-
image.style.cssText = `
|
|
1042
|
-
width: ${widthStyle};
|
|
1043
|
-
height: ${heightStyle};
|
|
1044
|
-
object-fit: cover;
|
|
1045
|
-
border-radius: 8px;
|
|
729
|
+
image.style.cssText = `
|
|
730
|
+
width: ${widthStyle};
|
|
731
|
+
height: ${heightStyle};
|
|
732
|
+
object-fit: cover;
|
|
733
|
+
border-radius: 8px;
|
|
1046
734
|
`;
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
existingButtons.forEach((btn) => {
|
|
1050
|
-
container.appendChild(btn);
|
|
1051
|
-
});
|
|
735
|
+
// Prepend the image to ensure buttons are rendered on top
|
|
736
|
+
container.prepend(image);
|
|
1052
737
|
this.isShowingVirtualTryOn = true;
|
|
1053
738
|
}
|
|
1054
739
|
/**
|
|
@@ -1056,16 +741,18 @@
|
|
|
1056
741
|
* @private
|
|
1057
742
|
*/
|
|
1058
743
|
showOriginalImage(container) {
|
|
1059
|
-
const
|
|
1060
|
-
if (
|
|
1061
|
-
//
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
existingButtons.forEach((btn) => {
|
|
1067
|
-
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
|
+
}
|
|
1068
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));
|
|
1069
756
|
}
|
|
1070
757
|
this.isShowingVirtualTryOn = false;
|
|
1071
758
|
}
|
|
@@ -1084,10 +771,20 @@
|
|
|
1084
771
|
this.cookieCheckInterval = null;
|
|
1085
772
|
}
|
|
1086
773
|
this.cameraButton = null;
|
|
774
|
+
// Remove message listener
|
|
775
|
+
if (this.iframeMessageListener) {
|
|
776
|
+
window.removeEventListener("message", this.iframeMessageListener);
|
|
777
|
+
this.iframeMessageListener = null;
|
|
778
|
+
}
|
|
1087
779
|
// Reset state
|
|
1088
780
|
this.virtualTryOnImageUrl = null;
|
|
1089
781
|
this.isShowingVirtualTryOn = false;
|
|
1090
|
-
this.
|
|
782
|
+
this.lastModelImage = null;
|
|
783
|
+
this.originalProductImageUrl = null;
|
|
784
|
+
if (this.previewBadge) {
|
|
785
|
+
this.previewBadge.remove();
|
|
786
|
+
this.previewBadge = null;
|
|
787
|
+
}
|
|
1091
788
|
console.log("[WeWear VTO] Widget destroyed successfully");
|
|
1092
789
|
}
|
|
1093
790
|
catch (error) {
|
|
@@ -1129,18 +826,6 @@
|
|
|
1129
826
|
console.error("[WeWear VTO] Initialization error:", error);
|
|
1130
827
|
}
|
|
1131
828
|
}
|
|
1132
|
-
function destroyVirtualTryOn() {
|
|
1133
|
-
try {
|
|
1134
|
-
if (widgetInstance) {
|
|
1135
|
-
widgetInstance.destroy();
|
|
1136
|
-
widgetInstance = null;
|
|
1137
|
-
console.log("[WeWear VTO] Widget instance destroyed");
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
catch (error) {
|
|
1141
|
-
console.error("[WeWear VTO] Error destroying widget:", error);
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
829
|
function getWidgetInstance() {
|
|
1145
830
|
return widgetInstance;
|
|
1146
831
|
}
|
|
@@ -1149,7 +834,6 @@
|
|
|
1149
834
|
}
|
|
1150
835
|
|
|
1151
836
|
exports.VirtualTryOnWidget = VirtualTryOnWidget;
|
|
1152
|
-
exports.destroyVirtualTryOn = destroyVirtualTryOn;
|
|
1153
837
|
exports.getWidgetInstance = getWidgetInstance;
|
|
1154
838
|
exports.initVirtualTryOn = initVirtualTryOn;
|
|
1155
839
|
|