@neurodyn/ai-try-on 0.0.4 → 0.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/ai-try-on.embed.min.js +34 -15
- package/dist/ai-try-on.global.min.js +34 -15
- package/dist/shopify.d.ts +20 -0
- package/dist/shopify.embed.min.js +5509 -0
- package/dist/shopify.embed.min.js.LICENSE.txt +88 -0
- package/dist/shopify.js +230 -0
- package/package.json +8 -3
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/*! For license information please see index.js.LICENSE.txt */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @license React
|
|
5
|
+
* react-dom-client.production.js
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
8
|
+
*
|
|
9
|
+
* This source code is licensed under the MIT license found in the
|
|
10
|
+
* LICENSE file in the root directory of this source tree.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @license React
|
|
15
|
+
* react-dom-server-legacy.browser.production.js
|
|
16
|
+
*
|
|
17
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
18
|
+
*
|
|
19
|
+
* This source code is licensed under the MIT license found in the
|
|
20
|
+
* LICENSE file in the root directory of this source tree.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @license React
|
|
25
|
+
* react-dom-server.browser.production.js
|
|
26
|
+
*
|
|
27
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
28
|
+
*
|
|
29
|
+
* This source code is licensed under the MIT license found in the
|
|
30
|
+
* LICENSE file in the root directory of this source tree.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @license React
|
|
35
|
+
* react-dom.production.js
|
|
36
|
+
*
|
|
37
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
38
|
+
*
|
|
39
|
+
* This source code is licensed under the MIT license found in the
|
|
40
|
+
* LICENSE file in the root directory of this source tree.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @license React
|
|
45
|
+
* react-jsx-runtime.production.js
|
|
46
|
+
*
|
|
47
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
48
|
+
*
|
|
49
|
+
* This source code is licensed under the MIT license found in the
|
|
50
|
+
* LICENSE file in the root directory of this source tree.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @license React
|
|
55
|
+
* react.production.js
|
|
56
|
+
*
|
|
57
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
58
|
+
*
|
|
59
|
+
* This source code is licensed under the MIT license found in the
|
|
60
|
+
* LICENSE file in the root directory of this source tree.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @license React
|
|
65
|
+
* scheduler.production.js
|
|
66
|
+
*
|
|
67
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
68
|
+
*
|
|
69
|
+
* This source code is licensed under the MIT license found in the
|
|
70
|
+
* LICENSE file in the root directory of this source tree.
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @license React
|
|
75
|
+
* use-sync-external-store-shim.production.js
|
|
76
|
+
*
|
|
77
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
78
|
+
*
|
|
79
|
+
* This source code is licensed under the MIT license found in the
|
|
80
|
+
* LICENSE file in the root directory of this source tree.
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @license lucide-react v0.553.0 - ISC
|
|
85
|
+
*
|
|
86
|
+
* This source code is licensed under the ISC license.
|
|
87
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
88
|
+
*/
|
package/dist/shopify.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { AiTryOnDialog, AiTryOnGenerationError, createAppProxyOperations, createShopifyTryOnClient, uploadImageToPresignedUrl } from "@neurodyn/react-ai-try-on";
|
|
2
|
+
import { createElement } from "react";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
const CART_ADD_PATH = '/cart/add.js';
|
|
5
|
+
class ShopifyCartError extends Error {
|
|
6
|
+
constructor(message){
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'ShopifyCartError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
async function addVariantToCart(variantId, { signal, win = window } = {}) {
|
|
12
|
+
const response = await win.fetch(CART_ADD_PATH, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
Accept: 'application/json'
|
|
17
|
+
},
|
|
18
|
+
body: JSON.stringify({
|
|
19
|
+
items: [
|
|
20
|
+
{
|
|
21
|
+
id: variantId,
|
|
22
|
+
quantity: 1
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}),
|
|
26
|
+
signal
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok) throw new ShopifyCartError(await readCartErrorMessage(response));
|
|
29
|
+
notifyThemeCartChanged(win);
|
|
30
|
+
}
|
|
31
|
+
async function readCartErrorMessage(response) {
|
|
32
|
+
try {
|
|
33
|
+
const body = await response.json();
|
|
34
|
+
const description = 'string' == typeof body.description ? body.description.trim() : '';
|
|
35
|
+
const message = 'string' == typeof body.message ? body.message.trim() : '';
|
|
36
|
+
return description || message || '';
|
|
37
|
+
} catch {
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function notifyThemeCartChanged(win) {
|
|
42
|
+
for (const type of [
|
|
43
|
+
'cart:refresh',
|
|
44
|
+
'cart:update'
|
|
45
|
+
])win.document.dispatchEvent(new CustomEvent(type, {
|
|
46
|
+
bubbles: true,
|
|
47
|
+
detail: {
|
|
48
|
+
source: 'vizbl-try-on'
|
|
49
|
+
}
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
function readCurrentVariantId(trigger, doc = document) {
|
|
53
|
+
const search = doc.defaultView?.location?.search ?? '';
|
|
54
|
+
const fromUrl = new URLSearchParams(search).get('variant')?.trim();
|
|
55
|
+
if (fromUrl) return fromUrl;
|
|
56
|
+
const field = doc.querySelector('form[action*="/cart/add"] [name="id"]:checked, form[action*="/cart/add"] select[name="id"], form[action*="/cart/add"] input[name="id"]');
|
|
57
|
+
const fromForm = field?.value?.trim();
|
|
58
|
+
if (fromForm) return fromForm;
|
|
59
|
+
return trigger.getAttribute('data-variant-id')?.trim() || void 0;
|
|
60
|
+
}
|
|
61
|
+
function readVariantPrice(trigger, variantId) {
|
|
62
|
+
const fallback = trigger.getAttribute('data-product-price')?.trim() || void 0;
|
|
63
|
+
if (!variantId) return fallback;
|
|
64
|
+
const payload = trigger.closest('[data-vizbl-try-on-block]')?.querySelector('[data-vizbl-try-on-variants]')?.textContent;
|
|
65
|
+
if (!payload) return fallback;
|
|
66
|
+
try {
|
|
67
|
+
const prices = JSON.parse(payload);
|
|
68
|
+
const price = prices[variantId]?.price;
|
|
69
|
+
return 'string' == typeof price && price.trim() || fallback;
|
|
70
|
+
} catch {
|
|
71
|
+
return fallback;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const mounted = new WeakMap();
|
|
75
|
+
const PROXY_BASE_PATH = "/apps/vizbl-ai-try-on";
|
|
76
|
+
let proxyOperations;
|
|
77
|
+
function builtInOperations() {
|
|
78
|
+
proxyOperations ??= createAppProxyOperations(PROXY_BASE_PATH);
|
|
79
|
+
return proxyOperations;
|
|
80
|
+
}
|
|
81
|
+
function revokeObjectUrl(src) {
|
|
82
|
+
if (src?.startsWith('blob:') && 'function' == typeof URL.revokeObjectURL) URL.revokeObjectURL(src);
|
|
83
|
+
}
|
|
84
|
+
const selector = '[data-vizbl-try-on]';
|
|
85
|
+
function mountShopifyTryOn(doc = document, options = {}) {
|
|
86
|
+
const existing = mounted.get(doc);
|
|
87
|
+
if (existing) {
|
|
88
|
+
existing.configure(options);
|
|
89
|
+
return existing;
|
|
90
|
+
}
|
|
91
|
+
let configuration = {
|
|
92
|
+
...options
|
|
93
|
+
};
|
|
94
|
+
const container = doc.createElement('div');
|
|
95
|
+
container.dataset.vizblTryOnDialog = '';
|
|
96
|
+
doc.body.append(container);
|
|
97
|
+
const root = createRoot(container);
|
|
98
|
+
let active = null;
|
|
99
|
+
let abort = null;
|
|
100
|
+
let destroyed = false;
|
|
101
|
+
let dialogId = 0;
|
|
102
|
+
let observer;
|
|
103
|
+
const close = ()=>{
|
|
104
|
+
observer.disconnect();
|
|
105
|
+
abort?.abort();
|
|
106
|
+
abort = null;
|
|
107
|
+
active = null;
|
|
108
|
+
root.render(null);
|
|
109
|
+
};
|
|
110
|
+
const click = (event)=>{
|
|
111
|
+
const trigger = event.target instanceof Element ? event.target.closest(selector) : null;
|
|
112
|
+
if (!trigger || 'true' === trigger.getAttribute('aria-disabled') || trigger instanceof HTMLButtonElement && trigger.disabled) return;
|
|
113
|
+
const productId = trigger.dataset.productId;
|
|
114
|
+
if (!productId) return;
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
if (active === trigger) return;
|
|
117
|
+
close();
|
|
118
|
+
active = trigger;
|
|
119
|
+
observer.observe(doc.body, {
|
|
120
|
+
childList: true,
|
|
121
|
+
subtree: true
|
|
122
|
+
});
|
|
123
|
+
const lifecycle = new AbortController();
|
|
124
|
+
abort = lifecycle;
|
|
125
|
+
const declared = trigger.dataset.language?.toLowerCase().split(/[-_]/)[0];
|
|
126
|
+
const language = void 0 === declared ? void 0 : 'ru' === declared ? 'ru' : 'en';
|
|
127
|
+
const resolve = ()=>configuration.operations ?? builtInOperations();
|
|
128
|
+
const require = ()=>{
|
|
129
|
+
const current = resolve();
|
|
130
|
+
if (!current) throw new AiTryOnGenerationError('service');
|
|
131
|
+
return current;
|
|
132
|
+
};
|
|
133
|
+
const operations = {
|
|
134
|
+
activate: (target, config)=>require().activate(target, config),
|
|
135
|
+
requestUploadUrls: (count, config)=>require().requestUploadUrls(count, config),
|
|
136
|
+
generate: (data, config)=>require().generate(data, config),
|
|
137
|
+
issueWidgetToken: (data, config)=>require().issueWidgetToken(data, config),
|
|
138
|
+
uploadPhoto: (url, photo, config)=>(resolve()?.uploadPhoto ?? uploadImageToPresignedUrl)(url, photo, config),
|
|
139
|
+
sendWidgetEvent: (data, config)=>resolve()?.sendWidgetEvent?.(data, config) ?? Promise.resolve()
|
|
140
|
+
};
|
|
141
|
+
const client = createShopifyTryOnClient({
|
|
142
|
+
operations,
|
|
143
|
+
signal: lifecycle.signal
|
|
144
|
+
});
|
|
145
|
+
const target = {
|
|
146
|
+
productId,
|
|
147
|
+
variantId: readCurrentVariantId(trigger, doc)
|
|
148
|
+
};
|
|
149
|
+
const processPhoto = client.createProcessPhoto(target);
|
|
150
|
+
let pending = false;
|
|
151
|
+
const externalActivation = async ()=>{
|
|
152
|
+
if (!resolve()) return {
|
|
153
|
+
enabled: false
|
|
154
|
+
};
|
|
155
|
+
const { available, photoUrl, reason, zoneType } = await client.activate(target);
|
|
156
|
+
return {
|
|
157
|
+
enabled: available,
|
|
158
|
+
productImageSrc: photoUrl,
|
|
159
|
+
productType: zoneType,
|
|
160
|
+
temporarilyUnavailable: 'tokens_exhausted' === reason
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
const externalGeneration = async (photo, context)=>{
|
|
164
|
+
if (pending || lifecycle.signal.aborted) throw new AiTryOnGenerationError('service');
|
|
165
|
+
pending = true;
|
|
166
|
+
try {
|
|
167
|
+
const result = await processPhoto(photo, context);
|
|
168
|
+
if (lifecycle.signal.aborted) {
|
|
169
|
+
revokeObjectUrl(result.after.src);
|
|
170
|
+
throw new DOMException('Generation was aborted', 'AbortError');
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (error instanceof AiTryOnGenerationError || lifecycle.signal.aborted) throw error;
|
|
175
|
+
throw new AiTryOnGenerationError('service');
|
|
176
|
+
} finally{
|
|
177
|
+
pending = false;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
const addToCart = target.variantId ? ()=>addVariantToCart(target.variantId, {
|
|
181
|
+
signal: lifecycle.signal
|
|
182
|
+
}) : void 0;
|
|
183
|
+
root.render(createElement(AiTryOnDialog, {
|
|
184
|
+
key: ++dialogId,
|
|
185
|
+
open: true,
|
|
186
|
+
language,
|
|
187
|
+
productName: trigger.dataset.productName ?? '',
|
|
188
|
+
productImageSrc: trigger.dataset.productImage ?? '',
|
|
189
|
+
productPrice: readVariantPrice(trigger, target.variantId),
|
|
190
|
+
productUrl: trigger.dataset.productUrl,
|
|
191
|
+
externalActivation,
|
|
192
|
+
externalAddToCart: addToCart,
|
|
193
|
+
externalGeneration,
|
|
194
|
+
externalClient: client,
|
|
195
|
+
onOpenChange: (open)=>!open && close()
|
|
196
|
+
}));
|
|
197
|
+
};
|
|
198
|
+
const unload = (event)=>{
|
|
199
|
+
if (event.target instanceof Element && active && event.target.contains(active)) close();
|
|
200
|
+
};
|
|
201
|
+
doc.addEventListener('click', click);
|
|
202
|
+
doc.addEventListener('shopify:section:unload', unload);
|
|
203
|
+
observer = new MutationObserver(()=>{
|
|
204
|
+
if (active && !active.isConnected) close();
|
|
205
|
+
});
|
|
206
|
+
const controller = {
|
|
207
|
+
configure (next) {
|
|
208
|
+
if (!destroyed) configuration = {
|
|
209
|
+
...configuration,
|
|
210
|
+
...next
|
|
211
|
+
};
|
|
212
|
+
},
|
|
213
|
+
destroy () {
|
|
214
|
+
if (destroyed) return;
|
|
215
|
+
destroyed = true;
|
|
216
|
+
abort?.abort();
|
|
217
|
+
doc.removeEventListener('click', click);
|
|
218
|
+
doc.removeEventListener('shopify:section:unload', unload);
|
|
219
|
+
observer.disconnect();
|
|
220
|
+
mounted.delete(doc);
|
|
221
|
+
queueMicrotask(()=>{
|
|
222
|
+
root.unmount();
|
|
223
|
+
container.remove();
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
mounted.set(doc, controller);
|
|
228
|
+
return controller;
|
|
229
|
+
}
|
|
230
|
+
export { AiTryOnGenerationError as ShopifyGenerationError, mountShopifyTryOn };
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neurodyn/ai-try-on",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0
|
|
4
|
+
"version": "0.1.0",
|
|
5
5
|
"exports": {
|
|
6
|
+
"./shopify": {
|
|
7
|
+
"types": "./dist/shopify.d.ts",
|
|
8
|
+
"import": "./dist/shopify.js"
|
|
9
|
+
},
|
|
6
10
|
".": {
|
|
7
11
|
"types": "./dist/index.d.ts",
|
|
8
12
|
"import": "./dist/ai-try-on.js",
|
|
@@ -18,7 +22,7 @@
|
|
|
18
22
|
"dependencies": {
|
|
19
23
|
"react": "^19.2.0",
|
|
20
24
|
"react-dom": "^19.2.0",
|
|
21
|
-
"@neurodyn/react-ai-try-on": "0.0
|
|
25
|
+
"@neurodyn/react-ai-try-on": "0.1.0"
|
|
22
26
|
},
|
|
23
27
|
"devDependencies": {
|
|
24
28
|
"@rsbuild/core": "1.7.1",
|
|
@@ -37,8 +41,9 @@
|
|
|
37
41
|
},
|
|
38
42
|
"scripts": {
|
|
39
43
|
"dev": "rslib build --watch",
|
|
40
|
-
"build": "rslib build",
|
|
44
|
+
"build": "rslib build && node scripts/check-shopify-bundle.mjs",
|
|
41
45
|
"test": "vitest run",
|
|
46
|
+
"typecheck": "tsc --noEmit",
|
|
42
47
|
"lint": "eslint .",
|
|
43
48
|
"lint:fix": "eslint . --fix",
|
|
44
49
|
"rsdoctor": "RSDOCTOR=true rslib build"
|