styllar-react-plugin 1.0.0 → 1.0.2
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/styllar-react-plugin.js +430 -351
- package/dist/styllar-react-plugin.umd.cjs +10 -10
- package/package.json +2 -2
- package/src/StyllarPlugin.jsx +152 -44
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(W,_){typeof exports=="object"&&typeof module<"u"?_(exports,require("react")):typeof define=="function"&&define.amd?define(["exports","react"],_):(W=typeof globalThis<"u"?globalThis:W||self,_(W.StyllarReactPlugin={},W.React))})(this,function(W,_){"use strict";var re={exports:{}},J={};/**
|
|
2
2
|
* @license React
|
|
3
3
|
* react-jsx-runtime.production.min.js
|
|
4
4
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* This source code is licensed under the MIT license found in the
|
|
8
8
|
* LICENSE file in the root directory of this source tree.
|
|
9
|
-
*/var
|
|
9
|
+
*/var me;function Me(){if(me)return J;me=1;var j=_,E=Symbol.for("react.element"),f=Symbol.for("react.fragment"),g=Object.prototype.hasOwnProperty,S=j.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,N={key:!0,ref:!0,__self:!0,__source:!0};function I(D,y,A){var h,C={},k=null,z=null;A!==void 0&&(k=""+A),y.key!==void 0&&(k=""+y.key),y.ref!==void 0&&(z=y.ref);for(h in y)g.call(y,h)&&!N.hasOwnProperty(h)&&(C[h]=y[h]);if(D&&D.defaultProps)for(h in y=D.defaultProps,y)C[h]===void 0&&(C[h]=y[h]);return{$$typeof:E,type:D,key:k,ref:z,props:C,_owner:S.current}}return J.Fragment=f,J.jsx=I,J.jsxs=I,J}var K={};/**
|
|
10
10
|
* @license React
|
|
11
11
|
* react-jsx-runtime.development.js
|
|
12
12
|
*
|
|
@@ -14,17 +14,17 @@
|
|
|
14
14
|
*
|
|
15
15
|
* This source code is licensed under the MIT license found in the
|
|
16
16
|
* LICENSE file in the root directory of this source tree.
|
|
17
|
-
*/var
|
|
18
|
-
`+
|
|
19
|
-
`),
|
|
20
|
-
`),
|
|
21
|
-
`+
|
|
17
|
+
*/var ge;function Ue(){return ge||(ge=1,process.env.NODE_ENV!=="production"&&function(){var j=_,E=Symbol.for("react.element"),f=Symbol.for("react.portal"),g=Symbol.for("react.fragment"),S=Symbol.for("react.strict_mode"),N=Symbol.for("react.profiler"),I=Symbol.for("react.provider"),D=Symbol.for("react.context"),y=Symbol.for("react.forward_ref"),A=Symbol.for("react.suspense"),h=Symbol.for("react.suspense_list"),C=Symbol.for("react.memo"),k=Symbol.for("react.lazy"),z=Symbol.for("react.offscreen"),Y=Symbol.iterator,X="@@iterator";function G(e){if(e===null||typeof e!="object")return null;var r=Y&&e[Y]||e[X];return typeof r=="function"?r:null}var F=j.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;function x(e){{for(var r=arguments.length,t=new Array(r>1?r-1:0),n=1;n<r;n++)t[n-1]=arguments[n];ne("error",e,t)}}function ne(e,r,t){{var n=F.ReactDebugCurrentFrame,l=n.getStackAddendum();l!==""&&(r+="%s",t=t.concat([l]));var c=t.map(function(s){return String(s)});c.unshift("Warning: "+r),Function.prototype.apply.call(console[e],console,c)}}var ae=!1,oe=!1,q=!1,se=!1,i=!1,u;u=Symbol.for("react.module.reference");function p(e){return!!(typeof e=="string"||typeof e=="function"||e===g||e===N||i||e===S||e===A||e===h||se||e===z||ae||oe||q||typeof e=="object"&&e!==null&&(e.$$typeof===k||e.$$typeof===C||e.$$typeof===I||e.$$typeof===D||e.$$typeof===y||e.$$typeof===u||e.getModuleId!==void 0))}function T(e,r,t){var n=e.displayName;if(n)return n;var l=r.displayName||r.name||"";return l!==""?t+"("+l+")":t}function w(e){return e.displayName||"Context"}function v(e){if(e==null)return null;if(typeof e.tag=="number"&&x("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case g:return"Fragment";case f:return"Portal";case N:return"Profiler";case S:return"StrictMode";case A:return"Suspense";case h:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case D:var r=e;return w(r)+".Consumer";case I:var t=e;return w(t._context)+".Provider";case y:return T(e,e.render,"ForwardRef");case C:var n=e.displayName||null;return n!==null?n:v(e.type)||"Memo";case k:{var l=e,c=l._payload,s=l._init;try{return v(s(c))}catch{return null}}}return null}var b=Object.assign,L=0,he,be,ye,xe,_e,Ee,Se;function we(){}we.__reactDisabledLog=!0;function Xe(){{if(L===0){he=console.log,be=console.info,ye=console.warn,xe=console.error,_e=console.group,Ee=console.groupCollapsed,Se=console.groupEnd;var e={configurable:!0,enumerable:!0,value:we,writable:!0};Object.defineProperties(console,{info:e,log:e,warn:e,error:e,group:e,groupCollapsed:e,groupEnd:e})}L++}}function Ge(){{if(L--,L===0){var e={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:b({},e,{value:he}),info:b({},e,{value:be}),warn:b({},e,{value:ye}),error:b({},e,{value:xe}),group:b({},e,{value:_e}),groupCollapsed:b({},e,{value:Ee}),groupEnd:b({},e,{value:Se})})}L<0&&x("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var ie=F.ReactCurrentDispatcher,le;function $(e,r,t){{if(le===void 0)try{throw Error()}catch(l){var n=l.stack.trim().match(/\n( *(at )?)/);le=n&&n[1]||""}return`
|
|
18
|
+
`+le+e}}var ce=!1,Z;{var He=typeof WeakMap=="function"?WeakMap:Map;Z=new He}function Re(e,r){if(!e||ce)return"";{var t=Z.get(e);if(t!==void 0)return t}var n;ce=!0;var l=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var c;c=ie.current,ie.current=null,Xe();try{if(r){var s=function(){throw Error()};if(Object.defineProperty(s.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(s,[])}catch(P){n=P}Reflect.construct(e,[],s)}else{try{s.call()}catch(P){n=P}e.call(s.prototype)}}else{try{throw Error()}catch(P){n=P}e()}}catch(P){if(P&&n&&typeof P.stack=="string"){for(var o=P.stack.split(`
|
|
19
|
+
`),R=n.stack.split(`
|
|
20
|
+
`),d=o.length-1,m=R.length-1;d>=1&&m>=0&&o[d]!==R[m];)m--;for(;d>=1&&m>=0;d--,m--)if(o[d]!==R[m]){if(d!==1||m!==1)do if(d--,m--,m<0||o[d]!==R[m]){var O=`
|
|
21
|
+
`+o[d].replace(" at new "," at ");return e.displayName&&O.includes("<anonymous>")&&(O=O.replace("<anonymous>",e.displayName)),typeof e=="function"&&Z.set(e,O),O}while(d>=1&&m>=0);break}}}finally{ce=!1,ie.current=c,Ge(),Error.prepareStackTrace=l}var V=e?e.displayName||e.name:"",U=V?$(V):"";return typeof e=="function"&&Z.set(e,U),U}function qe(e,r,t){return Re(e,!1)}function $e(e){var r=e.prototype;return!!(r&&r.isReactComponent)}function Q(e,r,t){if(e==null)return"";if(typeof e=="function")return Re(e,$e(e));if(typeof e=="string")return $(e);switch(e){case A:return $("Suspense");case h:return $("SuspenseList")}if(typeof e=="object")switch(e.$$typeof){case y:return qe(e.render);case C:return Q(e.type,r,t);case k:{var n=e,l=n._payload,c=n._init;try{return Q(c(l),r,t)}catch{}}}return""}var H=Object.prototype.hasOwnProperty,je={},Te=F.ReactDebugCurrentFrame;function ee(e){if(e){var r=e._owner,t=Q(e.type,e._source,r?r.type:null);Te.setExtraStackFrame(t)}else Te.setExtraStackFrame(null)}function Ze(e,r,t,n,l){{var c=Function.call.bind(H);for(var s in e)if(c(e,s)){var o=void 0;try{if(typeof e[s]!="function"){var R=Error((n||"React class")+": "+t+" type `"+s+"` is invalid; it must be a function, usually from the `prop-types` package, but received `"+typeof e[s]+"`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");throw R.name="Invariant Violation",R}o=e[s](r,s,n,t,null,"SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED")}catch(d){o=d}o&&!(o instanceof Error)&&(ee(l),x("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).",n||"React class",t,s,typeof o),ee(null)),o instanceof Error&&!(o.message in je)&&(je[o.message]=!0,ee(l),x("Failed %s type: %s",t,o.message),ee(null))}}}var Qe=Array.isArray;function ue(e){return Qe(e)}function er(e){{var r=typeof Symbol=="function"&&Symbol.toStringTag,t=r&&e[Symbol.toStringTag]||e.constructor.name||"Object";return t}}function rr(e){try{return Pe(e),!1}catch{return!0}}function Pe(e){return""+e}function Ce(e){if(rr(e))return x("The provided key is an unsupported type %s. This value must be coerced to a string before before using it here.",er(e)),Pe(e)}var ke=F.ReactCurrentOwner,tr={key:!0,ref:!0,__self:!0,__source:!0},Oe,De;function nr(e){if(H.call(e,"ref")){var r=Object.getOwnPropertyDescriptor(e,"ref").get;if(r&&r.isReactWarning)return!1}return e.ref!==void 0}function ar(e){if(H.call(e,"key")){var r=Object.getOwnPropertyDescriptor(e,"key").get;if(r&&r.isReactWarning)return!1}return e.key!==void 0}function or(e,r){typeof e.ref=="string"&&ke.current}function sr(e,r){{var t=function(){Oe||(Oe=!0,x("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)",r))};t.isReactWarning=!0,Object.defineProperty(e,"key",{get:t,configurable:!0})}}function ir(e,r){{var t=function(){De||(De=!0,x("%s: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)",r))};t.isReactWarning=!0,Object.defineProperty(e,"ref",{get:t,configurable:!0})}}var lr=function(e,r,t,n,l,c,s){var o={$$typeof:E,type:e,key:r,ref:t,props:s,_owner:c};return o._store={},Object.defineProperty(o._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:!1}),Object.defineProperty(o,"_self",{configurable:!1,enumerable:!1,writable:!1,value:n}),Object.defineProperty(o,"_source",{configurable:!1,enumerable:!1,writable:!1,value:l}),Object.freeze&&(Object.freeze(o.props),Object.freeze(o)),o};function cr(e,r,t,n,l){{var c,s={},o=null,R=null;t!==void 0&&(Ce(t),o=""+t),ar(r)&&(Ce(r.key),o=""+r.key),nr(r)&&(R=r.ref,or(r,l));for(c in r)H.call(r,c)&&!tr.hasOwnProperty(c)&&(s[c]=r[c]);if(e&&e.defaultProps){var d=e.defaultProps;for(c in d)s[c]===void 0&&(s[c]=d[c])}if(o||R){var m=typeof e=="function"?e.displayName||e.name||"Unknown":e;o&&sr(s,m),R&&ir(s,m)}return lr(e,o,R,l,n,ke.current,s)}}var fe=F.ReactCurrentOwner,Ne=F.ReactDebugCurrentFrame;function B(e){if(e){var r=e._owner,t=Q(e.type,e._source,r?r.type:null);Ne.setExtraStackFrame(t)}else Ne.setExtraStackFrame(null)}var de;de=!1;function pe(e){return typeof e=="object"&&e!==null&&e.$$typeof===E}function Ae(){{if(fe.current){var e=v(fe.current.type);if(e)return`
|
|
22
22
|
|
|
23
|
-
Check the render method of \``+e+"`."}return""}}function
|
|
23
|
+
Check the render method of \``+e+"`."}return""}}function ur(e){return""}var Fe={};function fr(e){{var r=Ae();if(!r){var t=typeof e=="string"?e:e.displayName||e.name;t&&(r=`
|
|
24
24
|
|
|
25
|
-
Check the top-level render call using <`+t+">.")}return r}}function
|
|
25
|
+
Check the top-level render call using <`+t+">.")}return r}}function Ie(e,r){{if(!e._store||e._store.validated||e.key!=null)return;e._store.validated=!0;var t=fr(r);if(Fe[t])return;Fe[t]=!0;var n="";e&&e._owner&&e._owner!==fe.current&&(n=" It was passed a child from "+v(e._owner.type)+"."),B(e),x('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.',t,n),B(null)}}function ze(e,r){{if(typeof e!="object")return;if(ue(e))for(var t=0;t<e.length;t++){var n=e[t];pe(n)&&Ie(n,r)}else if(pe(e))e._store&&(e._store.validated=!0);else if(e){var l=G(e);if(typeof l=="function"&&l!==e.entries)for(var c=l.call(e),s;!(s=c.next()).done;)pe(s.value)&&Ie(s.value,r)}}}function dr(e){{var r=e.type;if(r==null||typeof r=="string")return;var t;if(typeof r=="function")t=r.propTypes;else if(typeof r=="object"&&(r.$$typeof===y||r.$$typeof===C))t=r.propTypes;else return;if(t){var n=v(r);Ze(t,e.props,"prop",n,e)}else if(r.PropTypes!==void 0&&!de){de=!0;var l=v(r);x("Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?",l||"Unknown")}typeof r.getDefaultProps=="function"&&!r.getDefaultProps.isReactClassApproved&&x("getDefaultProps is only used on classic React.createClass definitions. Use a static property named `defaultProps` instead.")}}function pr(e){{for(var r=Object.keys(e.props),t=0;t<r.length;t++){var n=r[t];if(n!=="children"&&n!=="key"){B(e),x("Invalid prop `%s` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.",n),B(null);break}}e.ref!==null&&(B(e),x("Invalid attribute `ref` supplied to `React.Fragment`."),B(null))}}var Le={};function We(e,r,t,n,l,c){{var s=p(e);if(!s){var o="";(e===void 0||typeof e=="object"&&e!==null&&Object.keys(e).length===0)&&(o+=" You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.");var R=ur();R?o+=R:o+=Ae();var d;e===null?d="null":ue(e)?d="array":e!==void 0&&e.$$typeof===E?(d="<"+(v(e.type)||"Unknown")+" />",o=" Did you accidentally export a JSX literal instead of a component?"):d=typeof e,x("React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s",d,o)}var m=cr(e,r,t,l,c);if(m==null)return m;if(s){var O=r.children;if(O!==void 0)if(n)if(ue(O)){for(var V=0;V<O.length;V++)ze(O[V],e);Object.freeze&&Object.freeze(O)}else x("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else ze(O,e)}if(H.call(r,"key")){var U=v(e),P=Object.keys(r).filter(function(yr){return yr!=="key"}),ve=P.length>0?"{key: someKey, "+P.join(": ..., ")+": ...}":"{key: someKey}";if(!Le[U+ve]){var br=P.length>0?"{"+P.join(": ..., ")+": ...}":"{}";x(`A props object containing a "key" prop is being spread into JSX:
|
|
26
26
|
let props = %s;
|
|
27
27
|
<%s {...props} />
|
|
28
28
|
React keys must be passed directly to JSX without using spread:
|
|
29
29
|
let props = %s;
|
|
30
|
-
<%s key={someKey} {...props} />`,
|
|
30
|
+
<%s key={someKey} {...props} />`,ve,U,br,U),Le[U+ve]=!0}}return e===g?pr(m):dr(m),m}}function vr(e,r,t){return We(e,r,t,!0)}function mr(e,r,t){return We(e,r,t,!1)}var gr=mr,hr=vr;K.Fragment=g,K.jsx=gr,K.jsxs=hr}()),K}process.env.NODE_ENV==="production"?re.exports=Me():re.exports=Ue();var a=re.exports;const Ye="StyllarDB",M="user_profile",te=()=>new Promise((j,E)=>{const f=indexedDB.open(Ye,1);f.onupgradeneeded=g=>g.target.result.createObjectStore(M),f.onsuccess=()=>j(f.result),f.onerror=()=>E(f.error)}),Be=async j=>{const E=await te();return new Promise((f,g)=>{const N=E.transaction(M,"readwrite").objectStore(M).put(j,"userData");N.onsuccess=()=>f(),N.onerror=()=>g(N.error)})},Ve=async()=>{const j=await te();return new Promise((E,f)=>{const S=j.transaction(M,"readonly").objectStore(M).get("userData");S.onsuccess=()=>E(S.result),S.onerror=()=>f(S.error)})},Je=async()=>{const j=await te();return new Promise((E,f)=>{const S=j.transaction(M,"readwrite").objectStore(M).delete("userData");S.onsuccess=()=>E(),S.onerror=()=>f(S.error)})},Ke=()=>{const[j,E]=_.useState(!1),[f,g]=_.useState("idle"),[S,N]=_.useState(""),[I,D]=_.useState(null),[y,A]=_.useState(!1),[h,C]=_.useState(""),[k,z]=_.useState(""),[Y,X]=_.useState(null),[G,F]=_.useState(null),x="http://136.113.197.77:5000/predict/size-from-chart";_.useEffect(()=>{(async()=>{try{const u=await Ve();u&&u.frontImage&&u.sideImage&&(C(u.height),z(u.weight),X(u.frontImage),F(u.sideImage),A(!0),console.log("Styllar: Loaded user profile from memory."))}catch(u){console.error("Styllar: Failed to load profile",u)}})()},[]);const ne=i=>{if(!i)return;const u=()=>{const p=document.querySelectorAll('button, .size-swatch, .size-option, li[role="radio"], div[data-value]');for(let T of p){const w=T.innerText.trim().toUpperCase(),v=String(i).trim().toUpperCase();if(w===v||w===`SIZE ${v}`)return T.click(),!0}return!1};if(!u()){let p=0;const T=setInterval(()=>{p++,(u()||p>10)&&clearInterval(T)},500)}};function ae(){var v;let i=0,u=0;const p=["pant","jeans","trouser","skirt","short","legging","jogger","bottom","denim","cargo"],T=["shirt","top","tee","blouse","jacket","coat","hoodie","sweater","vest","cardigan","dress","t-shirt"],w=(document.title+" "+(((v=document.querySelector("h1"))==null?void 0:v.innerText)||"")).toLowerCase();return p.forEach(b=>{w.includes(b)&&(u+=2)}),T.forEach(b=>{w.includes(b)&&(i+=2)}),u>i?"bottom":"top"}function oe(){const i={chart_type:"none",chart_content:null,available_sizes:[]},u=document.querySelector("table");if(u&&/chest|waist|bust|hip|size/i.test(u.innerText))return i.chart_type="html_table",i.chart_content=u.outerHTML,i;const p=document.querySelectorAll("img");for(let v of p){const b=v.src.toLowerCase(),L=(v.alt||"").toLowerCase();if(b.includes("size")&&b.includes("chart")||L.includes("size")&&L.includes("guide"))return i.chart_type="image_url",i.chart_content=v.src,i}const T=document.querySelector('.size-guide, #size-chart, .measurement-guide, [class*="SizeGuide"]');return T?(i.chart_type="raw_text",i.chart_content=T.innerText,i):(document.querySelectorAll("button, span, li").forEach(v=>{const b=v.innerText.trim();/^(XS|S|M|L|XL|XXL|[0-9]{2})$/i.test(b)&&b.length<4&&(i.available_sizes.includes(b)||i.available_sizes.push(b))}),i)}const q=async()=>{if(!Y||!G||!h||!k){alert("Please fill all fields.");return}g("loading"),await Be({height:h,weight:k,frontImage:Y,sideImage:G}),A(!0);const i=ae(),u=oe(),p=new FormData;p.append("front_image",Y),p.append("side_image",G),p.append("height",h),p.append("weight",k),p.append("product_type",i),p.append("chart_type",u.chart_type),p.append("chart_content",u.chart_content),p.append("available_sizes",JSON.stringify(u.available_sizes));try{const w=await(await fetch(x,{method:"POST",body:p})).json();if(w.error)throw new Error(w.error);D(w),g("success"),w.recommended_size&&ne(w.recommended_size)}catch(T){N(T.message),g("error")}},se=async()=>{await Je(),C(""),z(""),X(null),F(null),A(!1),D(null),g("idle")};return a.jsxs("div",{className:"fixed bottom-6 right-6 z-[999999]",children:[a.jsx("button",{onClick:()=>E(!j),className:"bg-black text-white px-7 py-3.5 rounded-full shadow-xl font-bold text-sm hover:opacity-80 transition-opacity",children:"Styllar"}),j&&a.jsxs("div",{className:"absolute bottom-20 right-0 w-[330px] bg-white rounded-2xl p-6 shadow-2xl border border-gray-100 text-black",children:[a.jsxs("div",{className:"flex justify-between items-center mb-4",children:[a.jsx("h3",{className:"m-0 text-lg font-bold",children:"Smart Fit Assistant"}),a.jsx("button",{onClick:()=>E(!1),className:"text-gray-400 hover:text-black text-2xl leading-none",children:"×"})]}),f==="loading"&&a.jsxs("div",{className:"flex flex-col items-center justify-center py-8",children:[a.jsx("div",{className:"w-8 h-8 border-4 border-gray-200 border-t-black rounded-full animate-spin mb-4"}),a.jsx("p",{className:"text-sm text-gray-600",children:"Analyzing body & page data..."})]}),f==="success"&&I&&a.jsxs("div",{className:"text-center py-6",children:[a.jsx("div",{className:"text-sm text-green-600 font-bold tracking-wider",children:"MATCH FOUND"}),a.jsx("div",{className:"text-5xl font-black my-4 text-black",children:I.recommended_size}),a.jsxs("div",{className:"text-xs text-gray-500 mb-6",children:["Confidence: ",I.confidence]}),a.jsx("button",{onClick:()=>{g("idle"),D(null)},className:"text-blue-600 text-sm underline hover:text-blue-800",children:"Scan Another Product"})]}),f==="error"&&a.jsxs("div",{className:"py-4",children:[a.jsxs("p",{className:"text-red-500 text-sm mb-4",children:["Error: ",S]}),a.jsx("button",{onClick:()=>g("idle"),className:"w-full bg-black text-white py-3 rounded-xl font-bold text-sm",children:"Try Again"})]}),f==="idle"&&y&&a.jsxs("div",{className:"py-4",children:[a.jsxs("div",{className:"bg-gray-50 border border-gray-200 rounded-xl p-4 mb-5 text-center",children:[a.jsx("span",{className:"block text-xl mb-2",children:"👤"}),a.jsx("h4",{className:"font-bold text-sm mb-1",children:"Body Profile Saved"}),a.jsxs("p",{className:"text-xs text-gray-500",children:[h,"cm | ",k,"kg"]})]}),a.jsx("button",{onClick:q,className:"w-full bg-black text-white py-3.5 rounded-xl font-bold text-sm hover:opacity-80 transition-opacity mb-3",children:"⚡ Auto-Scan This Product"}),a.jsx("button",{onClick:se,className:"w-full text-gray-500 text-xs underline hover:text-black",children:"Edit Profile / Upload New Photos"})]}),f==="idle"&&!y&&a.jsxs("div",{children:[a.jsx("p",{className:"text-sm text-gray-600 mb-5 leading-relaxed",children:"Upload photos for a precision body scan."}),a.jsxs("div",{className:"flex gap-3 mb-4",children:[a.jsx("input",{type:"number",placeholder:"Height (cm)",value:h,onChange:i=>C(i.target.value),className:"w-1/2 p-2.5 border border-gray-300 rounded-lg outline-none focus:border-black text-sm"}),a.jsx("input",{type:"number",placeholder:"Weight (kg)",value:k,onChange:i=>z(i.target.value),className:"w-1/2 p-2.5 border border-gray-300 rounded-lg outline-none focus:border-black text-sm"})]}),a.jsxs("div",{className:"mb-3",children:[a.jsx("label",{className:"text-[11px] font-bold text-gray-400 uppercase block mb-1",children:"Front Image"}),a.jsx("input",{type:"file",accept:"image/*",onChange:i=>X(i.target.files[0]),className:"w-full text-xs file:mr-3 file:py-1.5 file:px-3 file:rounded-md file:border-0 file:text-xs file:bg-gray-100 file:text-black hover:file:bg-gray-200"})]}),a.jsxs("div",{className:"mb-5",children:[a.jsx("label",{className:"text-[11px] font-bold text-gray-400 uppercase block mb-1",children:"Side Image"}),a.jsx("input",{type:"file",accept:"image/*",onChange:i=>F(i.target.files[0]),className:"w-full text-xs file:mr-3 file:py-1.5 file:px-3 file:rounded-md file:border-0 file:text-xs file:bg-gray-100 file:text-black hover:file:bg-gray-200"})]}),a.jsx("button",{onClick:q,className:"w-full bg-black text-white py-3.5 rounded-xl font-bold text-sm hover:opacity-80 transition-opacity",children:"Save Profile & Scan"})]})]})]})};W.StyllarPlugin=Ke,Object.defineProperty(W,Symbol.toStringTag,{value:"Module"})});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "styllar-react-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/styllar-react-plugin.umd.cjs",
|
|
6
6
|
"module": "dist/styllar-react-plugin.js",
|
|
@@ -35,4 +35,4 @@
|
|
|
35
35
|
"tailwindcss": "^4.2.0",
|
|
36
36
|
"vite": "^5.4.10"
|
|
37
37
|
}
|
|
38
|
-
}
|
|
38
|
+
}
|
package/src/StyllarPlugin.jsx
CHANGED
|
@@ -1,13 +1,56 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
2
|
|
|
3
|
+
// --- 1. INDEXED-DB HELPERS (For storing large image files safely) ---
|
|
4
|
+
const DB_NAME = 'StyllarDB';
|
|
5
|
+
const STORE_NAME = 'user_profile';
|
|
6
|
+
|
|
7
|
+
const openDB = () => {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const request = indexedDB.open(DB_NAME, 1);
|
|
10
|
+
request.onupgradeneeded = (e) => e.target.result.createObjectStore(STORE_NAME);
|
|
11
|
+
request.onsuccess = () => resolve(request.result);
|
|
12
|
+
request.onerror = () => reject(request.error);
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const saveProfileToDB = async (profileData) => {
|
|
17
|
+
const db = await openDB();
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const tx = db.transaction(STORE_NAME, 'readwrite');
|
|
20
|
+
const request = tx.objectStore(STORE_NAME).put(profileData, 'userData');
|
|
21
|
+
request.onsuccess = () => resolve();
|
|
22
|
+
request.onerror = () => reject(request.error);
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const getProfileFromDB = async () => {
|
|
27
|
+
const db = await openDB();
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const tx = db.transaction(STORE_NAME, 'readonly');
|
|
30
|
+
const request = tx.objectStore(STORE_NAME).get('userData');
|
|
31
|
+
request.onsuccess = () => resolve(request.result);
|
|
32
|
+
request.onerror = () => reject(request.error);
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const clearProfileFromDB = async () => {
|
|
37
|
+
const db = await openDB();
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const tx = db.transaction(STORE_NAME, 'readwrite');
|
|
40
|
+
const request = tx.objectStore(STORE_NAME).delete('userData');
|
|
41
|
+
request.onsuccess = () => resolve();
|
|
42
|
+
request.onerror = () => reject(request.error);
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// --- MAIN COMPONENT ---
|
|
3
47
|
const StyllarPlugin = () => {
|
|
4
|
-
// --- 1. REACT STATE MANAGEMENT ---
|
|
5
48
|
const [isOpen, setIsOpen] = useState(false);
|
|
6
|
-
const [status, setStatus] = useState('idle');
|
|
49
|
+
const [status, setStatus] = useState('idle');
|
|
7
50
|
const [errorMsg, setErrorMsg] = useState('');
|
|
8
51
|
const [result, setResult] = useState(null);
|
|
52
|
+
const [hasSavedProfile, setHasSavedProfile] = useState(false);
|
|
9
53
|
|
|
10
|
-
// Form states
|
|
11
54
|
const [height, setHeight] = useState('');
|
|
12
55
|
const [weight, setWeight] = useState('');
|
|
13
56
|
const [frontImage, setFrontImage] = useState(null);
|
|
@@ -15,7 +58,52 @@ const StyllarPlugin = () => {
|
|
|
15
58
|
|
|
16
59
|
const API_ENDPOINT = "http://136.113.197.77:5000/predict/size-from-chart";
|
|
17
60
|
|
|
18
|
-
// --- 2.
|
|
61
|
+
// --- 2. LOAD SAVED PROFILE ON MOUNT ---
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
const loadUserData = async () => {
|
|
64
|
+
try {
|
|
65
|
+
const savedData = await getProfileFromDB();
|
|
66
|
+
if (savedData && savedData.frontImage && savedData.sideImage) {
|
|
67
|
+
setHeight(savedData.height);
|
|
68
|
+
setWeight(savedData.weight);
|
|
69
|
+
setFrontImage(savedData.frontImage);
|
|
70
|
+
setSideImage(savedData.sideImage);
|
|
71
|
+
setHasSavedProfile(true);
|
|
72
|
+
console.log("Styllar: Loaded user profile from memory.");
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error("Styllar: Failed to load profile", err);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
loadUserData();
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
// --- 3. AUTO-SELECT FUNCTION ---
|
|
82
|
+
const autoSelectSize = (targetSize) => {
|
|
83
|
+
if (!targetSize) return;
|
|
84
|
+
const trySelect = () => {
|
|
85
|
+
const sizeElements = document.querySelectorAll('button, .size-swatch, .size-option, li[role="radio"], div[data-value]');
|
|
86
|
+
for (let el of sizeElements) {
|
|
87
|
+
const text = el.innerText.trim().toUpperCase();
|
|
88
|
+
const recommended = String(targetSize).trim().toUpperCase();
|
|
89
|
+
if (text === recommended || text === `SIZE ${recommended}`) {
|
|
90
|
+
el.click();
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (!trySelect()) {
|
|
98
|
+
let attempts = 0;
|
|
99
|
+
const interval = setInterval(() => {
|
|
100
|
+
attempts++;
|
|
101
|
+
if (trySelect() || attempts > 10) clearInterval(interval);
|
|
102
|
+
}, 500);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// --- 4. SCRAPING LOGIC ---
|
|
19
107
|
function detectProductCategory() {
|
|
20
108
|
let scoreTop = 0; let scoreBottom = 0;
|
|
21
109
|
const bottomKeywords = ['pant', 'jeans', 'trouser', 'skirt', 'short', 'legging', 'jogger', 'bottom', 'denim', 'cargo'];
|
|
@@ -30,28 +118,19 @@ const StyllarPlugin = () => {
|
|
|
30
118
|
const data = { chart_type: 'none', chart_content: null, available_sizes: [] };
|
|
31
119
|
const table = document.querySelector('table');
|
|
32
120
|
if (table && /chest|waist|bust|hip|size/i.test(table.innerText)) {
|
|
33
|
-
data.chart_type = 'html_table';
|
|
34
|
-
data.chart_content = table.outerHTML;
|
|
35
|
-
return data;
|
|
121
|
+
data.chart_type = 'html_table'; data.chart_content = table.outerHTML; return data;
|
|
36
122
|
}
|
|
37
123
|
const allImages = document.querySelectorAll('img');
|
|
38
124
|
for (let img of allImages) {
|
|
39
|
-
const src = img.src.toLowerCase();
|
|
40
|
-
const alt = (img.alt || "").toLowerCase();
|
|
125
|
+
const src = img.src.toLowerCase(); const alt = (img.alt || "").toLowerCase();
|
|
41
126
|
if ((src.includes('size') && src.includes('chart')) || (alt.includes('size') && alt.includes('guide'))) {
|
|
42
|
-
data.chart_type = 'image_url';
|
|
43
|
-
data.chart_content = img.src;
|
|
44
|
-
return data;
|
|
127
|
+
data.chart_type = 'image_url'; data.chart_content = img.src; return data;
|
|
45
128
|
}
|
|
46
129
|
}
|
|
47
|
-
|
|
48
130
|
const sizeGuideDiv = document.querySelector('.size-guide, #size-chart, .measurement-guide, [class*="SizeGuide"]');
|
|
49
131
|
if (sizeGuideDiv) {
|
|
50
|
-
data.chart_type = 'raw_text';
|
|
51
|
-
data.chart_content = sizeGuideDiv.innerText;
|
|
52
|
-
return data;
|
|
132
|
+
data.chart_type = 'raw_text'; data.chart_content = sizeGuideDiv.innerText; return data;
|
|
53
133
|
}
|
|
54
|
-
|
|
55
134
|
const buttons = document.querySelectorAll('button, span, li');
|
|
56
135
|
buttons.forEach(el => {
|
|
57
136
|
const txt = el.innerText.trim();
|
|
@@ -62,14 +141,18 @@ const StyllarPlugin = () => {
|
|
|
62
141
|
return data;
|
|
63
142
|
}
|
|
64
143
|
|
|
65
|
-
// ---
|
|
144
|
+
// --- 5. SUBMIT HANDLER ---
|
|
66
145
|
const runBodyScan = async () => {
|
|
67
146
|
if (!frontImage || !sideImage || !height || !weight) {
|
|
68
|
-
alert("Please fill all fields.");
|
|
69
|
-
return;
|
|
147
|
+
alert("Please fill all fields."); return;
|
|
70
148
|
}
|
|
71
149
|
|
|
72
150
|
setStatus('loading');
|
|
151
|
+
|
|
152
|
+
// Save profile to IndexedDB so they don't have to enter it again on the next page
|
|
153
|
+
await saveProfileToDB({ height, weight, frontImage, sideImage });
|
|
154
|
+
setHasSavedProfile(true);
|
|
155
|
+
|
|
73
156
|
const category = detectProductCategory();
|
|
74
157
|
const pageData = scanPageForSizeData();
|
|
75
158
|
|
|
@@ -90,16 +173,27 @@ const StyllarPlugin = () => {
|
|
|
90
173
|
|
|
91
174
|
setResult(data);
|
|
92
175
|
setStatus('success');
|
|
176
|
+
|
|
177
|
+
if (data.recommended_size) {
|
|
178
|
+
autoSelectSize(data.recommended_size);
|
|
179
|
+
}
|
|
93
180
|
} catch (e) {
|
|
94
181
|
setErrorMsg(e.message);
|
|
95
182
|
setStatus('error');
|
|
96
183
|
}
|
|
97
184
|
};
|
|
98
185
|
|
|
99
|
-
|
|
186
|
+
const handleClearProfile = async () => {
|
|
187
|
+
await clearProfileFromDB();
|
|
188
|
+
setHeight(''); setWeight(''); setFrontImage(null); setSideImage(null);
|
|
189
|
+
setHasSavedProfile(false);
|
|
190
|
+
setResult(null);
|
|
191
|
+
setStatus('idle');
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// --- 6. UI RENDER ---
|
|
100
195
|
return (
|
|
101
196
|
<div className="fixed bottom-6 right-6 z-[999999]">
|
|
102
|
-
{/* The Trigger Button */}
|
|
103
197
|
<button
|
|
104
198
|
onClick={() => setIsOpen(!isOpen)}
|
|
105
199
|
className="bg-black text-white px-7 py-3.5 rounded-full shadow-xl font-bold text-sm hover:opacity-80 transition-opacity"
|
|
@@ -107,17 +201,14 @@ const StyllarPlugin = () => {
|
|
|
107
201
|
Styllar
|
|
108
202
|
</button>
|
|
109
203
|
|
|
110
|
-
{/* The Modal Window */}
|
|
111
204
|
{isOpen && (
|
|
112
205
|
<div className="absolute bottom-20 right-0 w-[330px] bg-white rounded-2xl p-6 shadow-2xl border border-gray-100 text-black">
|
|
113
206
|
|
|
114
|
-
{/* Header */}
|
|
115
207
|
<div className="flex justify-between items-center mb-4">
|
|
116
208
|
<h3 className="m-0 text-lg font-bold">Smart Fit Assistant</h3>
|
|
117
209
|
<button onClick={() => setIsOpen(false)} className="text-gray-400 hover:text-black text-2xl leading-none">×</button>
|
|
118
210
|
</div>
|
|
119
211
|
|
|
120
|
-
{/* Loading State */}
|
|
121
212
|
{status === 'loading' && (
|
|
122
213
|
<div className="flex flex-col items-center justify-center py-8">
|
|
123
214
|
<div className="w-8 h-8 border-4 border-gray-200 border-t-black rounded-full animate-spin mb-4"></div>
|
|
@@ -125,7 +216,6 @@ const StyllarPlugin = () => {
|
|
|
125
216
|
</div>
|
|
126
217
|
)}
|
|
127
218
|
|
|
128
|
-
{/* Success State */}
|
|
129
219
|
{status === 'success' && result && (
|
|
130
220
|
<div className="text-center py-6">
|
|
131
221
|
<div className="text-sm text-green-600 font-bold tracking-wider">MATCH FOUND</div>
|
|
@@ -135,12 +225,11 @@ const StyllarPlugin = () => {
|
|
|
135
225
|
onClick={() => { setStatus('idle'); setResult(null); }}
|
|
136
226
|
className="text-blue-600 text-sm underline hover:text-blue-800"
|
|
137
227
|
>
|
|
138
|
-
|
|
228
|
+
Scan Another Product
|
|
139
229
|
</button>
|
|
140
230
|
</div>
|
|
141
231
|
)}
|
|
142
232
|
|
|
143
|
-
{/* Error State */}
|
|
144
233
|
{status === 'error' && (
|
|
145
234
|
<div className="py-4">
|
|
146
235
|
<p className="text-red-500 text-sm mb-4">Error: {errorMsg}</p>
|
|
@@ -153,8 +242,35 @@ const StyllarPlugin = () => {
|
|
|
153
242
|
</div>
|
|
154
243
|
)}
|
|
155
244
|
|
|
156
|
-
{/*
|
|
157
|
-
{status === 'idle' && (
|
|
245
|
+
{/* NEW: Display this if they have a saved profile */}
|
|
246
|
+
{status === 'idle' && hasSavedProfile && (
|
|
247
|
+
<div className="py-4">
|
|
248
|
+
<div className="bg-gray-50 border border-gray-200 rounded-xl p-4 mb-5 text-center">
|
|
249
|
+
<span className="block text-xl mb-2">👤</span>
|
|
250
|
+
<h4 className="font-bold text-sm mb-1">Body Profile Saved</h4>
|
|
251
|
+
<p className="text-xs text-gray-500">
|
|
252
|
+
{height}cm | {weight}kg
|
|
253
|
+
</p>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<button
|
|
257
|
+
onClick={runBodyScan}
|
|
258
|
+
className="w-full bg-black text-white py-3.5 rounded-xl font-bold text-sm hover:opacity-80 transition-opacity mb-3"
|
|
259
|
+
>
|
|
260
|
+
⚡ Auto-Scan This Product
|
|
261
|
+
</button>
|
|
262
|
+
|
|
263
|
+
<button
|
|
264
|
+
onClick={handleClearProfile}
|
|
265
|
+
className="w-full text-gray-500 text-xs underline hover:text-black"
|
|
266
|
+
>
|
|
267
|
+
Edit Profile / Upload New Photos
|
|
268
|
+
</button>
|
|
269
|
+
</div>
|
|
270
|
+
)}
|
|
271
|
+
|
|
272
|
+
{/* Original form, only visible if NO saved profile exists */}
|
|
273
|
+
{status === 'idle' && !hasSavedProfile && (
|
|
158
274
|
<div>
|
|
159
275
|
<p className="text-sm text-gray-600 mb-5 leading-relaxed">
|
|
160
276
|
Upload photos for a precision body scan.
|
|
@@ -162,16 +278,12 @@ const StyllarPlugin = () => {
|
|
|
162
278
|
|
|
163
279
|
<div className="flex gap-3 mb-4">
|
|
164
280
|
<input
|
|
165
|
-
type="number"
|
|
166
|
-
placeholder="Height (cm)"
|
|
167
|
-
value={height}
|
|
281
|
+
type="number" placeholder="Height (cm)" value={height}
|
|
168
282
|
onChange={(e) => setHeight(e.target.value)}
|
|
169
283
|
className="w-1/2 p-2.5 border border-gray-300 rounded-lg outline-none focus:border-black text-sm"
|
|
170
284
|
/>
|
|
171
285
|
<input
|
|
172
|
-
type="number"
|
|
173
|
-
placeholder="Weight (kg)"
|
|
174
|
-
value={weight}
|
|
286
|
+
type="number" placeholder="Weight (kg)" value={weight}
|
|
175
287
|
onChange={(e) => setWeight(e.target.value)}
|
|
176
288
|
className="w-1/2 p-2.5 border border-gray-300 rounded-lg outline-none focus:border-black text-sm"
|
|
177
289
|
/>
|
|
@@ -180,9 +292,7 @@ const StyllarPlugin = () => {
|
|
|
180
292
|
<div className="mb-3">
|
|
181
293
|
<label className="text-[11px] font-bold text-gray-400 uppercase block mb-1">Front Image</label>
|
|
182
294
|
<input
|
|
183
|
-
type="file"
|
|
184
|
-
accept="image/*"
|
|
185
|
-
onChange={(e) => setFrontImage(e.target.files[0])}
|
|
295
|
+
type="file" accept="image/*" onChange={(e) => setFrontImage(e.target.files[0])}
|
|
186
296
|
className="w-full text-xs file:mr-3 file:py-1.5 file:px-3 file:rounded-md file:border-0 file:text-xs file:bg-gray-100 file:text-black hover:file:bg-gray-200"
|
|
187
297
|
/>
|
|
188
298
|
</div>
|
|
@@ -190,9 +300,7 @@ const StyllarPlugin = () => {
|
|
|
190
300
|
<div className="mb-5">
|
|
191
301
|
<label className="text-[11px] font-bold text-gray-400 uppercase block mb-1">Side Image</label>
|
|
192
302
|
<input
|
|
193
|
-
type="file"
|
|
194
|
-
accept="image/*"
|
|
195
|
-
onChange={(e) => setSideImage(e.target.files[0])}
|
|
303
|
+
type="file" accept="image/*" onChange={(e) => setSideImage(e.target.files[0])}
|
|
196
304
|
className="w-full text-xs file:mr-3 file:py-1.5 file:px-3 file:rounded-md file:border-0 file:text-xs file:bg-gray-100 file:text-black hover:file:bg-gray-200"
|
|
197
305
|
/>
|
|
198
306
|
</div>
|
|
@@ -201,7 +309,7 @@ const StyllarPlugin = () => {
|
|
|
201
309
|
onClick={runBodyScan}
|
|
202
310
|
className="w-full bg-black text-white py-3.5 rounded-xl font-bold text-sm hover:opacity-80 transition-opacity"
|
|
203
311
|
>
|
|
204
|
-
|
|
312
|
+
Save Profile & Scan
|
|
205
313
|
</button>
|
|
206
314
|
</div>
|
|
207
315
|
)}
|