@wewear/virtual-try-on 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.js +502 -110
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +502 -110
- package/dist/index.js.map +1 -1
- package/dist/widget.d.ts +53 -4
- package/package.json +2 -2
- package/README.md +0 -82
package/dist/index.esm.js
CHANGED
|
@@ -54,37 +54,6 @@ class VirtualTryOnWidget {
|
|
|
54
54
|
* Makes API call to virtual try-on service
|
|
55
55
|
* @param ww_access_token - Optional authentication token
|
|
56
56
|
* @param ww_user_id - User identifier
|
|
57
|
-
* @param ww_product_id - Product identifier
|
|
58
|
-
* @returns Promise with virtual try-on result or null if failed
|
|
59
|
-
*/
|
|
60
|
-
async callVirtualTryOnApi(ww_access_token, ww_user_id, ww_product_id) {
|
|
61
|
-
try {
|
|
62
|
-
const response = await fetch(`${this.config.baseUrl}/api/virtual-try-on`, {
|
|
63
|
-
method: "POST",
|
|
64
|
-
headers: {
|
|
65
|
-
"Content-Type": "application/json",
|
|
66
|
-
Accept: "application/json",
|
|
67
|
-
},
|
|
68
|
-
body: JSON.stringify({
|
|
69
|
-
ww_access_token,
|
|
70
|
-
ww_user_id,
|
|
71
|
-
ww_product_id,
|
|
72
|
-
}),
|
|
73
|
-
});
|
|
74
|
-
if (!response.ok) {
|
|
75
|
-
throw new Error(`API responded with status ${response.status}: ${response.statusText}`);
|
|
76
|
-
}
|
|
77
|
-
const result = await response.json();
|
|
78
|
-
if (!result.imageUrl) {
|
|
79
|
-
throw new Error("API response missing imageUrl");
|
|
80
|
-
}
|
|
81
|
-
return result;
|
|
82
|
-
}
|
|
83
|
-
catch (error) {
|
|
84
|
-
console.error("[WeWear VTO] API call failed:", error);
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
57
|
/**
|
|
89
58
|
* Creates the virtual try-on button element
|
|
90
59
|
* @param onClick - Click handler function
|
|
@@ -168,95 +137,73 @@ class VirtualTryOnWidget {
|
|
|
168
137
|
existingModals.forEach((modal) => {
|
|
169
138
|
modal.remove();
|
|
170
139
|
});
|
|
140
|
+
// Create modal container
|
|
171
141
|
const modal = document.createElement("div");
|
|
172
142
|
modal.className = CSS_CLASSES.MODAL;
|
|
173
|
-
modal.setAttribute("role", "dialog");
|
|
174
|
-
modal.setAttribute("aria-modal", "true");
|
|
175
|
-
modal.setAttribute("aria-label", "Virtual Try-On Result");
|
|
176
143
|
modal.style.cssText = `
|
|
177
144
|
position: fixed;
|
|
178
145
|
top: 0;
|
|
179
146
|
left: 0;
|
|
180
147
|
width: 100%;
|
|
181
148
|
height: 100%;
|
|
182
|
-
background: rgba(0, 0, 0, 0.
|
|
149
|
+
background-color: rgba(0, 0, 0, 0.9);
|
|
183
150
|
display: flex;
|
|
184
151
|
align-items: center;
|
|
185
152
|
justify-content: center;
|
|
186
153
|
z-index: ${Z_INDEX.MODAL};
|
|
187
|
-
|
|
154
|
+
padding: 20px;
|
|
155
|
+
box-sizing: border-box;
|
|
188
156
|
`;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
<img
|
|
200
|
-
src="${imageUrl}"
|
|
201
|
-
alt="Virtual Try-On Result"
|
|
202
|
-
style="
|
|
203
|
-
max-width: 100%;
|
|
204
|
-
max-height: 80vh;
|
|
205
|
-
border-radius: 8px;
|
|
206
|
-
display: block;
|
|
207
|
-
"
|
|
208
|
-
/>
|
|
209
|
-
<div style="
|
|
210
|
-
text-align: right;
|
|
211
|
-
margin-top: 15px;
|
|
212
|
-
">
|
|
213
|
-
<button
|
|
214
|
-
type="button"
|
|
215
|
-
style="
|
|
216
|
-
padding: 8px 16px;
|
|
217
|
-
border: none;
|
|
218
|
-
background: #000000;
|
|
219
|
-
color: #ffffff;
|
|
220
|
-
border-radius: 6px;
|
|
221
|
-
cursor: pointer;
|
|
222
|
-
font-size: 14px;
|
|
223
|
-
font-weight: 500;
|
|
224
|
-
transition: background-color 0.2s ease;
|
|
225
|
-
"
|
|
226
|
-
onmouseover="this.style.backgroundColor='#333333'"
|
|
227
|
-
onmouseout="this.style.backgroundColor='#000000'"
|
|
228
|
-
>
|
|
229
|
-
Close
|
|
230
|
-
</button>
|
|
231
|
-
</div>
|
|
232
|
-
</div>
|
|
233
|
-
<style>
|
|
234
|
-
@keyframes fadeIn {
|
|
235
|
-
from { opacity: 0; }
|
|
236
|
-
to { opacity: 1; }
|
|
237
|
-
}
|
|
238
|
-
@keyframes slideIn {
|
|
239
|
-
from { transform: scale(0.9) translateY(20px); opacity: 0; }
|
|
240
|
-
to { transform: scale(1) translateY(0); opacity: 1; }
|
|
241
|
-
}
|
|
242
|
-
</style>
|
|
157
|
+
// Create image container
|
|
158
|
+
const imageContainer = document.createElement("div");
|
|
159
|
+
imageContainer.style.cssText = `
|
|
160
|
+
position: relative;
|
|
161
|
+
max-width: 90%;
|
|
162
|
+
max-height: 90%;
|
|
163
|
+
background-color: white;
|
|
164
|
+
border-radius: 8px;
|
|
165
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
|
166
|
+
overflow: hidden;
|
|
243
167
|
`;
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
168
|
+
// Create image element
|
|
169
|
+
const image = document.createElement("img");
|
|
170
|
+
image.src = imageUrl;
|
|
171
|
+
image.alt = "Virtual Try-On Result";
|
|
172
|
+
image.style.cssText = `
|
|
173
|
+
width: 100%;
|
|
174
|
+
height: 100%;
|
|
175
|
+
object-fit: contain;
|
|
176
|
+
display: block;
|
|
177
|
+
`;
|
|
178
|
+
// Create close button
|
|
179
|
+
const closeButton = document.createElement("button");
|
|
180
|
+
closeButton.innerHTML = "×";
|
|
181
|
+
closeButton.style.cssText = `
|
|
182
|
+
position: absolute;
|
|
183
|
+
top: 10px;
|
|
184
|
+
right: 10px;
|
|
185
|
+
width: 30px;
|
|
186
|
+
height: 30px;
|
|
187
|
+
border: none;
|
|
188
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
189
|
+
color: white;
|
|
190
|
+
font-size: 20px;
|
|
191
|
+
font-weight: bold;
|
|
192
|
+
cursor: pointer;
|
|
193
|
+
border-radius: 50%;
|
|
194
|
+
display: flex;
|
|
195
|
+
align-items: center;
|
|
196
|
+
justify-content: center;
|
|
197
|
+
`;
|
|
198
|
+
closeButton.onclick = () => {
|
|
199
|
+
modal.remove();
|
|
200
|
+
};
|
|
250
201
|
// Close on backdrop click
|
|
251
202
|
modal.onclick = (e) => {
|
|
252
203
|
if (e.target === modal) {
|
|
253
204
|
modal.remove();
|
|
254
205
|
}
|
|
255
206
|
};
|
|
256
|
-
// Prevent content clicks from closing modal
|
|
257
|
-
if (modalContent) {
|
|
258
|
-
modalContent.onclick = (e) => e.stopPropagation();
|
|
259
|
-
}
|
|
260
207
|
// Close on Escape key
|
|
261
208
|
const handleEscape = (e) => {
|
|
262
209
|
if (e.key === "Escape") {
|
|
@@ -265,9 +212,146 @@ class VirtualTryOnWidget {
|
|
|
265
212
|
}
|
|
266
213
|
};
|
|
267
214
|
document.addEventListener("keydown", handleEscape);
|
|
215
|
+
imageContainer.appendChild(image);
|
|
216
|
+
imageContainer.appendChild(closeButton);
|
|
217
|
+
modal.appendChild(imageContainer);
|
|
268
218
|
document.body.appendChild(modal);
|
|
269
219
|
}
|
|
270
220
|
/**
|
|
221
|
+
* Shows the camera capture modal
|
|
222
|
+
* @param ww_access_token - Access token for API
|
|
223
|
+
* @param ww_user_id - User identifier
|
|
224
|
+
* @param ww_product_id - Product identifier
|
|
225
|
+
*/
|
|
226
|
+
showCameraModal(ww_access_token, ww_user_id, ww_product_id) {
|
|
227
|
+
console.log("[WeWear VTO] Opening camera modal...");
|
|
228
|
+
// Remove any existing modals first
|
|
229
|
+
const existingModals = document.querySelectorAll(`.${CSS_CLASSES.MODAL}`);
|
|
230
|
+
existingModals.forEach((modal) => {
|
|
231
|
+
modal.remove();
|
|
232
|
+
});
|
|
233
|
+
// Create modal container
|
|
234
|
+
const modal = document.createElement("div");
|
|
235
|
+
modal.className = CSS_CLASSES.MODAL;
|
|
236
|
+
modal.style.cssText = `
|
|
237
|
+
position: fixed;
|
|
238
|
+
top: 0;
|
|
239
|
+
left: 0;
|
|
240
|
+
width: 100%;
|
|
241
|
+
height: 100%;
|
|
242
|
+
background-color: rgba(0, 0, 0, 0.95);
|
|
243
|
+
display: flex;
|
|
244
|
+
flex-direction: column;
|
|
245
|
+
align-items: center;
|
|
246
|
+
justify-content: center;
|
|
247
|
+
z-index: ${Z_INDEX.MODAL};
|
|
248
|
+
padding: 20px;
|
|
249
|
+
box-sizing: border-box;
|
|
250
|
+
`;
|
|
251
|
+
// Create camera container
|
|
252
|
+
const cameraContainer = document.createElement("div");
|
|
253
|
+
cameraContainer.style.cssText = `
|
|
254
|
+
position: relative;
|
|
255
|
+
width: 100%;
|
|
256
|
+
max-width: 500px;
|
|
257
|
+
height: 70vh;
|
|
258
|
+
background-color: #000;
|
|
259
|
+
border-radius: 12px;
|
|
260
|
+
overflow: hidden;
|
|
261
|
+
display: flex;
|
|
262
|
+
flex-direction: column;
|
|
263
|
+
align-items: center;
|
|
264
|
+
justify-content: center;
|
|
265
|
+
`;
|
|
266
|
+
// Create video element
|
|
267
|
+
const video = document.createElement("video");
|
|
268
|
+
video.autoplay = true;
|
|
269
|
+
video.playsInline = true;
|
|
270
|
+
video.muted = true;
|
|
271
|
+
video.style.cssText = `
|
|
272
|
+
width: 100%;
|
|
273
|
+
height: 100%;
|
|
274
|
+
object-fit: cover;
|
|
275
|
+
border-radius: 12px;
|
|
276
|
+
`;
|
|
277
|
+
// Create canvas for capturing
|
|
278
|
+
const canvas = document.createElement("canvas");
|
|
279
|
+
canvas.style.display = "none";
|
|
280
|
+
// Create capture button
|
|
281
|
+
const captureButton = document.createElement("button");
|
|
282
|
+
captureButton.innerHTML = `
|
|
283
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="white">
|
|
284
|
+
<circle cx="12" cy="12" r="10" stroke="white" stroke-width="2" fill="none"/>
|
|
285
|
+
<circle cx="12" cy="12" r="6" fill="white"/>
|
|
286
|
+
</svg>
|
|
287
|
+
`;
|
|
288
|
+
captureButton.style.cssText = `
|
|
289
|
+
position: absolute;
|
|
290
|
+
bottom: 20px;
|
|
291
|
+
left: 50%;
|
|
292
|
+
transform: translateX(-50%);
|
|
293
|
+
width: 60px;
|
|
294
|
+
height: 60px;
|
|
295
|
+
border: 3px solid white;
|
|
296
|
+
background-color: transparent;
|
|
297
|
+
border-radius: 50%;
|
|
298
|
+
cursor: pointer;
|
|
299
|
+
display: none;
|
|
300
|
+
align-items: center;
|
|
301
|
+
justify-content: center;
|
|
302
|
+
transition: all 0.2s ease;
|
|
303
|
+
`;
|
|
304
|
+
// Create close button
|
|
305
|
+
const closeButton = document.createElement("button");
|
|
306
|
+
closeButton.innerHTML = "×";
|
|
307
|
+
closeButton.style.cssText = `
|
|
308
|
+
position: absolute;
|
|
309
|
+
top: 15px;
|
|
310
|
+
right: 15px;
|
|
311
|
+
width: 40px;
|
|
312
|
+
height: 40px;
|
|
313
|
+
border: none;
|
|
314
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
315
|
+
color: white;
|
|
316
|
+
font-size: 24px;
|
|
317
|
+
font-weight: bold;
|
|
318
|
+
cursor: pointer;
|
|
319
|
+
border-radius: 50%;
|
|
320
|
+
display: flex;
|
|
321
|
+
align-items: center;
|
|
322
|
+
justify-content: center;
|
|
323
|
+
`;
|
|
324
|
+
// Create loading indicator
|
|
325
|
+
const loadingIndicator = document.createElement("div");
|
|
326
|
+
loadingIndicator.innerHTML = "Initializing camera...";
|
|
327
|
+
loadingIndicator.style.cssText = `
|
|
328
|
+
color: white;
|
|
329
|
+
font-size: 16px;
|
|
330
|
+
position: absolute;
|
|
331
|
+
top: 50%;
|
|
332
|
+
left: 50%;
|
|
333
|
+
transform: translate(-50%, -50%);
|
|
334
|
+
`;
|
|
335
|
+
// Add event listeners
|
|
336
|
+
closeButton.onclick = () => {
|
|
337
|
+
this.stopCamera(video);
|
|
338
|
+
modal.remove();
|
|
339
|
+
};
|
|
340
|
+
captureButton.onclick = () => {
|
|
341
|
+
this.captureImage(video, canvas, ww_access_token, ww_user_id, ww_product_id, modal);
|
|
342
|
+
};
|
|
343
|
+
// Assemble the camera modal
|
|
344
|
+
cameraContainer.appendChild(video);
|
|
345
|
+
cameraContainer.appendChild(canvas);
|
|
346
|
+
cameraContainer.appendChild(captureButton);
|
|
347
|
+
cameraContainer.appendChild(closeButton);
|
|
348
|
+
cameraContainer.appendChild(loadingIndicator);
|
|
349
|
+
modal.appendChild(cameraContainer);
|
|
350
|
+
document.body.appendChild(modal);
|
|
351
|
+
console.log("[WeWear VTO] Camera modal added to DOM");
|
|
352
|
+
// Start camera
|
|
353
|
+
this.startCamera(video, loadingIndicator, captureButton);
|
|
354
|
+
} /**
|
|
271
355
|
* Initializes the virtual try-on widget on the current page
|
|
272
356
|
* @returns Promise that resolves when initialization is complete
|
|
273
357
|
*/
|
|
@@ -304,12 +388,18 @@ class VirtualTryOnWidget {
|
|
|
304
388
|
*/
|
|
305
389
|
async handleTryOnClick() {
|
|
306
390
|
var _a;
|
|
391
|
+
console.log("[WeWear VTO] Button clicked, starting try-on process...");
|
|
307
392
|
try {
|
|
308
393
|
// Get required data
|
|
309
394
|
const ww_access_token = this.getCookie("ww_access_token");
|
|
310
395
|
const ww_user_id = this.getCookie("ww_user_id");
|
|
311
396
|
const skuElement = document.querySelector(this.config.skuSelector);
|
|
312
397
|
const ww_product_id = (_a = skuElement === null || skuElement === void 0 ? void 0 : skuElement.textContent) === null || _a === void 0 ? void 0 : _a.trim();
|
|
398
|
+
console.log("[WeWear VTO] Retrieved data:", {
|
|
399
|
+
ww_user_id,
|
|
400
|
+
ww_product_id,
|
|
401
|
+
ww_access_token: !!ww_access_token,
|
|
402
|
+
});
|
|
313
403
|
// Validate required data
|
|
314
404
|
if (!ww_user_id) {
|
|
315
405
|
console.warn("[WeWear VTO] Missing required cookie: ww_user_id");
|
|
@@ -321,16 +411,8 @@ class VirtualTryOnWidget {
|
|
|
321
411
|
this.showError("Product information not available");
|
|
322
412
|
return;
|
|
323
413
|
}
|
|
324
|
-
// Show
|
|
325
|
-
|
|
326
|
-
// Make API call
|
|
327
|
-
const result = await this.callVirtualTryOnApi(ww_access_token, ww_user_id, ww_product_id);
|
|
328
|
-
if (result === null || result === void 0 ? void 0 : result.imageUrl) {
|
|
329
|
-
this.showImageModal(result.imageUrl);
|
|
330
|
-
}
|
|
331
|
-
else {
|
|
332
|
-
this.showError("Virtual try-on service is currently unavailable. Please try again later.");
|
|
333
|
-
}
|
|
414
|
+
// Show camera capture modal
|
|
415
|
+
this.showCameraModal(ww_access_token, ww_user_id, ww_product_id);
|
|
334
416
|
}
|
|
335
417
|
catch (error) {
|
|
336
418
|
console.error("[WeWear VTO] Try-on request failed:", error);
|
|
@@ -346,6 +428,316 @@ class VirtualTryOnWidget {
|
|
|
346
428
|
// Simple alert for now - could be enhanced with a custom modal
|
|
347
429
|
alert(`WeWear Virtual Try-On: ${message}`);
|
|
348
430
|
}
|
|
431
|
+
/**
|
|
432
|
+
* Starts the camera stream
|
|
433
|
+
* @param video - Video element to display camera stream
|
|
434
|
+
* @param loadingIndicator - Loading indicator element
|
|
435
|
+
* @param captureButton - Capture button element
|
|
436
|
+
*/
|
|
437
|
+
async startCamera(video, loadingIndicator, captureButton) {
|
|
438
|
+
console.log("[WeWear VTO] Starting camera...");
|
|
439
|
+
try {
|
|
440
|
+
const constraints = {
|
|
441
|
+
video: {
|
|
442
|
+
width: { ideal: 1280 },
|
|
443
|
+
height: { ideal: 720 },
|
|
444
|
+
facingMode: "user", // Front-facing camera
|
|
445
|
+
},
|
|
446
|
+
audio: false,
|
|
447
|
+
};
|
|
448
|
+
console.log("[WeWear VTO] Requesting camera access...");
|
|
449
|
+
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
450
|
+
video.srcObject = stream;
|
|
451
|
+
video.onloadedmetadata = () => {
|
|
452
|
+
console.log("[WeWear VTO] Camera stream loaded successfully");
|
|
453
|
+
loadingIndicator.style.display = "none";
|
|
454
|
+
captureButton.style.display = "flex";
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
console.error("[WeWear VTO] Camera access error:", error);
|
|
459
|
+
loadingIndicator.innerHTML =
|
|
460
|
+
"Camera access denied. Please allow camera permissions.";
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Stops the camera stream
|
|
465
|
+
* @param video - Video element with camera stream
|
|
466
|
+
*/
|
|
467
|
+
stopCamera(video) {
|
|
468
|
+
const stream = video.srcObject;
|
|
469
|
+
if (stream) {
|
|
470
|
+
const tracks = stream.getTracks();
|
|
471
|
+
tracks.forEach((track) => {
|
|
472
|
+
track.stop();
|
|
473
|
+
});
|
|
474
|
+
video.srcObject = null;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Captures an image from the video stream
|
|
479
|
+
* @param video - Video element with camera stream
|
|
480
|
+
* @param canvas - Canvas element for capturing
|
|
481
|
+
* @param ww_access_token - Access token for API
|
|
482
|
+
* @param ww_user_id - User identifier
|
|
483
|
+
* @param ww_product_id - Product identifier
|
|
484
|
+
* @param modal - Modal element to close after processing
|
|
485
|
+
*/
|
|
486
|
+
async captureImage(video, canvas, ww_access_token, ww_user_id, ww_product_id, modal) {
|
|
487
|
+
try {
|
|
488
|
+
// Set canvas dimensions to match video
|
|
489
|
+
canvas.width = video.videoWidth;
|
|
490
|
+
canvas.height = video.videoHeight;
|
|
491
|
+
// Draw video frame to canvas
|
|
492
|
+
const ctx = canvas.getContext("2d");
|
|
493
|
+
if (!ctx) {
|
|
494
|
+
throw new Error("Could not get canvas context");
|
|
495
|
+
}
|
|
496
|
+
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
497
|
+
// Convert canvas to blob
|
|
498
|
+
const blob = await new Promise((resolve, reject) => {
|
|
499
|
+
canvas.toBlob((blob) => {
|
|
500
|
+
if (blob) {
|
|
501
|
+
resolve(blob);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
reject(new Error("Failed to create blob from canvas"));
|
|
505
|
+
}
|
|
506
|
+
}, "image/jpeg", 0.8);
|
|
507
|
+
});
|
|
508
|
+
// Stop camera and close camera modal
|
|
509
|
+
this.stopCamera(video);
|
|
510
|
+
modal.remove();
|
|
511
|
+
// Show review modal instead of immediately processing
|
|
512
|
+
this.showReviewModal(blob, ww_access_token, ww_user_id, ww_product_id);
|
|
513
|
+
}
|
|
514
|
+
catch (error) {
|
|
515
|
+
console.error("[WeWear VTO] Image capture error:", error);
|
|
516
|
+
this.showError("Failed to capture image. Please try again.");
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Shows the review modal for captured image
|
|
521
|
+
* @param imageBlob - Captured image blob
|
|
522
|
+
* @param ww_access_token - Access token for API
|
|
523
|
+
* @param ww_user_id - User identifier
|
|
524
|
+
* @param ww_product_id - Product identifier
|
|
525
|
+
*/
|
|
526
|
+
showReviewModal(imageBlob, ww_access_token, ww_user_id, ww_product_id) {
|
|
527
|
+
console.log("[WeWear VTO] Opening review modal...");
|
|
528
|
+
// Remove any existing modals first
|
|
529
|
+
const existingModals = document.querySelectorAll(`.${CSS_CLASSES.MODAL}`);
|
|
530
|
+
existingModals.forEach((modal) => {
|
|
531
|
+
modal.remove();
|
|
532
|
+
});
|
|
533
|
+
// Create image URL for preview
|
|
534
|
+
const imageUrl = URL.createObjectURL(imageBlob);
|
|
535
|
+
// Create modal container (same as camera modal)
|
|
536
|
+
const modal = document.createElement("div");
|
|
537
|
+
modal.className = CSS_CLASSES.MODAL;
|
|
538
|
+
modal.style.cssText = `
|
|
539
|
+
position: fixed;
|
|
540
|
+
top: 0;
|
|
541
|
+
left: 0;
|
|
542
|
+
width: 100%;
|
|
543
|
+
height: 100%;
|
|
544
|
+
background-color: rgba(0, 0, 0, 0.95);
|
|
545
|
+
display: flex;
|
|
546
|
+
flex-direction: column;
|
|
547
|
+
align-items: center;
|
|
548
|
+
justify-content: center;
|
|
549
|
+
z-index: ${Z_INDEX.MODAL};
|
|
550
|
+
padding: 20px;
|
|
551
|
+
box-sizing: border-box;
|
|
552
|
+
`;
|
|
553
|
+
// Create review container (similar to camera container)
|
|
554
|
+
const reviewContainer = document.createElement("div");
|
|
555
|
+
reviewContainer.style.cssText = `
|
|
556
|
+
position: relative;
|
|
557
|
+
width: 100%;
|
|
558
|
+
max-width: 500px;
|
|
559
|
+
height: 70vh;
|
|
560
|
+
background-color: #000;
|
|
561
|
+
border-radius: 12px;
|
|
562
|
+
overflow: hidden;
|
|
563
|
+
display: flex;
|
|
564
|
+
flex-direction: column;
|
|
565
|
+
align-items: center;
|
|
566
|
+
justify-content: center;
|
|
567
|
+
`;
|
|
568
|
+
// Create image element (replaces video element)
|
|
569
|
+
const image = document.createElement("img");
|
|
570
|
+
image.src = imageUrl;
|
|
571
|
+
image.alt = "Review your photo";
|
|
572
|
+
image.style.cssText = `
|
|
573
|
+
width: 100%;
|
|
574
|
+
height: 100%;
|
|
575
|
+
object-fit: cover;
|
|
576
|
+
border-radius: 12px;
|
|
577
|
+
`;
|
|
578
|
+
// Create close button (same as camera modal)
|
|
579
|
+
const closeButton = document.createElement("button");
|
|
580
|
+
closeButton.innerHTML = "×";
|
|
581
|
+
closeButton.style.cssText = `
|
|
582
|
+
position: absolute;
|
|
583
|
+
top: 15px;
|
|
584
|
+
right: 15px;
|
|
585
|
+
width: 40px;
|
|
586
|
+
height: 40px;
|
|
587
|
+
border: none;
|
|
588
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
589
|
+
color: white;
|
|
590
|
+
font-size: 24px;
|
|
591
|
+
font-weight: bold;
|
|
592
|
+
cursor: pointer;
|
|
593
|
+
border-radius: 50%;
|
|
594
|
+
display: flex;
|
|
595
|
+
align-items: center;
|
|
596
|
+
justify-content: center;
|
|
597
|
+
`;
|
|
598
|
+
// Create button container positioned at bottom (similar to capture button position)
|
|
599
|
+
const buttonContainer = document.createElement("div");
|
|
600
|
+
buttonContainer.style.cssText = `
|
|
601
|
+
position: absolute;
|
|
602
|
+
bottom: 20px;
|
|
603
|
+
left: 50%;
|
|
604
|
+
transform: translateX(-50%);
|
|
605
|
+
display: flex;
|
|
606
|
+
gap: 15px;
|
|
607
|
+
width: calc(100% - 40px);
|
|
608
|
+
max-width: 300px;
|
|
609
|
+
`;
|
|
610
|
+
// Create retake button
|
|
611
|
+
const retakeButton = document.createElement("button");
|
|
612
|
+
retakeButton.textContent = "Retake";
|
|
613
|
+
retakeButton.style.cssText = `
|
|
614
|
+
flex: 1;
|
|
615
|
+
padding: 12px 24px;
|
|
616
|
+
background-color: transparent;
|
|
617
|
+
color: white;
|
|
618
|
+
border: 2px solid white;
|
|
619
|
+
border-radius: 8px;
|
|
620
|
+
font-size: 16px;
|
|
621
|
+
font-weight: 600;
|
|
622
|
+
cursor: pointer;
|
|
623
|
+
transition: all 0.2s ease;
|
|
624
|
+
`;
|
|
625
|
+
// Create use photo button
|
|
626
|
+
const usePhotoButton = document.createElement("button");
|
|
627
|
+
usePhotoButton.textContent = "Use Photo";
|
|
628
|
+
usePhotoButton.style.cssText = `
|
|
629
|
+
flex: 1;
|
|
630
|
+
padding: 12px 24px;
|
|
631
|
+
background-color: white;
|
|
632
|
+
color: black;
|
|
633
|
+
border: 2px solid white;
|
|
634
|
+
border-radius: 8px;
|
|
635
|
+
font-size: 16px;
|
|
636
|
+
font-weight: 600;
|
|
637
|
+
cursor: pointer;
|
|
638
|
+
transition: all 0.2s ease;
|
|
639
|
+
`;
|
|
640
|
+
// Add hover effects
|
|
641
|
+
retakeButton.addEventListener("mouseenter", () => {
|
|
642
|
+
retakeButton.style.backgroundColor = "white";
|
|
643
|
+
retakeButton.style.color = "black";
|
|
644
|
+
});
|
|
645
|
+
retakeButton.addEventListener("mouseleave", () => {
|
|
646
|
+
retakeButton.style.backgroundColor = "transparent";
|
|
647
|
+
retakeButton.style.color = "white";
|
|
648
|
+
});
|
|
649
|
+
usePhotoButton.addEventListener("mouseenter", () => {
|
|
650
|
+
usePhotoButton.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
|
|
651
|
+
});
|
|
652
|
+
usePhotoButton.addEventListener("mouseleave", () => {
|
|
653
|
+
usePhotoButton.style.backgroundColor = "white";
|
|
654
|
+
});
|
|
655
|
+
// Add event listeners
|
|
656
|
+
closeButton.onclick = () => {
|
|
657
|
+
URL.revokeObjectURL(imageUrl); // Clean up
|
|
658
|
+
modal.remove();
|
|
659
|
+
};
|
|
660
|
+
retakeButton.onclick = () => {
|
|
661
|
+
URL.revokeObjectURL(imageUrl); // Clean up
|
|
662
|
+
modal.remove();
|
|
663
|
+
// Reopen camera modal
|
|
664
|
+
this.showCameraModal(ww_access_token, ww_user_id, ww_product_id);
|
|
665
|
+
};
|
|
666
|
+
usePhotoButton.onclick = async () => {
|
|
667
|
+
URL.revokeObjectURL(imageUrl); // Clean up
|
|
668
|
+
modal.remove();
|
|
669
|
+
// Process the image
|
|
670
|
+
await this.processAcceptedImage(imageBlob, ww_access_token, ww_user_id, ww_product_id);
|
|
671
|
+
};
|
|
672
|
+
// Assemble the review modal (similar to camera modal)
|
|
673
|
+
buttonContainer.appendChild(retakeButton);
|
|
674
|
+
buttonContainer.appendChild(usePhotoButton);
|
|
675
|
+
reviewContainer.appendChild(image);
|
|
676
|
+
reviewContainer.appendChild(closeButton);
|
|
677
|
+
reviewContainer.appendChild(buttonContainer);
|
|
678
|
+
modal.appendChild(reviewContainer);
|
|
679
|
+
document.body.appendChild(modal);
|
|
680
|
+
console.log("[WeWear VTO] Review modal added to DOM");
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Processes the accepted image by calling the API
|
|
684
|
+
* @param imageBlob - Accepted image blob
|
|
685
|
+
* @param ww_access_token - Access token for API
|
|
686
|
+
* @param ww_user_id - User identifier
|
|
687
|
+
* @param ww_product_id - Product identifier
|
|
688
|
+
*/
|
|
689
|
+
async processAcceptedImage(imageBlob, ww_access_token, ww_user_id, ww_product_id) {
|
|
690
|
+
try {
|
|
691
|
+
console.log("[WeWear VTO] Processing accepted image...");
|
|
692
|
+
// Call the API with the accepted image
|
|
693
|
+
const result = await this.callVirtualTryOnApiWithImage(ww_access_token, ww_user_id, ww_product_id, imageBlob);
|
|
694
|
+
if (result === null || result === void 0 ? void 0 : result.imageUrl) {
|
|
695
|
+
this.showImageModal(result.imageUrl);
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
console.error("[WeWear VTO] Invalid API response:", result);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
catch (error) {
|
|
702
|
+
console.error("[WeWear VTO] Error processing accepted image:", error);
|
|
703
|
+
this.showError("Failed to process image. Please try again.");
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Makes API call to virtual try-on service with captured image
|
|
708
|
+
* @param ww_access_token - Optional authentication token
|
|
709
|
+
* @param ww_user_id - User identifier
|
|
710
|
+
* @param ww_product_id - Product identifier
|
|
711
|
+
* @param imageBlob - Captured image blob
|
|
712
|
+
* @returns Promise with virtual try-on result or null if failed
|
|
713
|
+
*/
|
|
714
|
+
async callVirtualTryOnApiWithImage(ww_access_token, ww_user_id, ww_product_id, imageBlob) {
|
|
715
|
+
try {
|
|
716
|
+
// Create form data for multipart upload
|
|
717
|
+
const formData = new FormData();
|
|
718
|
+
formData.append("ww_user_id", ww_user_id);
|
|
719
|
+
formData.append("ww_product_id", ww_product_id);
|
|
720
|
+
formData.append("image", imageBlob, "captured-image.jpg");
|
|
721
|
+
if (ww_access_token) {
|
|
722
|
+
formData.append("ww_access_token", ww_access_token);
|
|
723
|
+
}
|
|
724
|
+
const response = await fetch(`${this.config.baseUrl}/api/virtual-try-on`, {
|
|
725
|
+
method: "POST",
|
|
726
|
+
body: formData,
|
|
727
|
+
});
|
|
728
|
+
if (!response.ok) {
|
|
729
|
+
console.error("[WeWear VTO] API request failed:", response.status, response.statusText);
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
const result = await response.json();
|
|
733
|
+
console.log("[WeWear VTO] API response:", result);
|
|
734
|
+
return result;
|
|
735
|
+
}
|
|
736
|
+
catch (error) {
|
|
737
|
+
console.error("[WeWear VTO] API call failed:", error);
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
349
741
|
/**
|
|
350
742
|
* Destroys the widget and cleans up resources
|
|
351
743
|
*/
|