@solumflow-app/product-configurator 0.3.0 → 0.3.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/index.js +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {e,g,f,b,k,c,d,a,o,l,i,h,n}from'./chunk-ATEH2U7F.js';import {b as b$1}from'./chunk-RNTBO6KY.js';import {lazy,useMemo,useState,useSyncExternalStore,Suspense,useRef,useEffect,useId}from'react';import {jsxs,jsx}from'react/jsx-runtime';var X="#b8b4ad",
|
|
1
|
+
import {e,g,f,b,k,c,d,a,o,l,i,h,n}from'./chunk-ATEH2U7F.js';import {b as b$1}from'./chunk-RNTBO6KY.js';import {lazy,useMemo,useState,useSyncExternalStore,Suspense,useRef,useEffect,useId}from'react';import {jsxs,jsx}from'react/jsx-runtime';var X="#b8b4ad",_e=1e-6;function W(n){let{structure:e,value:t,selection:o}=n,r=[],l$1=new Map(e.parameters.map(a=>[a.key,a])),m=new Map(e.materials.map(a=>[a.code,a]));for(let a$1 of Object.keys(o.values))l$1.has(a$1)||r.push(a(["values",a$1],"configurator.unknownParam",{key:a$1}));let s=e.presets?t.presets.map(a=>({code:a.code,label:a.label,productId:a.productId})):[],u=null;o.preset!==void 0&&(u=s.find(a=>a.code===o.preset)??null,u||r.push(a(["preset"],"configurator.presetNotOffered",{code:o.preset}))),!u&&e.presets?.required&&(u=s[0]??null);let c=u?t.presets.find(a=>a.code===u?.code)?.values??{}:{},p=new Set(e.presets?.sets??[]),f=new Map,v={},d$1=a=>{let g=f.get(a.param),N=g?.visible?g.value:null;switch(a.op){case "eq":return N===a.value;case "neq":return N!==a.value;case "in":return Array.isArray(a.value)&&a.value.includes(N);case "gte":return typeof N=="number"&&N>=a.value;case "lte":return typeof N=="number"&&N<=a.value}};for(let a$1 of l(e)){let g=l$1.get(a$1);if(!g)continue;let N=e.rules.filter(R=>R.param===a$1),i=N.filter(R=>R.kind==="visible").every(R=>d$1(R.when)),b=o.values[a$1],h=["values",a$1],I={key:a$1,kind:g.kind,label:g.label,tab:g.tab};if(g.kind==="number"){let R=t.numbers[a$1]??{min:g.min,max:g.max,default:g.min},C=R.min,P=R.max;for(let H of N){if(H.kind!=="limit")continue;let K=d(H.formula,v);if(!K.ok){r.push(a(["rules",e.rules.indexOf(H)],`configurator.${K.code}`));continue}H.bound==="min"?C=Math.max(C,K.value):P=Math.min(P,K.value);}C>P&&(r.push(a(h,"configurator.emptyRange",{key:a$1,min:C,max:P})),P=C);let j=p.has(a$1)?c[a$1]:void 0,B=pe(R.default,C,P);j!==void 0?B=j:i&&b!==void 0&&(typeof b!="number"?r.push(a(h,"configurator.expectedNumber",{key:a$1})):b<C||b>P?(r.push(a(h,"configurator.outOfRange",{key:a$1,min:C,max:P})),B=pe(b,C,P)):Me(b,g.step)?B=b:r.push(a(h,"configurator.offStep",{key:a$1,step:g.step}))),v[a$1]=B,f.set(a$1,{...I,visible:i,value:B,min:C,max:P,step:g.step,unit:g.unit,fixed:j!==void 0,settled:j===void 0&&C===P});continue}if(g.kind==="toggle"){let R=t.toggles[a$1]??{offered:true,default:false};i=i&&R.offered;let C=R.default;i&&b!==void 0&&(typeof b!="boolean"?r.push(a(h,"configurator.expectedBoolean",{key:a$1})):C=b);let P=i&&C;v[a$1]=P?1:0,f.set(a$1,{...I,visible:i,value:P});continue}let A=new Set(N.filter(R=>R.kind==="exclude"&&d$1(R.when)).flatMap(R=>R.kind==="exclude"?R.codes:[])),L,J;if(g.kind==="choice"){let R=e.choiceLists.find(P=>P.key===g.list),C=t.choices[g.list];L=(R?.options??[]).filter(P=>C?.options[P.code]!==void 0&&!A.has(P.code)).map(P=>({code:P.code,label:P.label,priceCents:C?.options[P.code]?.priceCents??null})),J=C?.default;}else {let R=t.materials[a$1];L=(R?.offered??[]).map(C=>m.get(C)).filter(C=>C!==void 0).filter(C=>!A.has(C.code)).map(C=>({code:C.code,label:C.label,priceCents:C.priceGroup?t.priceGroups[C.priceGroup]??null:0,material:C})),J=R?.default;}i=i&&L.length>0;let ne=L.find(R=>R.code===J)?.code??L[0]?.code??null;i&&b!==void 0&&(typeof b!="string"||!L.some(R=>R.code===b)?r.push(a(h,"configurator.optionNotOffered",{key:a$1,code:String(b)})):ne=b),f.set(a$1,{...I,visible:i,value:i?ne:null,options:L,settled:L.length===1});}let y=e.parameters.map(a=>f.get(a.key)).filter(a=>a!==void 0),x={};for(let a of e.geometry.inputs){let g=f.get(a.binding.param);g&&(g.kind==="number"?x[a.name]=g.value:g.kind==="toggle"?x[a.name]=g.value===true?1:0:g.kind==="choice"&&(x[a.name]=typeof g.value=="string"?a.binding.map?.[g.value]??0:0));}let S={};for(let a of e.geometry.slots){let g=null;if(a.param){let N=f.get(a.param);g=N?.options?.find(i=>i.code===N.value)?.material??null;}else a.material&&(g=m.get(a.material)??null);S[a.name]={color:g?.color??a.color??X,material:g,grain:a.grain==="vertical"?"vertical":"horizontal"};}let M={values:{}};for(let a of y)a.visible&&!a.fixed&&a.value!==null&&(M.values[a.key]=a.value);return u&&(M.preset=u.code),{configuration:{parameters:y,byKey:Object.fromEntries(f),presets:s,preset:u,inputs:x,slots:S,selection:M},issues:r}}function me(n){let e={};for(let t of n.parameters)t.kind==="choice"&&t.visible&&typeof t.value=="string"&&(e[t.key]=t.value);return {choices:e,preset:n.preset?.code}}function pe(n,e,t){return Math.min(t,Math.max(e,n))}function Me(n,e){let t=n/e;return Math.abs(t-Math.round(t))<_e}var Ie=1;function V(n){let{structure:e,value:t,tax:o}=n,{configuration:r,issues:l}=W(n);if(l.length>0)return {ok:false,issues:l,configuration:r};let m=[],s={};for(let d of r.parameters)d.kind==="number"?s[d.key]=d.value:d.kind==="toggle"&&(s[d.key]=d.value===true?1:0);let u=[{key:"base",label:"base",amountCents:t.basePriceCents}],c$1=(d$1,y)=>{if(d$1===void 0)return 1;let x=d(d$1,s);return x.ok?x.value<0?(m.push(a(y,"configurator.negativeMultiplier")),null):x.value:(m.push(a(y,`configurator.${x.code}`)),null)};if(e.priceRules.forEach((d$1,y)=>{let x=["priceRules",y];if(d$1.kind==="formula"){let g=Object.fromEntries(e.amounts.map(I=>[I.key,t.amounts[I.key]])),N=c(d$1.formula);if(N.ok&&N.names.some(I=>I in g&&g[I]==null)){u.push({key:d$1.key,label:d$1.label,amountCents:null});return}let b={...s};for(let[I,A]of Object.entries(g))typeof A=="number"&&(b[I]=A);let h=d(d$1.formula,b);if(!h.ok){m.push(a([...x,"formula"],`configurator.${h.code}`));return}if(h.value<0){m.push(a([...x,"formula"],"configurator.negativeAmount"));return}de(u,d$1.key,d$1.label,Math.round(h.value));return}let S=r.byKey[d$1.param];if(!S?.visible)return;let M;if(d$1.kind==="toggle"){if(S.value!==true)return;M=t.amounts[d$1.amount]??null;}else {let g=S.options?.find(N=>N.code===S.value);if(!g)return;M=g.priceCents;}if(M===null){u.push({key:d$1.key,label:d$1.label,amountCents:null});return}let a$1=c$1(d$1.multiplier,[...x,"multiplier"]);a$1!==null&&de(u,d$1.key,d$1.label,Math.round(M*a$1));}),m.length>0)return {ok:false,issues:m,configuration:r};let p=t.basePriceCents===null?"open":u.some(d=>d.amountCents===null)?"partial":"priced",f=u.reduce((d,y)=>d+(y.amountCents??0),0),v=p==="open"?null:Le(f,o);return {ok:true,configuration:r,quote:{engineVersion:Ie,state:p,lines:u,netCents:v?.netCents??null,taxCents:v?.taxCents??null,totalCents:v?.totalCents??null,taxPercentage:o.taxPercentage,pricesIncludeTax:o.pricesIncludeTax}}}function de(n,e,t,o){o!==0&&n.push({key:e,label:t,amountCents:o});}function Le(n,e){let t=e.taxPercentage>0?e.taxPercentage:0;if(e.pricesIncludeTax){let r=Math.round(n*t/(100+t));return {netCents:n-r,taxCents:r,totalCents:n}}let o=Math.round(n*t/100);return {netCents:n,taxCents:o,totalCents:n+o}}function ge(n,e,t={choices:{}}){let o=[],r=[],l={...n.constants};for(let c of n.inputs){let p=e[c.name];if(p===void 0||!Number.isFinite(p)){o.push(a(["inputs",c.name],"configurator.inputMissing",{name:c.name}));continue}l[c.name]=p;}if(o.length>0)return {ok:false,issues:o};for(let c of i(n.root))l[c.name]=fe(c.node,t)===void 0?0:1;let m=(c,p,f)=>{let v=d(c,p);return v.ok?v.value:(o.push(v.code==="formula_unknown_name"?a(f,"configurator.unknownName",{name:v.name}):a(f,`configurator.${v.code}`)),null)},s=(c,p,f,v,d)=>{for(let y of c){if(o.length>0)return;let x=[...f,y.id];if(y.visible!==void 0){let i=m(y.visible,p,[...x,"visible"]);if(i===null||i<=0)continue}if(y.type==="repeat"){if(d+1>b.repeatDepth){o.push(a(x,"configurator.repeatTooDeep",{max:b.repeatDepth}));return}let i=m(y.count,p,[...x,"count"]);if(i===null)return;if(!Number.isInteger(i)||i<0||i>b.repeatCount){o.push(a([...x,"count"],"configurator.repeatCountInvalid",{count:i,max:b.repeatCount}));return}let b$1=y.index??"i";for(let h=0;h<i;h+=1)s(y.children,{...p,[b$1]:h},[...x,h],`${v}[${h}]`,d+1);continue}let S=y.size.map((i,b)=>m(i,p,[...x,"size",b])),M=y.at.map((i,b)=>m(i,p,[...x,"at",b]));if(S.includes(null)||M.includes(null))return;let a$1=S.findIndex(i=>i<0);if(a$1>=0){o.push(a([...x,"size",a$1],"configurator.negativeMeasure",{value:S[a$1]}));return}let g=fe(y,t);if(y.type==="model"&&g===void 0)continue;let N={};for(let[i,b]of Object.entries(y.params??{})){let h=m(b,p,[...x,"params",i]);if(h===null)return;N[i]=h;}if(r.push({key:`${y.id}${v}`,id:y.id,type:y.type,slot:y.slot,size:S,at:M,params:N,...g===void 0?{}:{model:g}}),r.length>b.expandedParts){o.push(a(["root"],"configurator.tooManyParts",{max:b.expandedParts}));return}}};s(n.root,l,["root"],"",0);let u=[];for(let c of n.annotations){let p=["annotations",c.id],f=m(c.from,l,[...p,"from"]),v=m(c.to,l,[...p,"to"]),d=m(c.at,l,[...p,"at"]);f===null||v===null||d===null||u.push({id:c.id,axis:c.axis,from:f,to:v,at:d,label:c.label??null,length:Math.abs(v-f)});}return o.length>0?{ok:false,issues:o}:{ok:true,geometry:{parts:r,dimensions:u,bounds:$e(r)}}}function $e(n){if(n.length===0)return {min:[0,0,0],max:[0,0,0]};let e=[1/0,1/0,1/0],t=[-1/0,-1/0,-1/0];for(let o of n)for(let r=0;r<3;r+=1)e[r]=Math.min(e[r],o.at[r]),t[r]=Math.max(t[r],o.at[r]+o.size[r]);return {min:e,max:t}}function fe(n,e){if(n.follows===void 0)return n.model;let t=n.follows===h?e.preset:e.choices[n.follows];return t===void 0?void 0:n.models?.[t]}var G={tabsLabel:"Onderdelen van de configurator",presetNone:"Geen keuze",switchOn:"Aan",switchOff:"Uit",unitMm:"mm",unitCount:"stuks",surchargePrefix:"+",optionOnRequest:"op aanvraag",priceHeading:"Prijs",priceIncludingTax:"incl. btw",priceFrom:"vanaf",priceInPartOnRequest:"deels op aanvraag",priceOnRequest:"prijs op aanvraag",priceUnavailable:"Prijs nog niet te berekenen",sketchTitle:"Maatschets",sketchUnavailable:"Deze combinatie kan niet getekend worden.",sceneLoading:"3D-weergave laden\u2026",sceneLabel:"3D-weergave van de configuratie",cameraControls:"Beeld verschuiven en zoomen",cameraLeft:"Beeld naar links",cameraRight:"Beeld naar rechts",cameraUp:"Beeld omhoog",cameraDown:"Beeld omlaag",cameraZoomIn:"Inzoomen",cameraZoomOut:"Uitzoomen",cameraReset:"Beeld herstellen"};var Te=`
|
|
2
2
|
.pc-scope{
|
|
3
3
|
--_fg:var(--pc-fg,currentColor);--_bg:var(--pc-bg,transparent);
|
|
4
4
|
--_surface:var(--pc-surface,color-mix(in srgb,currentColor 6%,transparent));
|
|
@@ -55,4 +55,4 @@ import {e,g,f,b,k,c,d,a,o,l,i,h,n}from'./chunk-ATEH2U7F.js';import {b as b$1}fro
|
|
|
55
55
|
.pc-price-total{font-size:26px;font-weight:700}
|
|
56
56
|
.pc-price-note{color:var(--_muted);font-size:13px}
|
|
57
57
|
.pc-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
|
58
|
-
`;function U(){return jsx("style",{href:"product-configurator",precedence:"default",children:$e})}function be(n,e,t){let o=n%100===0;return new Intl.NumberFormat(e,{style:"currency",currency:t,minimumFractionDigits:o?0:2,maximumFractionDigits:o?0:2}).format(n/100)}function he(n,e){let{min:t,max:o,step:a}=e,l=s=>Math.round(s*1e6)/1e6,m=Math.round(n/a)*a;return m<t&&(m=Math.ceil(t/a)*a),m>o&&(m=Math.floor(o/a)*a),l(Math.min(o,Math.max(t,m)))}function ve(n){let{structure:e,configuration:t,quote:o,onSelectionChange:a,locale:l="nl-NL",currency:m="EUR",disabled:s=false,className:c}=n,u={...G,...n.labels},p=useId(),f=useRef({}),v=e.presets&&t.presets.length>0?e.presets.tab:null,d=e.tabs.filter(i=>i.key===v||t.parameters.some(b=>b.tab===i.key&&ye(b))),[y,x]=useState(d[0]?.key),k=d.find(i=>i.key===y)??d[0],M=i=>be(i,l,m),r=(i,b)=>{a?.({...t.selection,values:{...t.selection.values,[i]:b}});},g=i=>{let{preset:b,...h}=t.selection;a?.(i===""?h:{...h,preset:i});},w=(i,b)=>{let h=i.key==="ArrowRight"?1:i.key==="ArrowLeft"?-1:0,I=d.length-1,A=i.key==="Home"?0:i.key==="End"?I:(b+h+d.length)%d.length;if(h===0&&i.key!=="Home"&&i.key!=="End")return;i.preventDefault();let $=d[A];$&&(x($.key),f.current[$.key]?.focus());};return jsxs("div",{className:["pc-panel","pc-scope",c].filter(Boolean).join(" "),children:[jsx(U,{}),d.length>1?jsx("div",{role:"tablist","aria-label":u.tabsLabel,className:"pc-tabs",children:d.map((i,b)=>jsx("button",{ref:h=>{f.current[i.key]=h;},type:"button",role:"tab",id:`${p}-tab-${i.key}`,"aria-selected":i.key===k?.key,"aria-controls":`${p}-panel-${i.key}`,tabIndex:i.key===k?.key?0:-1,className:"pc-tab",onClick:()=>x(i.key),onKeyDown:h=>w(h,b),children:i.label},i.key))}):null,k?jsxs("fieldset",{role:d.length>1?"tabpanel":void 0,id:`${p}-panel-${k.key}`,"aria-labelledby":d.length>1?`${p}-tab-${k.key}`:void 0,className:"pc-fields",disabled:s,children:[e.presets&&v===k.key?jsxs("div",{className:"pc-field",children:[jsx("label",{className:"pc-label",htmlFor:`${p}-preset`,children:e.presets.label}),jsxs("select",{id:`${p}-preset`,className:"pc-control",value:t.preset?.code??"",onChange:i=>g(i.target.value),children:[e.presets.required?null:jsx("option",{value:"",children:u.presetNone}),t.presets.map(i=>jsx("option",{value:i.code,children:i.label},i.code))]})]}):null,t.parameters.filter(i=>ye(i)&&i.tab===k.key).map(i=>jsx(Ae,{id:`${p}-${i.key}`,parameter:i,labels:u,money:M,onChange:b=>r(i.key,b)},i.key))]}):null,jsx(ze,{quote:o,labels:u,money:M})]})}function Ae(n){let{id:e,parameter:t,labels:o,money:a,onChange:l}=n;if(t.kind==="number"){let s={min:t.min??0,max:t.max??0,step:t.step??1},c=t.value,u=t.unit==="count"?o.unitCount:o.unitMm,p=f=>{Number.isFinite(f)&&l(he(f,s));};return jsxs("div",{className:"pc-field",children:[jsxs("label",{className:"pc-label",htmlFor:`${e}-range`,children:[t.label,jsxs("small",{children:[c," ",u]})]}),jsxs("div",{className:"pc-number",children:[jsx("input",{id:`${e}-range`,className:"pc-control",type:"range",min:s.min,max:s.max,step:s.step,value:c,disabled:t.fixed,onChange:f=>p(f.target.valueAsNumber)}),jsxs("span",{children:[jsx("label",{className:"pc-hidden",htmlFor:`${e}-number`,children:`${t.label} (${u})`}),jsx("input",{id:`${e}-number`,className:"pc-control",type:"number",inputMode:"numeric",min:s.min,max:s.max,step:s.step,defaultValue:c,readOnly:t.fixed,onBlur:f=>p(f.target.valueAsNumber),onKeyDown:f=>{f.key==="Enter"&&p(f.currentTarget.valueAsNumber);}},c)]})]})]})}if(t.kind==="toggle")return jsx("div",{className:"pc-field",children:jsxs("label",{className:"pc-switch",htmlFor:e,children:[jsx("input",{id:e,type:"checkbox",checked:t.value===true,onChange:s=>l(s.target.checked)}),jsx("span",{className:"pc-label",children:t.label})]})});let m=t.options??[];if(t.kind==="material"){let s=m.find(c=>c.code===t.value);return jsxs("fieldset",{className:"pc-field",children:[jsxs("legend",{className:"pc-label",children:[t.label,s?jsx("small",{children:s.label}):null]}),jsx("div",{className:"pc-swatches",children:m.map(c=>jsxs("label",{className:"pc-choice",title:c.label,children:[jsx("input",{type:"radio",name:e,value:c.code,checked:c.code===t.value,onChange:()=>l(c.code)}),jsx("span",{className:"pc-swatch",style:{backgroundColor:c.material?.color,backgroundImage:c.material?.texture?`url("${c.material.texture}")`:void 0},children:jsx("span",{className:"pc-hidden",children:c.label})})]},c.code))})]})}return jsxs("fieldset",{className:"pc-field",children:[jsx("legend",{className:"pc-label",children:t.label}),jsx("div",{className:"pc-choices",children:m.map(s=>jsxs("label",{className:"pc-choice",children:[jsx("input",{type:"radio",name:e,value:s.code,checked:s.code===t.value,onChange:()=>l(s.code)}),jsxs("span",{children:[s.label,jsx("small",{children:De(s,o,a)})]})]},s.code))})]})}function De(n,e,t){return n.priceCents===null?e.optionOnRequest:n.priceCents>0?`${e.surchargePrefix} ${t(n.priceCents)}`:""}function ze(n){let{quote:e,labels:t,money:o}=n,a,l=null;return e?e.state==="open"||e.totalCents===null?a=t.priceOnRequest:e.state==="partial"?(a=`${t.priceFrom} ${o(e.totalCents)}`,l=`${t.priceInPartOnRequest}, ${t.priceIncludingTax}`):(a=o(e.totalCents),l=t.priceIncludingTax):a=t.priceUnavailable,jsxs("div",{className:"pc-price","aria-live":"polite","data-quote-state":e?.state??"none",children:[jsx("span",{className:"pc-price-heading",children:t.priceHeading}),jsx("span",{className:"pc-price-total",children:a}),l?jsx("span",{className:"pc-price-note",children:l}):null]})}function ye(n){return n.visible&&!n.settled}function xe(n){let{geometry:e,slots:t={},labels:o=G,className:a}=n,l=Ve(e),m=Math.max(l.width,l.height)/42,s=[...e.parts].sort((u,p)=>u.at[2]+u.size[2]-(p.at[2]+p.size[2])),c=u=>l.top-u;return jsxs("div",{className:["pc-sketch","pc-scope",a].filter(Boolean).join(" "),children:[jsx(U,{}),jsxs("svg",{role:"img","aria-label":o.sketchTitle,viewBox:`${l.left} 0 ${l.width} ${l.height}`,preserveAspectRatio:"xMidYMid meet",children:[jsx("title",{children:o.sketchTitle}),s.map(u=>jsx(Fe,{part:u,slot:t[u.slot],y:c},u.key)),e.dimensions.map(u=>jsx(Ue,{dimension:u,unit:o.unitMm,fontSize:m,y:c},u.id))]})]})}function Fe(n$1){let{part:e,slot:t,y:o}=n$1,[a,l]=e.size,m=e.at[0],s=o(e.at[1]+l),c=t?n(t):null,u={className:"pc-sketch-part",fill:t?.color??X,fillOpacity:c?.transparent?.35:1,"data-part":e.key};if(e.type==="niche_panel"){let f=b$1(e),v=o(e.at[1]+f.y+f.height),d=`M${m} ${s}h${a}v${l}h${-a}Z`,y=f.width>0&&f.height>0?`M${m+f.x} ${v}h${f.width}v${f.height}h${-f.width}Z`:"";return jsx("path",{...u,d:d+y,fillRule:"evenodd"})}let p=e.type==="stadium_prism"?Math.min(a,l)/2:0;return jsx("rect",{...u,x:m,y:s,width:a,height:l,rx:p,ry:p})}function Ue(n){let{dimension:e,unit:t,fontSize:o,y:a}=n,l=o*.5,m=`${e.label?`${e.label} `:""}${Be(e.length)} ${t}`,s=Math.min(e.from,e.to),c=Math.max(e.from,e.to);if(e.axis==="x"){let f=a(e.at);return jsxs("g",{"data-dimension":e.id,children:[jsx("path",{className:"pc-sketch-line",d:`M${s} ${f}H${c}M${s} ${f-l}v${l*2}M${c} ${f-l}v${l*2}`}),jsx("text",{className:"pc-sketch-text",x:(s+c)/2,y:f-l*1.4,fontSize:o,children:m})]})}let u=e.at,p=a((s+c)/2);return jsxs("g",{"data-dimension":e.id,children:[jsx("path",{className:"pc-sketch-line",d:`M${u} ${a(s)}V${a(c)}M${u-l} ${a(s)}h${l*2}M${u-l} ${a(c)}h${l*2}`}),jsx("text",{className:"pc-sketch-text",x:u-l*1.4,y:p,fontSize:o,transform:`rotate(-90 ${u-l*1.4} ${p})`,children:m})]})}function Be(n){return String(Math.round(n*10)/10)}function Ve(n){let e=1/0,t=-1/0,o=1/0,a=-1/0,l=(s,c,u,p)=>{e=Math.min(e,s),t=Math.max(t,c),o=Math.min(o,u),a=Math.max(a,p);};for(let s of n.parts)l(s.at[0],s.at[0]+s.size[0],s.at[1],s.at[1]+s.size[1]);for(let s of n.dimensions){let c=Math.min(s.from,s.to),u=Math.max(s.from,s.to);s.axis==="x"?l(c,u,s.at,s.at):l(s.at,s.at,c,u);}if(!Number.isFinite(e))return {left:0,top:1e3,width:1e3,height:1e3};let m=Math.max(t-e,a-o)*.08;return {left:e-m,top:a+m,width:t-e+m*2,height:a-o+m*2}}var Q;function Ce(){if(Q!==void 0)return Q;try{let n=document.createElement("canvas");Q=!!(n.getContext("webgl2")??n.getContext("webgl"));}catch{Q=false;}return Q}function ke(n){let{children:e,placeholder:t=null,minHeight:o=280,rootMargin:a="200px",className:l}=n,m=useRef(null),[s,c]=useState(false);return useEffect(()=>{let u=m.current;if(s||!u)return;if(typeof IntersectionObserver>"u"){c(true);return}let p=new IntersectionObserver(f=>{f.some(v=>v.isIntersecting)&&(c(true),p.disconnect());},{rootMargin:a});return p.observe(u),()=>p.disconnect()},[s,a]),jsx("div",{ref:m,className:l,style:s?void 0:{minHeight:o},children:s?e:t})}var Je=lazy(()=>import('./configurator-scene-3ABIR55G.js').then(n=>({default:n.ConfiguratorScene}))),Xe=()=>()=>{},et={values:{}};function te(n){let{structure:e,value:t,tax:o$1,initialSelection:a,onChange:l,locale:m,currency:s,interactive:c=true,className:u}=n,p=useMemo(()=>({...G,...n.labels}),[n.labels]),f=i=>W({structure:e,value:t,selection:i}).configuration.selection,[v,d]=useState(()=>f(a??et)),y=useSyncExternalStore(Xe,Ce,()=>null),x=useMemo(()=>V({structure:e,value:t,selection:v,tax:o$1}),[e,t,v,o$1]),{configuration:k}=x,M=x.ok?x.quote:null,r=useMemo(()=>ge(e.geometry,k.inputs,me(k)),[e.geometry,k]),g=i=>{let b=f(i),h=V({structure:e,value:t,selection:b,tax:o$1});d(b),l?.(b,h.ok?h.quote:null);},w=r.ok?jsx(xe,{geometry:r.geometry,slots:k.slots,labels:p}):jsx("p",{className:"pc-scene-note",children:p.sketchUnavailable});return jsxs("div",{className:["pc-root","pc-scope",u].filter(Boolean).join(" "),children:[jsx(U,{}),jsxs("div",{className:"pc-visual",children:[y&&r.ok?jsx(ke,{className:"pc-scene",placeholder:jsx("p",{className:"pc-scene-note",children:p.sceneLoading}),children:jsx(o,{fallback:null,children:jsx(Suspense,{fallback:jsx("p",{className:"pc-scene-note",children:p.sceneLoading}),children:jsx("div",{role:"region","aria-label":p.sceneLabel,style:{position:"absolute",inset:0},children:jsx(Je,{geometry:r.geometry,slots:k.slots,camera:e.geometry.camera,lighting:e.scene?.lighting,edges:e.scene?.edges,cameraLabels:p,interactive:c})})})})}):null,w]}),jsx(ve,{structure:e,configuration:k,quote:M,onSelectionChange:g,labels:p,locale:m,currency:s,disabled:!c})]})}var tt=e.number().int().min(0).max(1e8),Y=tt.nullable(),Re=e.strictObject({basePriceCents:Y,numbers:e.record(f,e.strictObject({min:e.number().finite(),max:e.number().finite(),default:e.number().finite()})).default({}),choices:e.record(f,e.strictObject({options:e.record(g,e.strictObject({priceCents:Y})),default:g.optional()})).default({}),materials:e.record(f,e.strictObject({offered:e.array(g).max(b.materials),default:g.optional()})).default({}),toggles:e.record(f,e.strictObject({offered:e.boolean(),default:e.boolean()})).default({}),priceGroups:e.record(g,Y).default({}),amounts:e.record(f,Y).default({}),presets:e.array(e.strictObject({code:g,label:e.string().trim().min(1).max(80),productId:e.string().uuid().nullable(),values:e.record(f,e.number().finite())})).max(b.presets).default([])});var q=class extends Error{constructor(t,o){super(o);this.reason=t;this.name="ConfiguratorFieldError";}};function ot(n,e){let t=n.fields[e];if(t==null)throw new q("missing",`The product has no public configurator under "${e}". Make the field public in the object builder.`);if(typeof t=="object"&&"configurator"in t&&!("structure"in t))throw new q("listing",`"${e}" is the listing's flag. Fetch the product itself (getProduct) to draw its configurator.`);let o=t,a=k.safeParse(o.structure),l=Re.safeParse(o.value),m=o.tax;if(!a.success||!l.success||typeof o.structureHash!="string"||typeof o.valueHash!="string"||typeof m?.taxPercentage!="number"||typeof m.pricesIncludeTax!="boolean")throw new q("invalid",`"${e}" does not read as a configurator. Update @solumflow-app/product-configurator.`);return {structure:a.data,value:l.data,tax:{taxPercentage:m.taxPercentage,pricesIncludeTax:m.pricesIncludeTax},structureHash:o.structureHash,valueHash:o.valueHash}}function we(n,e,t,o){return {attribute:n,selection:t,structureHash:e,expected:{state:o.state,totalCents:o.totalCents}}}var at="Aanvragen";function fn(n){let{product:e,attribute:t,onChange:o,onSubmit:a}=n,l=useMemo(()=>ot(e,t),[e,t]),[m,s]=useState(()=>{let u=n.initialSelection??{values:{}},p=V({structure:l.structure,value:l.value,selection:u,tax:l.tax});return {selection:p.configuration.selection,quote:p.ok?p.quote:null}}),c=m.quote?we(t,l.structureHash,m.selection,m.quote):null;return jsxs("div",{className:n.className,children:[jsx(te,{structure:l.structure,value:l.value,tax:l.tax,initialSelection:n.initialSelection,labels:n.labels,locale:n.locale,currency:n.currency,onChange:(u,p)=>{s({selection:u,quote:p}),o?.({selection:u,quote:p,payload:p?we(t,l.structureHash,u,p):null});}}),a?jsx("button",{type:"button",className:"pc-submit",disabled:c===null,onClick:()=>{c&&a(c);},children:n.submitLabel??at}):null]})}export{q as ConfiguratorFieldError,G as DEFAULT_CONFIGURATOR_LABELS,fn as ProductConfigurator,ot as readConfigurator,we as toConfigurationPayload};
|
|
58
|
+
`;function U(){return jsx("style",{href:"product-configurator",precedence:"default",children:Te})}function be(n,e,t){let o=n%100===0;return new Intl.NumberFormat(e,{style:"currency",currency:t,minimumFractionDigits:o?0:2,maximumFractionDigits:o?0:2}).format(n/100)}function he(n,e){let{min:t,max:o,step:r}=e,l=s=>Math.round(s*1e6)/1e6,m=Math.round(n/r)*r;return m<t&&(m=Math.ceil(t/r)*r),m>o&&(m=Math.floor(o/r)*r),l(Math.min(o,Math.max(t,m)))}function ve(n){let{structure:e,configuration:t,quote:o,onSelectionChange:r,locale:l="nl-NL",currency:m="EUR",disabled:s=false,className:u}=n,c={...G,...n.labels},p=useId(),f=useRef({}),v=e.presets&&t.presets.length>0?e.presets.tab:null,d=e.tabs.filter(i=>i.key===v||t.parameters.some(b=>b.tab===i.key&&ye(b))),[y,x]=useState(d[0]?.key),S=d.find(i=>i.key===y)??d[0],M=i=>be(i,l,m),a=(i,b)=>{r?.({...t.selection,values:{...t.selection.values,[i]:b}});},g=i=>{let{preset:b,...h}=t.selection;r?.(i===""?h:{...h,preset:i});},N=(i,b)=>{let h=i.key==="ArrowRight"?1:i.key==="ArrowLeft"?-1:0,I=d.length-1,A=i.key==="Home"?0:i.key==="End"?I:(b+h+d.length)%d.length;if(h===0&&i.key!=="Home"&&i.key!=="End")return;i.preventDefault();let L=d[A];L&&(x(L.key),f.current[L.key]?.focus());};return jsxs("div",{className:["pc-panel","pc-scope",u].filter(Boolean).join(" "),children:[jsx(U,{}),d.length>1?jsx("div",{role:"tablist","aria-label":c.tabsLabel,className:"pc-tabs",children:d.map((i,b)=>jsx("button",{ref:h=>{f.current[i.key]=h;},type:"button",role:"tab",id:`${p}-tab-${i.key}`,"aria-selected":i.key===S?.key,"aria-controls":`${p}-panel-${i.key}`,tabIndex:i.key===S?.key?0:-1,className:"pc-tab",onClick:()=>x(i.key),onKeyDown:h=>N(h,b),children:i.label},i.key))}):null,S?jsxs("fieldset",{role:d.length>1?"tabpanel":void 0,id:`${p}-panel-${S.key}`,"aria-labelledby":d.length>1?`${p}-tab-${S.key}`:void 0,className:"pc-fields",disabled:s,children:[e.presets&&v===S.key?jsxs("div",{className:"pc-field",children:[jsx("label",{className:"pc-label",htmlFor:`${p}-preset`,children:e.presets.label}),jsxs("select",{id:`${p}-preset`,className:"pc-control",value:t.preset?.code??"",onChange:i=>g(i.target.value),children:[e.presets.required?null:jsx("option",{value:"",children:c.presetNone}),t.presets.map(i=>jsx("option",{value:i.code,children:i.label},i.code))]})]}):null,t.parameters.filter(i=>ye(i)&&i.tab===S.key).map(i=>Ce(i)?jsx(ze,{id:`${p}-${i.key}`,parameter:i,labels:c,money:M,onChange:b=>a(i.key,b)},i.key):jsx(Fe,{parameter:i,labels:c,money:M},i.key))]}):null,jsx(Ue,{quote:o,labels:c,money:M})]})}function ze(n){let{id:e,parameter:t,labels:o,money:r,onChange:l}=n;if(t.kind==="number"){let s={min:t.min??0,max:t.max??0,step:t.step??1},u=t.value,c=t.unit==="count"?o.unitCount:o.unitMm,p=f=>{Number.isFinite(f)&&l(he(f,s));};return jsxs("div",{className:"pc-field",children:[jsxs("label",{className:"pc-label",htmlFor:`${e}-range`,children:[t.label,jsxs("small",{children:[u," ",c]})]}),jsxs("div",{className:"pc-number",children:[jsx("input",{id:`${e}-range`,className:"pc-control",type:"range",min:s.min,max:s.max,step:s.step,value:u,disabled:t.fixed,onChange:f=>p(f.target.valueAsNumber)}),jsxs("span",{children:[jsx("label",{className:"pc-hidden",htmlFor:`${e}-number`,children:`${t.label} (${c})`}),jsx("input",{id:`${e}-number`,className:"pc-control",type:"number",inputMode:"numeric",min:s.min,max:s.max,step:s.step,defaultValue:u,readOnly:t.fixed,onBlur:f=>p(f.target.valueAsNumber),onKeyDown:f=>{f.key==="Enter"&&p(f.currentTarget.valueAsNumber);}},u)]})]})]})}if(t.kind==="toggle")return jsx("div",{className:"pc-field",children:jsxs("label",{className:"pc-switch",htmlFor:e,children:[jsx("input",{id:e,type:"checkbox",checked:t.value===true,onChange:s=>l(s.target.checked)}),jsx("span",{className:"pc-label",children:t.label})]})});let m=t.options??[];if(t.kind==="material"){let s=m.find(u=>u.code===t.value);return jsxs("fieldset",{className:"pc-field",children:[jsxs("legend",{className:"pc-label",children:[t.label,s?jsx("small",{children:s.label}):null]}),jsx("div",{className:"pc-swatches",children:m.map(u=>jsxs("label",{className:"pc-choice",title:u.label,children:[jsx("input",{type:"radio",name:e,value:u.code,checked:u.code===t.value,onChange:()=>l(u.code)}),jsx("span",{className:"pc-swatch",style:{backgroundColor:u.material?.color,backgroundImage:u.material?.texture?`url("${u.material.texture}")`:void 0},children:jsx("span",{className:"pc-hidden",children:u.label})})]},u.code))})]})}return jsxs("fieldset",{className:"pc-field",children:[jsx("legend",{className:"pc-label",children:t.label}),jsx("div",{className:"pc-choices",children:m.map(s=>jsxs("label",{className:"pc-choice",children:[jsx("input",{type:"radio",name:e,value:s.code,checked:s.code===t.value,onChange:()=>l(s.code)}),jsxs("span",{children:[s.label,jsx("small",{children:xe(s,o,r)})]})]},s.code))})]})}function Fe(n){let{parameter:e,labels:t,money:o}=n,r=e.options?.[0];return r?jsx("div",{className:"pc-field","data-pinned":e.key,children:jsxs("span",{className:"pc-label",children:[e.label,jsxs("small",{children:[r.label," \xB7 ",xe(r,t,o)]})]})}):null}function xe(n,e,t){return n.priceCents===null?e.optionOnRequest:n.priceCents>0?`${e.surchargePrefix} ${t(n.priceCents)}`:""}function Ue(n){let{quote:e,labels:t,money:o}=n,r,l=null;return e?e.state==="open"||e.totalCents===null?r=t.priceOnRequest:e.state==="partial"?(r=`${t.priceFrom} ${o(e.totalCents)}`,l=`${t.priceInPartOnRequest}, ${t.priceIncludingTax}`):(r=o(e.totalCents),l=t.priceIncludingTax):r=t.priceUnavailable,jsxs("div",{className:"pc-price","aria-live":"polite","data-quote-state":e?.state??"none",children:[jsx("span",{className:"pc-price-heading",children:t.priceHeading}),jsx("span",{className:"pc-price-total",children:r}),l?jsx("span",{className:"pc-price-note",children:l}):null]})}function Ce(n){return n.visible&&!n.settled}function ye(n){if(Ce(n))return true;let e=n.options??[],t=e.length===1?e[0]:void 0;return n.visible&&t!==void 0&&(t.priceCents===null||t.priceCents>0)}function ke(n){let{geometry:e,slots:t={},labels:o=G,className:r}=n,l=qe(e),m=Math.max(l.width,l.height)/42,s=[...e.parts].sort((c,p)=>c.at[2]+c.size[2]-(p.at[2]+p.size[2])),u=c=>l.top-c;return jsxs("div",{className:["pc-sketch","pc-scope",r].filter(Boolean).join(" "),children:[jsx(U,{}),jsxs("svg",{role:"img","aria-label":o.sketchTitle,viewBox:`${l.left} 0 ${l.width} ${l.height}`,preserveAspectRatio:"xMidYMid meet",children:[jsx("title",{children:o.sketchTitle}),s.map(c=>jsx(Be,{part:c,slot:t[c.slot],y:u},c.key)),e.dimensions.map(c=>jsx(Ve,{dimension:c,unit:o.unitMm,fontSize:m,y:u},c.id))]})]})}function Be(n$1){let{part:e,slot:t,y:o}=n$1,[r,l]=e.size,m=e.at[0],s=o(e.at[1]+l),u=t?n(t):null,c={className:"pc-sketch-part",fill:t?.color??X,fillOpacity:u?.transparent?.35:1,"data-part":e.key};if(e.type==="niche_panel"){let f=b$1(e),v=o(e.at[1]+f.y+f.height),d=`M${m} ${s}h${r}v${l}h${-r}Z`,y=f.width>0&&f.height>0?`M${m+f.x} ${v}h${f.width}v${f.height}h${-f.width}Z`:"";return jsx("path",{...c,d:d+y,fillRule:"evenodd"})}let p=e.type==="stadium_prism"?Math.min(r,l)/2:0;return jsx("rect",{...c,x:m,y:s,width:r,height:l,rx:p,ry:p})}function Ve(n){let{dimension:e,unit:t,fontSize:o,y:r}=n,l=o*.5,m=`${e.label?`${e.label} `:""}${Qe(e.length)} ${t}`,s=Math.min(e.from,e.to),u=Math.max(e.from,e.to);if(e.axis==="x"){let f=r(e.at);return jsxs("g",{"data-dimension":e.id,children:[jsx("path",{className:"pc-sketch-line",d:`M${s} ${f}H${u}M${s} ${f-l}v${l*2}M${u} ${f-l}v${l*2}`}),jsx("text",{className:"pc-sketch-text",x:(s+u)/2,y:f-l*1.4,fontSize:o,children:m})]})}let c=e.at,p=r((s+u)/2);return jsxs("g",{"data-dimension":e.id,children:[jsx("path",{className:"pc-sketch-line",d:`M${c} ${r(s)}V${r(u)}M${c-l} ${r(s)}h${l*2}M${c-l} ${r(u)}h${l*2}`}),jsx("text",{className:"pc-sketch-text",x:c-l*1.4,y:p,fontSize:o,transform:`rotate(-90 ${c-l*1.4} ${p})`,children:m})]})}function Qe(n){return String(Math.round(n*10)/10)}function qe(n){let e=1/0,t=-1/0,o=1/0,r=-1/0,l=(s,u,c,p)=>{e=Math.min(e,s),t=Math.max(t,u),o=Math.min(o,c),r=Math.max(r,p);};for(let s of n.parts)l(s.at[0],s.at[0]+s.size[0],s.at[1],s.at[1]+s.size[1]);for(let s of n.dimensions){let u=Math.min(s.from,s.to),c=Math.max(s.from,s.to);s.axis==="x"?l(u,c,s.at,s.at):l(s.at,s.at,u,c);}if(!Number.isFinite(e))return {left:0,top:1e3,width:1e3,height:1e3};let m=Math.max(t-e,r-o)*.08;return {left:e-m,top:r+m,width:t-e+m*2,height:r-o+m*2}}var Q;function Se(){if(Q!==void 0)return Q;try{let n=document.createElement("canvas");Q=!!(n.getContext("webgl2")??n.getContext("webgl"));}catch{Q=false;}return Q}function Re(n){let{children:e,placeholder:t=null,minHeight:o=280,rootMargin:r="200px",className:l}=n,m=useRef(null),[s,u]=useState(false);return useEffect(()=>{let c=m.current;if(s||!c)return;if(typeof IntersectionObserver>"u"){u(true);return}let p=new IntersectionObserver(f=>{f.some(v=>v.isIntersecting)&&(u(true),p.disconnect());},{rootMargin:r});return p.observe(c),()=>p.disconnect()},[s,r]),jsx("div",{ref:m,className:l,style:s?void 0:{minHeight:o},children:s?e:t})}var et=lazy(()=>import('./configurator-scene-3ABIR55G.js').then(n=>({default:n.ConfiguratorScene}))),tt=()=>()=>{},nt={values:{}};function te(n){let{structure:e,value:t,tax:o$1,initialSelection:r,onChange:l,locale:m,currency:s,interactive:u=true,className:c}=n,p=useMemo(()=>({...G,...n.labels}),[n.labels]),f=i=>W({structure:e,value:t,selection:i}).configuration.selection,[v,d]=useState(()=>f(r??nt)),y=useSyncExternalStore(tt,Se,()=>null),x=useMemo(()=>V({structure:e,value:t,selection:v,tax:o$1}),[e,t,v,o$1]),{configuration:S}=x,M=x.ok?x.quote:null,a=useMemo(()=>ge(e.geometry,S.inputs,me(S)),[e.geometry,S]),g=i=>{let b=f(i),h=V({structure:e,value:t,selection:b,tax:o$1});d(b),l?.(b,h.ok?h.quote:null);},N=a.ok?jsx(ke,{geometry:a.geometry,slots:S.slots,labels:p}):jsx("p",{className:"pc-scene-note",children:p.sketchUnavailable});return jsxs("div",{className:["pc-root","pc-scope",c].filter(Boolean).join(" "),children:[jsx(U,{}),jsxs("div",{className:"pc-visual",children:[y&&a.ok?jsx(Re,{className:"pc-scene",placeholder:jsx("p",{className:"pc-scene-note",children:p.sceneLoading}),children:jsx(o,{fallback:null,children:jsx(Suspense,{fallback:jsx("p",{className:"pc-scene-note",children:p.sceneLoading}),children:jsx("div",{role:"region","aria-label":p.sceneLabel,style:{position:"absolute",inset:0},children:jsx(et,{geometry:a.geometry,slots:S.slots,camera:e.geometry.camera,lighting:e.scene?.lighting,edges:e.scene?.edges,cameraLabels:p,interactive:u})})})})}):null,N]}),jsx(ve,{structure:e,configuration:S,quote:M,onSelectionChange:g,labels:p,locale:m,currency:s,disabled:!u})]})}var rt=e.number().int().min(0).max(1e8),Y=rt.nullable(),Ne=e.strictObject({basePriceCents:Y,numbers:e.record(f,e.strictObject({min:e.number().finite(),max:e.number().finite(),default:e.number().finite()})).default({}),choices:e.record(f,e.strictObject({options:e.record(g,e.strictObject({priceCents:Y})),default:g.optional()})).default({}),materials:e.record(f,e.strictObject({offered:e.array(g).max(b.materials),default:g.optional()})).default({}),toggles:e.record(f,e.strictObject({offered:e.boolean(),default:e.boolean()})).default({}),priceGroups:e.record(g,Y).default({}),amounts:e.record(f,Y).default({}),presets:e.array(e.strictObject({code:g,label:e.string().trim().min(1).max(80),productId:e.string().uuid().nullable(),values:e.record(f,e.number().finite())})).max(b.presets).default([])});var q=class extends Error{constructor(t,o){super(o);this.reason=t;this.name="ConfiguratorFieldError";}};function it(n,e){let t=n.fields[e];if(t==null)throw new q("missing",`The product has no public configurator under "${e}". Make the field public in the object builder.`);if(typeof t=="object"&&"configurator"in t&&!("structure"in t))throw new q("listing",`"${e}" is the listing's flag. Fetch the product itself (getProduct) to draw its configurator.`);let o=t,r=k.safeParse(o.structure),l=Ne.safeParse(o.value),m=o.tax;if(!r.success||!l.success||typeof o.structureHash!="string"||typeof o.valueHash!="string"||typeof m?.taxPercentage!="number"||typeof m.pricesIncludeTax!="boolean")throw new q("invalid",`"${e}" does not read as a configurator. Update @solumflow-app/product-configurator.`);return {structure:r.data,value:l.data,tax:{taxPercentage:m.taxPercentage,pricesIncludeTax:m.pricesIncludeTax},structureHash:o.structureHash,valueHash:o.valueHash}}function Pe(n,e,t,o){return {attribute:n,selection:t,structureHash:e,expected:{state:o.state,totalCents:o.totalCents}}}var st="Aanvragen";function bn(n){let{product:e,attribute:t,onChange:o,onSubmit:r}=n,l=useMemo(()=>it(e,t),[e,t]),[m,s]=useState(()=>{let c=n.initialSelection??{values:{}},p=V({structure:l.structure,value:l.value,selection:c,tax:l.tax});return {selection:p.configuration.selection,quote:p.ok?p.quote:null}}),u=m.quote?Pe(t,l.structureHash,m.selection,m.quote):null;return jsxs("div",{className:n.className,children:[jsx(te,{structure:l.structure,value:l.value,tax:l.tax,initialSelection:n.initialSelection,labels:n.labels,locale:n.locale,currency:n.currency,onChange:(c,p)=>{s({selection:c,quote:p}),o?.({selection:c,quote:p,payload:p?Pe(t,l.structureHash,c,p):null});}}),r?jsx("button",{type:"button",className:"pc-submit",disabled:u===null,onClick:()=>{u&&r(u);},children:n.submitLabel??st}):null]})}export{q as ConfiguratorFieldError,G as DEFAULT_CONFIGURATOR_LABELS,bn as ProductConfigurator,it as readConfigurator,Pe as toConfigurationPayload};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solumflow-app/product-configurator",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Show a product's 3D configurator on your own website and send what a visitor put together back as a request or an order.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|