diff --git a/404.html b/404.html new file mode 100644 index 00000000..38d88831 --- /dev/null +++ b/404.html @@ -0,0 +1,43 @@ + + +
+ + + + + +U?Ke(d,E,T,!0,!1,Q):b(p,_,P,E,T,B,j,F,Q)},He=(d,p,_,P,E,T,B,j,F)=>{let A=0;const U=p.length;let Q=d.length-1,Y=U-1;for(;A<=Q&&A<=Y;){const se=d[A],ae=p[A]=F?At(p[A]):tt(p[A]);if(Qt(se,ae))S(se,ae,_,null,E,T,B,j,F);else break;A++}for(;A<=Q&&A<=Y;){const se=d[Q],ae=p[Y]=F?At(p[Y]):tt(p[Y]);if(Qt(se,ae))S(se,ae,_,null,E,T,B,j,F);else break;Q--,Y--}if(A>Q){if(A<=Y){const se=Y+1,ae=seY)for(;A<=Q;)Fe(d[A],E,T,!0),A++;else{const se=A,ae=A,ye=new Map;for(A=ae;A<=Y;A++){const Je=p[A]=F?At(p[A]):tt(p[A]);Je.key!=null&&ye.set(Je.key,A)}let he,Oe=0;const st=Y-ae+1;let on=!1,hs=0;const Pn=new Array(st);for(A=0;A=st){Fe(Je,E,T,!0);continue}let ut;if(Je.key!=null)ut=ye.get(Je.key);else for(he=ae;he<=Y;he++)if(Pn[he-ae]===0&&Qt(Je,p[he])){ut=he;break}ut===void 0?Fe(Je,E,T,!0):(Pn[ut-ae]=A+1,ut>=hs?hs=ut:on=!0,S(Je,p[ut],_,null,E,T,B,j,F),Oe++)}const ms=on?ou(Pn):cn;for(he=ms.length-1,A=st-1;A>=0;A--){const Je=ae+A,ut=p[Je],gs=Je+1{const{el:T,type:B,transition:j,children:F,shapeFlag:A}=d;if(A&6){We(d.component.subTree,p,_,P);return}if(A&128){d.suspense.move(p,_,P);return}if(A&64){B.move(d,p,_,G);return}if(B===_e){r(T,p,_);for(let Q=0;Q j.enter(T),E);else{const{leave:Q,delayLeave:Y,afterLeave:se}=j,ae=()=>r(T,p,_),ye=()=>{Q(T,()=>{ae(),se&&se()})};Y?Y(T,ae,ye):ye()}else r(T,p,_)},Fe=(d,p,_,P=!1,E=!1)=>{const{type:T,props:B,ref:j,children:F,dynamicChildren:A,shapeFlag:U,patchFlag:Q,dirs:Y,cacheIndex:se}=d;if(Q===-2&&(E=!1),j!=null&&Ar(j,null,_,d,!0),se!=null&&(p.renderCache[se]=void 0),U&256){p.ctx.deactivate(d);return}const ae=U&1&&Y,ye=!hn(d);let he;if(ye&&(he=B&&B.onVnodeBeforeUnmount)&&et(he,p,d),U&6)ct(d.component,_,P);else{if(U&128){d.suspense.unmount(_,P);return}ae&&ft(d,null,p,"beforeUnmount"),U&64?d.type.remove(d,p,_,G,P):A&&!A.hasOnce&&(T!==_e||Q>0&&Q&64)?Ke(A,p,_,!1,!0):(T===_e&&Q&384||!E&&U&16)&&Ke(F,p,_),P&&kt(d)}(ye&&(he=B&&B.onVnodeUnmounted)||ae)&&Ue(()=>{he&&et(he,p,d),ae&&ft(d,null,p,"unmounted")},_)},kt=d=>{const{type:p,el:_,anchor:P,transition:E}=d;if(p===_e){St(_,P);return}if(p===Nn){k(d);return}const T=()=>{o(_),E&&!E.persisted&&E.afterLeave&&E.afterLeave()};if(d.shapeFlag&1&&E&&!E.persisted){const{leave:B,delayLeave:j}=E,F=()=>B(_,T);j?j(d.el,T,F):F()}else T()},St=(d,p)=>{let _;for(;d!==p;)_=h(d),o(d),d=_;o(p)},ct=(d,p,_)=>{const{bum:P,scope:E,update:T,subTree:B,um:j,m:F,a:A}=d;Ms(F),Ms(A),P&&eo(P),E.stop(),T&&(T.active=!1,Fe(B,d,p,_)),j&&Ue(j,p),Ue(()=>{d.isUnmounted=!0},p),p&&p.pendingBranch&&!p.isUnmounted&&d.asyncDep&&!d.asyncResolved&&d.suspenseId===p.pendingId&&(p.deps--,p.deps===0&&p.resolve())},Ke=(d,p,_,P=!1,E=!1,T=0)=>{for(let B=T;B {if(d.shapeFlag&6)return C(d.component.subTree);if(d.shapeFlag&128)return d.suspense.next();const p=h(d.anchor||d.el),_=p&&p[Qc];return _?h(_):p};let W=!1;const V=(d,p,_)=>{d==null?p._vnode&&Fe(p._vnode,null,null,!0):S(p._vnode||null,d,p,null,null,null,_),p._vnode=d,W||(W=!0,xs(),Er(),W=!1)},G={p:S,um:Fe,m:We,r:kt,mt:X,mc:b,pc:q,pbc:x,n:C,o:e};let ce,ge;return t&&([ce,ge]=t(G)),{render:V,hydrate:ce,createApp:Wc(V,ce)}}function so({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function Kt({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function El(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function xl(e,t,n=!1){const r=e.children,o=t.children;if(ee(r)&&ee(o))for(let s=0;s >1,e[n[l]] 0&&(t[r]=n[s-1]),n[s]=r)}}for(s=n.length,i=n[s-1];s-- >0;)n[s]=i,i=t[i];return n}function Cl(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:Cl(t)}function Ms(e){if(e)for(let t=0;t Ve(su);function lu(e,t){return Qo(e,null,t)}const pr={};function Ne(e,t,n){return Qo(e,t,n)}function Qo(e,t,{immediate:n,deep:r,flush:o,once:s,onTrack:i,onTrigger:l}=we){if(t&&s){const M=t;t=(...$)=>{M(...$),N()}}const a=Re,c=M=>r===!0?M:Ot(M,r===!1?1:void 0);let f,u=!1,h=!1;if(je(e)?(f=()=>e.value,u=_n(e)):dn(e)?(f=()=>c(e),u=!0):ee(e)?(h=!0,u=e.some(M=>dn(M)||_n(M)),f=()=>e.map(M=>{if(je(M))return M.value;if(dn(M))return c(M);if(re(M))return It(M,a,2)})):re(e)?t?f=()=>It(e,a,2):f=()=>(g&&g(),rt(e,a,3,[v])):f=nt,t&&r){const M=f;f=()=>Ot(M())}let g,v=M=>{g=m.onStop=()=>{It(M,a,4),g=m.onStop=void 0}},S;if(rr)if(v=nt,t?n&&rt(t,a,3,[f(),h?[]:void 0,v]):f(),o==="sync"){const M=iu();S=M.__watcherHandles||(M.__watcherHandles=[])}else return nt;let L=h?new Array(e.length).fill(pr):pr;const O=()=>{if(!(!m.active||!m.dirty))if(t){const M=m.run();(r||u||(h?M.some(($,b)=>$t($,L[b])):$t(M,L)))&&(g&&g(),rt(t,a,3,[M,L===pr?void 0:h&&L[0]===pr?[]:L,v]),L=M)}else m.run()};O.allowRecurse=!!t;let w;o==="sync"?w=O:o==="post"?w=()=>Ue(O,a&&a.suspense):(O.pre=!0,a&&(O.id=a.uid),w=()=>jr(O));const m=new jo(f,nt,w),k=Fi(),N=()=>{m.stop(),k&&Ho(k.effects,m)};return t?n?O():L=m.run():o==="post"?Ue(m.run.bind(m),a&&a.suspense):m.run(),S&&S.push(N),N}function au(e,t,n){const r=this.proxy,o=Ce(e)?e.includes(".")?Ll(r,e):()=>r[e]:e.bind(r,r);let s;re(t)?s=t:(s=t.handler,n=t);const i=nr(this),l=Qo(o,s.bind(r),n);return i(),l}function Ll(e,t){const n=t.split(".");return()=>{let r=e;for(let o=0;o {Ot(r,t,n)});else if(Ii(e)){for(const r in e)Ot(e[r],t,n);for(const r of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,r)&&Ot(e[r],t,n)}return e}const cu=(e,t)=>t==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${Xe(t)}Modifiers`]||e[`${Vt(t)}Modifiers`];function uu(e,t,...n){if(e.isUnmounted)return;const r=e.vnode.props||we;let o=n;const s=t.startsWith("update:"),i=s&&cu(r,t.slice(7));i&&(i.trim&&(o=n.map(f=>Ce(f)?f.trim():f)),i.number&&(o=n.map(Pa)));let l,a=r[l=Zr(t)]||r[l=Zr(Xe(t))];!a&&s&&(a=r[l=Zr(Vt(t))]),a&&rt(a,e,6,o);const c=r[l+"Once"];if(c){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,rt(c,e,6,o)}}function Pl(e,t,n=!1){const r=t.emitsCache,o=r.get(e);if(o!==void 0)return o;const s=e.emits;let i={},l=!1;if(!re(e)){const a=c=>{const f=Pl(c,t,!0);f&&(l=!0,Me(i,f))};!n&&t.mixins.length&&t.mixins.forEach(a),e.extends&&a(e.extends),e.mixins&&e.mixins.forEach(a)}return!s&&!l?(be(e)&&r.set(e,null),null):(ee(s)?s.forEach(a=>i[a]=null):Me(i,s),be(e)&&r.set(e,i),i)}function zr(e,t){return!e||!Yn(t)?!1:(t=t.slice(2).replace(/Once$/,""),fe(e,t[0].toLowerCase()+t.slice(1))||fe(e,Vt(t))||fe(e,t))}function io(e){const{type:t,vnode:n,proxy:r,withProxy:o,propsOptions:[s],slots:i,attrs:l,emit:a,render:c,renderCache:f,props:u,data:h,setupState:g,ctx:v,inheritAttrs:S}=e,L=xr(e);let O,w;try{if(n.shapeFlag&4){const k=o||r,N=k;O=tt(c.call(N,k,f,u,g,h,v)),w=l}else{const k=t;O=tt(k.length>1?k(u,{attrs:l,slots:i,emit:a}):k(u,null)),w=t.props?l:fu(l)}}catch(k){jn.length=0,er(k,e,1),O=oe(ze)}let m=O;if(w&&S!==!1){const k=Object.keys(w),{shapeFlag:N}=m;k.length&&N&7&&(s&&k.some($o)&&(w=du(w,s)),m=Nt(m,w,!1,!0))}return n.dirs&&(m=Nt(m,null,!1,!0),m.dirs=m.dirs?m.dirs.concat(n.dirs):n.dirs),n.transition&&(m.transition=n.transition),O=m,xr(L),O}const fu=e=>{let t;for(const n in e)(n==="class"||n==="style"||Yn(n))&&((t||(t={}))[n]=e[n]);return t},du=(e,t)=>{const n={};for(const r in e)(!$o(r)||!(r.slice(9)in t))&&(n[r]=e[r]);return n};function pu(e,t,n){const{props:r,children:o,component:s}=e,{props:i,children:l,patchFlag:a}=t,c=s.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&a>=0){if(a&1024)return!0;if(a&16)return r?$s(r,i,c):!!i;if(a&8){const f=t.dynamicProps;for(let u=0;u e.__isSuspense;function Al(e,t){t&&t.pendingBranch?ee(e)?t.effects.push(...e):t.effects.push(e):vc(e)}const _e=Symbol.for("v-fgt"),nn=Symbol.for("v-txt"),ze=Symbol.for("v-cmt"),Nn=Symbol.for("v-stc"),jn=[];let Qe=null;function K(e=!1){jn.push(Qe=e?null:[])}function gu(){jn.pop(),Qe=jn[jn.length-1]||null}let Wn=1;function Hs(e){Wn+=e,e<0&&Qe&&(Qe.hasOnce=!0)}function Tl(e){return e.dynamicChildren=Wn>0?Qe||cn:null,gu(),Wn>0&&Qe&&Qe.push(e),e}function Z(e,t,n,r,o,s){return Tl(ne(e,t,n,r,o,s,!0))}function xe(e,t,n,r,o){return Tl(oe(e,t,n,r,o,!0))}function Tr(e){return e?e.__v_isVNode===!0:!1}function Qt(e,t){return e.type===t.type&&e.key===t.key}const Ol=({key:e})=>e??null,br=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?Ce(e)||je(e)||re(e)?{i:Ie,r:e,k:t,f:!!n}:e:null);function ne(e,t=null,n=null,r=0,o=null,s=e===_e?0:1,i=!1,l=!1){const a={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Ol(t),ref:t&&br(t),scopeId:Fr,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:s,patchFlag:r,dynamicProps:o,dynamicChildren:null,appContext:null,ctx:Ie};return l?(Xo(a,n),s&128&&e.normalize(a)):n&&(a.shapeFlag|=Ce(n)?8:16),Wn>0&&!i&&Qe&&(a.patchFlag>0||s&6)&&a.patchFlag!==32&&Qe.push(a),a}const oe=vu;function vu(e,t=null,n=null,r=0,o=null,s=!1){if((!e||e===$c)&&(e=ze),Tr(e)){const l=Nt(e,t,!0);return n&&Xo(l,n),Wn>0&&!s&&Qe&&(l.shapeFlag&6?Qe[Qe.indexOf(e)]=l:Qe.push(l)),l.patchFlag=-2,l}if(Au(e)&&(e=e.__vccOpts),t){t=_u(t);let{class:l,style:a}=t;l&&!Ce(l)&&(t.class=qe(l)),be(a)&&(Xi(a)&&!ee(a)&&(a=Me({},a)),t.style=Xn(a))}const i=Ce(e)?1:mu(e)?128:Xc(e)?64:be(e)?4:re(e)?2:0;return ne(e,t,n,r,o,i,s,!0)}function _u(e){return e?Xi(e)||vl(e)?Me({},e):e:null}function Nt(e,t,n=!1,r=!1){const{props:o,ref:s,patchFlag:i,children:l,transition:a}=e,c=t?yu(o||{},t):o,f={__v_isVNode:!0,__v_skip:!0,type:e.type,props:c,key:c&&Ol(c),ref:t&&t.ref?n&&s?ee(s)?s.concat(br(t)):[s,br(t)]:br(t):s,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:l,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==_e?i===-1?16:i|16:i,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:a,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Nt(e.ssContent),ssFallback:e.ssFallback&&Nt(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return a&&r&&Lr(f,a.clone(f)),f}function jt(e=" ",t=0){return oe(nn,null,e,t)}function bu(e,t){const n=oe(Nn,null,e);return n.staticCount=t,n}function Te(e="",t=!1){return t?(K(),xe(ze,null,e)):oe(ze,null,e)}function tt(e){return e==null||typeof e=="boolean"?oe(ze):ee(e)?oe(_e,null,e.slice()):typeof e=="object"?At(e):oe(nn,null,String(e))}function At(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:Nt(e)}function Xo(e,t){let n=0;const{shapeFlag:r}=e;if(t==null)t=null;else if(ee(t))n=16;else if(typeof t=="object")if(r&65){const o=t.default;o&&(o._c&&(o._d=!1),Xo(e,o()),o._c&&(o._d=!0));return}else{n=32;const o=t._;!o&&!vl(t)?t._ctx=Ie:o===3&&Ie&&(Ie.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else re(t)?(t={default:t,_ctx:Ie},n=32):(t=String(t),r&64?(n=16,t=[jt(t)]):n=8);e.children=t,e.shapeFlag|=n}function yu(...e){const t={};for(let n=0;n Re||Ie;let Or,Lo;{const e=$i(),t=(n,r)=>{let o;return(o=e[n])||(o=e[n]=[]),o.push(r),s=>{o.length>1?o.forEach(i=>i(s)):o[0](s)}};Or=t("__VUE_INSTANCE_SETTERS__",n=>Re=n),Lo=t("__VUE_SSR_SETTERS__",n=>rr=n)}const nr=e=>{const t=Re;return Or(e),e.scope.on(),()=>{e.scope.off(),Or(t)}},Ns=()=>{Re&&Re.scope.off(),Or(null)};function Rl(e){return e.vnode.shapeFlag&4}let rr=!1;function Eu(e,t=!1,n=!1){t&&Lo(t);const{props:r,children:o}=e.vnode,s=Rl(e);Kc(e,r,s,t),Jc(e,o,n);const i=s?xu(e,t):void 0;return t&&Lo(!1),i}function xu(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,Nc);const{setup:r}=n;if(r){const o=e.setupContext=r.length>1?Lu(e):null,s=nr(e);Bt();const i=It(r,e,0,[e.props,o]);if(zt(),s(),Oi(i)){if(i.then(Ns,Ns),t)return i.then(l=>{js(e,l,t)}).catch(l=>{er(l,e,0)});e.asyncDep=i}else js(e,i,t)}else Il(e,t)}function js(e,t,n){re(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:be(t)&&(e.setupState=tl(t)),Il(e,n)}let Fs;function Il(e,t,n){const r=e.type;if(!e.render){if(!t&&Fs&&!r.render){const o=r.template||Jo(e).template;if(o){const{isCustomElement:s,compilerOptions:i}=e.appContext.config,{delimiters:l,compilerOptions:a}=r,c=Me(Me({isCustomElement:s,delimiters:l},i),a);r.render=Fs(o,c)}}e.render=r.render||nt}{const o=nr(e);Bt();try{jc(e)}finally{zt(),o()}}}const Cu={get(e,t){return Ge(e,"get",""),e[t]}};function Lu(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.attrs,Cu),slots:e.slots,emit:e.emit,expose:t}}function Kr(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(tl(sc(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Hn)return Hn[n](e)},has(t,n){return n in t||n in Hn}})):e.proxy}function Pu(e,t=!0){return re(e)?e.displayName||e.name:e.name||t&&e.__name}function Au(e){return re(e)&&"__vccOpts"in e}const R=(e,t)=>ic(e,t,rr);function ie(e,t,n){const r=arguments.length;return r===2?be(t)&&!ee(t)?Tr(t)?oe(e,null,[t]):oe(e,t):oe(e,null,t):(r>3?n=Array.prototype.slice.call(arguments,2):r===3&&Tr(n)&&(n=[n]),oe(e,t,n))}const Tu="3.4.37";/** +* @vue/runtime-dom v3.4.37 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/const Ou="http://www.w3.org/2000/svg",Ru="http://www.w3.org/1998/Math/MathML",_t=typeof document<"u"?document:null,Ds=_t&&_t.createElement("template"),Iu={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{const o=t==="svg"?_t.createElementNS(Ou,e):t==="mathml"?_t.createElementNS(Ru,e):n?_t.createElement(e,{is:n}):_t.createElement(e);return e==="select"&&r&&r.multiple!=null&&o.setAttribute("multiple",r.multiple),o},createText:e=>_t.createTextNode(e),createComment:e=>_t.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>_t.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,r,o,s){const i=n?n.previousSibling:t.lastChild;if(o&&(o===s||o.nextSibling))for(;t.insertBefore(o.cloneNode(!0),n),!(o===s||!(o=o.nextSibling)););else{Ds.innerHTML=r==="svg"?``:r==="mathml"?``:e;const l=Ds.content;if(r==="svg"||r==="mathml"){const a=l.firstChild;for(;a.firstChild;)l.appendChild(a.firstChild);l.removeChild(a)}t.insertBefore(l,n)}return[i?i.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},xt="transition",An="animation",Kn=Symbol("_vtc"),En=(e,{slots:t})=>ie(Sc,Mu(e),t);En.displayName="Transition";const Ml={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String};En.props=Me({},il,Ml);const Ut=(e,t=[])=>{ee(e)?e.forEach(n=>n(...t)):e&&e(...t)},Vs=e=>e?ee(e)?e.some(t=>t.length>1):e.length>1:!1;function Mu(e){const t={};for(const y in e)y in Ml||(t[y]=e[y]);if(e.css===!1)return t;const{name:n="v",type:r,duration:o,enterFromClass:s=`${n}-enter-from`,enterActiveClass:i=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:a=s,appearActiveClass:c=i,appearToClass:f=l,leaveFromClass:u=`${n}-leave-from`,leaveActiveClass:h=`${n}-leave-active`,leaveToClass:g=`${n}-leave-to`}=e,v=$u(o),S=v&&v[0],L=v&&v[1],{onBeforeEnter:O,onEnter:w,onEnterCancelled:m,onLeave:k,onLeaveCancelled:N,onBeforeAppear:M=O,onAppear:$=w,onAppearCancelled:b=m}=t,z=(y,H,X)=>{qt(y,H?f:l),qt(y,H?c:i),X&&X()},x=(y,H)=>{y._isLeaving=!1,qt(y,u),qt(y,g),qt(y,h),H&&H()},D=y=>(H,X)=>{const te=y?$:w,I=()=>z(H,y,X);Ut(te,[H,I]),Bs(()=>{qt(H,y?a:s),Ct(H,y?f:l),Vs(te)||zs(H,r,S,I)})};return Me(t,{onBeforeEnter(y){Ut(O,[y]),Ct(y,s),Ct(y,i)},onBeforeAppear(y){Ut(M,[y]),Ct(y,a),Ct(y,c)},onEnter:D(!1),onAppear:D(!0),onLeave(y,H){y._isLeaving=!0;const X=()=>x(y,H);Ct(y,u),Ct(y,h),ju(),Bs(()=>{y._isLeaving&&(qt(y,u),Ct(y,g),Vs(k)||zs(y,r,L,X))}),Ut(k,[y,X])},onEnterCancelled(y){z(y,!1),Ut(m,[y])},onAppearCancelled(y){z(y,!0),Ut(b,[y])},onLeaveCancelled(y){x(y),Ut(N,[y])}})}function $u(e){if(e==null)return null;if(be(e))return[lo(e.enter),lo(e.leave)];{const t=lo(e);return[t,t]}}function lo(e){return Aa(e)}function Ct(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[Kn]||(e[Kn]=new Set)).add(t)}function qt(e,t){t.split(/\s+/).forEach(r=>r&&e.classList.remove(r));const n=e[Kn];n&&(n.delete(t),n.size||(e[Kn]=void 0))}function Bs(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let Hu=0;function zs(e,t,n,r){const o=e._endId=++Hu,s=()=>{o===e._endId&&r()};if(n)return setTimeout(s,n);const{type:i,timeout:l,propCount:a}=Nu(e,t);if(!i)return r();const c=i+"end";let f=0;const u=()=>{e.removeEventListener(c,h),s()},h=g=>{g.target===e&&++f>=a&&u()};setTimeout(()=>{f(n[v]||"").split(", "),o=r(`${xt}Delay`),s=r(`${xt}Duration`),i=Ws(o,s),l=r(`${An}Delay`),a=r(`${An}Duration`),c=Ws(l,a);let f=null,u=0,h=0;t===xt?i>0&&(f=xt,u=i,h=s.length):t===An?c>0&&(f=An,u=c,h=a.length):(u=Math.max(i,c),f=u>0?i>c?xt:An:null,h=f?f===xt?s.length:a.length:0);const g=f===xt&&/\b(transform|all)(,|$)/.test(r(`${xt}Property`).toString());return{type:f,timeout:u,propCount:h,hasTransform:g}}function Ws(e,t){for(;e.length Ks(n)+Ks(e[r])))}function Ks(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function ju(){return document.body.offsetHeight}function Fu(e,t,n){const r=e[Kn];r&&(t=(t?[t,...r]:[...r]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const Rr=Symbol("_vod"),$l=Symbol("_vsh"),Ir={beforeMount(e,{value:t},{transition:n}){e[Rr]=e.style.display==="none"?"":e.style.display,n&&t?n.beforeEnter(e):Tn(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:r}){!t!=!n&&(r?t?(r.beforeEnter(e),Tn(e,!0),r.enter(e)):r.leave(e,()=>{Tn(e,!1)}):Tn(e,t))},beforeUnmount(e,{value:t}){Tn(e,t)}};function Tn(e,t){e.style.display=t?e[Rr]:"none",e[$l]=!t}const Du=Symbol(""),Vu=/(^|;)\s*display\s*:/;function Bu(e,t,n){const r=e.style,o=Ce(n);let s=!1;if(n&&!o){if(t)if(Ce(t))for(const i of t.split(";")){const l=i.slice(0,i.indexOf(":")).trim();n[l]==null&&yr(r,l,"")}else for(const i in t)n[i]==null&&yr(r,i,"");for(const i in n)i==="display"&&(s=!0),yr(r,i,n[i])}else if(o){if(t!==n){const i=r[Du];i&&(n+=";"+i),r.cssText=n,s=Vu.test(n)}}else t&&e.removeAttribute("style");Rr in e&&(e[Rr]=s?r.display:"",e[$l]&&(r.display="none"))}const Us=/\s*!important$/;function yr(e,t,n){if(ee(n))n.forEach(r=>yr(e,t,r));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const r=zu(e,t);Us.test(n)?e.setProperty(Vt(r),n.replace(Us,""),"important"):e[r]=n}}const qs=["Webkit","Moz","ms"],ao={};function zu(e,t){const n=ao[t];if(n)return n;let r=Xe(t);if(r!=="filter"&&r in e)return ao[t]=r;r=Qn(r);for(let o=0;o co||(Ju.then(()=>co=0),co=Date.now());function Qu(e,t){const n=r=>{if(!r._vts)r._vts=Date.now();else if(r._vts<=n.attached)return;rt(Xu(r,n.value),t,5,[r])};return n.value=e,n.attached=Yu(),n}function Xu(e,t){if(ee(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(r=>o=>!o._stopped&&r&&r(o))}else return t}const Xs=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,Zu=(e,t,n,r,o,s)=>{const i=o==="svg";t==="class"?Fu(e,r,i):t==="style"?Bu(e,n,r):Yn(t)?$o(t)||qu(e,t,n,r,s):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):ef(e,t,r,i))?(Wu(e,t,r),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Js(e,t,r,i,s,t!=="value")):(t==="true-value"?e._trueValue=r:t==="false-value"&&(e._falseValue=r),Js(e,t,r,i))};function ef(e,t,n,r){if(r)return!!(t==="innerHTML"||t==="textContent"||t in e&&Xs(t)&&re(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const o=e.tagName;if(o==="IMG"||o==="VIDEO"||o==="CANVAS"||o==="SOURCE")return!1}return Xs(t)&&Ce(n)?!1:t in e}const tf={esc:"escape",space:" ",up:"arrow-up",left:"arrow-left",right:"arrow-right",down:"arrow-down",delete:"backspace"},nf=(e,t)=>{const n=e._withKeys||(e._withKeys={}),r=t.join(".");return n[r]||(n[r]=o=>{if(!("key"in o))return;const s=Vt(o.key);if(t.some(i=>i===s||tf[i]===s))return e(o)})},rf=Me({patchProp:Zu},Iu);let uo,Zs=!1;function of(){return uo=Zs?uo:nu(rf),Zs=!0,uo}const sf=(...e)=>{const t=of().createApp(...e),{mount:n}=t;return t.mount=r=>{const o=af(r);if(o)return n(o,!0,lf(o))},t};function lf(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function af(e){return Ce(e)?document.querySelector(e):e}var or=e=>/^[a-z][a-z0-9+.-]*:/.test(e)||e.startsWith("//"),cf=/.md((\?|#).*)?$/,uf=(e,t="/")=>or(e)||e.startsWith("/")&&!e.startsWith(t)&&!cf.test(e),Ur=e=>/^(https?:)?\/\//.test(e),ei=e=>{if(!e||e.endsWith("/"))return e;let t=e.replace(/(^|\/)README.md$/i,"$1index.html");return t.endsWith(".md")?t=t.substring(0,t.length-3)+".html":t.endsWith(".html")||(t=t+".html"),t.endsWith("/index.html")&&(t=t.substring(0,t.length-10)),t},ff="http://.",df=(e,t)=>{if(!e.startsWith("/")&&t){const n=t.slice(0,t.lastIndexOf("/"));return ei(new URL(`${n}/${e}`,ff).pathname)}return ei(e)},pf=(e,t)=>{const n=Object.keys(e).sort((r,o)=>{const s=o.split("/").length-r.split("/").length;return s!==0?s:o.length-r.length});for(const r of n)if(t.startsWith(r))return r;return"/"},hf=/(#|\?)/,Hl=e=>{const[t,...n]=e.split(hf);return{pathname:t,hashAndQueries:n.join("")}},mf=["link","meta","script","style","noscript","template"],gf=["title","base"],vf=([e,t,n])=>gf.includes(e)?e:mf.includes(e)?e==="meta"&&t.name?`${e}.${t.name}`:e==="template"&&t.id?`${e}.${t.id}`:JSON.stringify([e,Object.entries(t).map(([r,o])=>typeof o=="boolean"?o?[r,""]:null:[r,o]).filter(r=>r!=null).sort(([r],[o])=>r.localeCompare(o)),n]):null,_f=e=>{const t=new Set,n=[];return e.forEach(r=>{const o=vf(r);o&&!t.has(o)&&(t.add(o),n.push(r))}),n},bf=e=>e[e.length-1]==="/"||e.endsWith(".html")?e:`${e}/`,Nl=e=>e[e.length-1]==="/"?e.slice(0,-1):e,jl=e=>e[0]==="/"?e.slice(1):e,Zo=e=>Object.prototype.toString.call(e)==="[object Object]",ot=e=>typeof e=="string";const yf="modulepreload",wf=function(e){return"/learning-kotlin/"+e},ti={},Pe=function(t,n,r){let o=Promise.resolve();if(n&&n.length>0){document.getElementsByTagName("link");const s=document.querySelector("meta[property=csp-nonce]"),i=(s==null?void 0:s.nonce)||(s==null?void 0:s.getAttribute("nonce"));o=Promise.all(n.map(l=>{if(l=wf(l),l in ti)return;ti[l]=!0;const a=l.endsWith(".css"),c=a?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${l}"]${c}`))return;const f=document.createElement("link");if(f.rel=a?"stylesheet":yf,a||(f.as="script",f.crossOrigin=""),f.href=l,i&&f.setAttribute("nonce",i),document.head.appendChild(f),a)return new Promise((u,h)=>{f.addEventListener("load",u),f.addEventListener("error",()=>h(new Error(`Unable to preload CSS for ${l}`)))})}))}return o.then(()=>t()).catch(s=>{const i=new Event("vite:preloadError",{cancelable:!0});if(i.payload=s,window.dispatchEvent(i),!i.defaultPrevented)throw s})},kf=JSON.parse("{}"),Sf=Object.fromEntries([["/",{loader:()=>Pe(()=>import("./index.html-7_H8SzH5.js"),__vite__mapDeps([0,1])),meta:{title:"Welcome"}}],["/en/",{loader:()=>Pe(()=>import("./index.html-NwXwu3YI.js"),__vite__mapDeps([2,1])),meta:{title:"Welcome"}}],["/fr/",{loader:()=>Pe(()=>import("./index.html-DfLOFdKV.js"),[]),meta:{title:""}}],["/en/backend-development/",{loader:()=>Pe(()=>import("./index.html-DPrxNVl-.js"),[]),meta:{title:"📚 Backend development"}}],["/en/front-development/",{loader:()=>Pe(()=>import("./index.html-BaJOs0HP.js"),__vite__mapDeps([3,4])),meta:{title:"📚 Frontend development"}}],["/en/kotlin-features/",{loader:()=>Pe(()=>import("./index.html-Bht4bXi_.js"),[]),meta:{title:"📚 Kotlin language features"}}],["/en/kotlin-features-advanced/",{loader:()=>Pe(()=>import("./index.html-CDLKHirq.js"),[]),meta:{title:"📚 Advanced and other Kotlin features"}}],["/en/other-technologies/",{loader:()=>Pe(()=>import("./index.html-CDbrj19k.js"),__vite__mapDeps([5,6])),meta:{title:"🛠 Let's make a cross-plaform app !"}}],["/en/presentation/",{loader:()=>Pe(()=>import("./index.html-BRh3fjnU.js"),__vite__mapDeps([7,8])),meta:{title:"🚀 Presentation of Kotlin"}}],["/en/workshops/",{loader:()=>Pe(()=>import("./index.html-DOWQbO5d.js"),__vite__mapDeps([9,10])),meta:{title:"📅 Workshops"}}],["/fr/backend-development/",{loader:()=>Pe(()=>import("./index.html-xZK9TAru.js"),[]),meta:{title:"📚 Développement du backend"}}],["/fr/front-development/",{loader:()=>Pe(()=>import("./index.html-Bxjju2_F.js"),__vite__mapDeps([11,4])),meta:{title:"📚 Développement frontend"}}],["/fr/kotlin-features/",{loader:()=>Pe(()=>import("./index.html-CeJwufaG.js"),[]),meta:{title:"📚 Fonctionnalités du langage Kotlin"}}],["/fr/kotlin-features-advanced/",{loader:()=>Pe(()=>import("./index.html-DkjVfAMI.js"),[]),meta:{title:"📚 Fonctionnalités avancées de Kotlin"}}],["/fr/other-technologies/",{loader:()=>Pe(()=>import("./index.html-C_RVdbGD.js"),__vite__mapDeps([12,6])),meta:{title:"🛠 Construisons une app multiplateforme !"}}],["/fr/presentation/",{loader:()=>Pe(()=>import("./index.html-09oChKdI.js"),__vite__mapDeps([13,8])),meta:{title:"🚀 Présentation de Kotlin"}}],["/fr/workshops/",{loader:()=>Pe(()=>import("./index.html-BnOna0wn.js"),__vite__mapDeps([14,10])),meta:{title:"📅 Workshops"}}],["/404.html",{loader:()=>Pe(()=>import("./404.html-CsHE7vwU.js"),[]),meta:{title:""}}]]);/*! + * vue-router v4.4.3 + * (c) 2024 Eduardo San Martin Morote + * @license MIT + */const an=typeof document<"u";function Ef(e){return e.__esModule||e[Symbol.toStringTag]==="Module"}const me=Object.assign;function fo(e,t){const n={};for(const r in t){const o=t[r];n[r]=it(o)?o.map(e):e(o)}return n}const Fn=()=>{},it=Array.isArray,Fl=/#/g,xf=/&/g,Cf=/\//g,Lf=/=/g,Pf=/\?/g,Dl=/\+/g,Af=/%5B/g,Tf=/%5D/g,Vl=/%5E/g,Of=/%60/g,Bl=/%7B/g,Rf=/%7C/g,zl=/%7D/g,If=/%20/g;function es(e){return encodeURI(""+e).replace(Rf,"|").replace(Af,"[").replace(Tf,"]")}function Mf(e){return es(e).replace(Bl,"{").replace(zl,"}").replace(Vl,"^")}function Po(e){return es(e).replace(Dl,"%2B").replace(If,"+").replace(Fl,"%23").replace(xf,"%26").replace(Of,"`").replace(Bl,"{").replace(zl,"}").replace(Vl,"^")}function $f(e){return Po(e).replace(Lf,"%3D")}function Hf(e){return es(e).replace(Fl,"%23").replace(Pf,"%3F")}function Nf(e){return e==null?"":Hf(e).replace(Cf,"%2F")}function Un(e){try{return decodeURIComponent(""+e)}catch{}return""+e}const jf=/\/$/,Ff=e=>e.replace(jf,"");function po(e,t,n="/"){let r,o={},s="",i="";const l=t.indexOf("#");let a=t.indexOf("?");return l=0&&(a=-1),a>-1&&(r=t.slice(0,a),s=t.slice(a+1,l>-1?l:t.length),o=e(s)),l>-1&&(r=r||t.slice(0,l),i=t.slice(l,t.length)),r=zf(r??t,n),{fullPath:r+(s&&"?")+s+i,path:r,query:o,hash:Un(i)}}function Df(e,t){const n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}function ni(e,t){return!t||!e.toLowerCase().startsWith(t.toLowerCase())?e:e.slice(t.length)||"/"}function Vf(e,t,n){const r=t.matched.length-1,o=n.matched.length-1;return r>-1&&r===o&&bn(t.matched[r],n.matched[o])&&Wl(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function bn(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function Wl(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const n in e)if(!Bf(e[n],t[n]))return!1;return!0}function Bf(e,t){return it(e)?ri(e,t):it(t)?ri(t,e):e===t}function ri(e,t){return it(t)?e.length===t.length&&e.every((n,r)=>n===t[r]):e.length===1&&e[0]===t}function zf(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),r=e.split("/"),o=r[r.length-1];(o===".."||o===".")&&r.push("");let s=n.length-1,i,l;for(i=0;i 1&&s--;else break;return n.slice(0,s).join("/")+"/"+r.slice(i).join("/")}const vt={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0};var qn;(function(e){e.pop="pop",e.push="push"})(qn||(qn={}));var Dn;(function(e){e.back="back",e.forward="forward",e.unknown=""})(Dn||(Dn={}));function Wf(e){if(!e)if(an){const t=document.querySelector("base");e=t&&t.getAttribute("href")||"/",e=e.replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return e[0]!=="/"&&e[0]!=="#"&&(e="/"+e),Ff(e)}const Kf=/^[^#]+#/;function Uf(e,t){return e.replace(Kf,"#")+t}function qf(e,t){const n=document.documentElement.getBoundingClientRect(),r=e.getBoundingClientRect();return{behavior:t.behavior,left:r.left-n.left-(t.left||0),top:r.top-n.top-(t.top||0)}}const qr=()=>({left:window.scrollX,top:window.scrollY});function Gf(e){let t;if("el"in e){const n=e.el,r=typeof n=="string"&&n.startsWith("#"),o=typeof n=="string"?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!o)return;t=qf(o,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(t.left!=null?t.left:window.scrollX,t.top!=null?t.top:window.scrollY)}function oi(e,t){return(history.state?history.state.position-t:-1)+e}const Ao=new Map;function Jf(e,t){Ao.set(e,t)}function Yf(e){const t=Ao.get(e);return Ao.delete(e),t}let Qf=()=>location.protocol+"//"+location.host;function Kl(e,t){const{pathname:n,search:r,hash:o}=t,s=e.indexOf("#");if(s>-1){let l=o.includes(e.slice(s))?e.slice(s).length:1,a=o.slice(l);return a[0]!=="/"&&(a="/"+a),ni(a,"")}return ni(n,e)+r+o}function Xf(e,t,n,r){let o=[],s=[],i=null;const l=({state:h})=>{const g=Kl(e,location),v=n.value,S=t.value;let L=0;if(h){if(n.value=g,t.value=h,i&&i===v){i=null;return}L=S?h.position-S.position:0}else r(g);o.forEach(O=>{O(n.value,v,{delta:L,type:qn.pop,direction:L?L>0?Dn.forward:Dn.back:Dn.unknown})})};function a(){i=n.value}function c(h){o.push(h);const g=()=>{const v=o.indexOf(h);v>-1&&o.splice(v,1)};return s.push(g),g}function f(){const{history:h}=window;h.state&&h.replaceState(me({},h.state,{scroll:qr()}),"")}function u(){for(const h of s)h();s=[],window.removeEventListener("popstate",l),window.removeEventListener("beforeunload",f)}return window.addEventListener("popstate",l),window.addEventListener("beforeunload",f,{passive:!0}),{pauseListeners:a,listen:c,destroy:u}}function si(e,t,n,r=!1,o=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:o?qr():null}}function Zf(e){const{history:t,location:n}=window,r={value:Kl(e,n)},o={value:t.state};o.value||s(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function s(a,c,f){const u=e.indexOf("#"),h=u>-1?(n.host&&document.querySelector("base")?e:e.slice(u))+a:Qf()+e+a;try{t[f?"replaceState":"pushState"](c,"",h),o.value=c}catch(g){console.error(g),n[f?"replace":"assign"](h)}}function i(a,c){const f=me({},t.state,si(o.value.back,a,o.value.forward,!0),c,{position:o.value.position});s(a,f,!0),r.value=a}function l(a,c){const f=me({},o.value,t.state,{forward:a,scroll:qr()});s(f.current,f,!0);const u=me({},si(r.value,a,null),{position:f.position+1},c);s(a,u,!1),r.value=a}return{location:r,state:o,push:l,replace:i}}function ed(e){e=Wf(e);const t=Zf(e),n=Xf(e,t.state,t.location,t.replace);function r(s,i=!0){i||n.pauseListeners(),history.go(s)}const o=me({location:"",base:e,go:r,createHref:Uf.bind(null,e)},t,n);return Object.defineProperty(o,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(o,"state",{enumerable:!0,get:()=>t.state.value}),o}function td(e){return typeof e=="string"||e&&typeof e=="object"}function Ul(e){return typeof e=="string"||typeof e=="symbol"}const ql=Symbol("");var ii;(function(e){e[e.aborted=4]="aborted",e[e.cancelled=8]="cancelled",e[e.duplicated=16]="duplicated"})(ii||(ii={}));function yn(e,t){return me(new Error,{type:e,[ql]:!0},t)}function gt(e,t){return e instanceof Error&&ql in e&&(t==null||!!(e.type&t))}const li="[^/]+?",nd={sensitive:!1,strict:!1,start:!0,end:!0},rd=/[.+*?^${}()[\]/\\]/g;function od(e,t){const n=me({},nd,t),r=[];let o=n.start?"^":"";const s=[];for(const c of e){const f=c.length?[]:[90];n.strict&&!c.length&&(o+="/");for(let u=0;u t.length?t.length===1&&t[0]===80?1:-1:0}function Gl(e,t){let n=0;const r=e.score,o=t.score;for(;n 0&&t[t.length-1]<0}const id={type:0,value:""},ld=/[a-zA-Z0-9_]/;function ad(e){if(!e)return[[]];if(e==="/")return[[id]];if(!e.startsWith("/"))throw new Error(`Invalid path "${e}"`);function t(g){throw new Error(`ERR (${n})/"${c}": ${g}`)}let n=0,r=n;const o=[];let s;function i(){s&&o.push(s),s=[]}let l=0,a,c="",f="";function u(){c&&(n===0?s.push({type:0,value:c}):n===1||n===2||n===3?(s.length>1&&(a==="*"||a==="+")&&t(`A repeatable param (${c}) must be alone in its segment. eg: '/:ids+.`),s.push({type:1,value:c,regexp:f,repeatable:a==="*"||a==="+",optional:a==="*"||a==="?"})):t("Invalid state to consume buffer"),c="")}function h(){c+=a}for(;l {i(m)}:Fn}function i(u){if(Ul(u)){const h=r.get(u);h&&(r.delete(u),n.splice(n.indexOf(h),1),h.children.forEach(i),h.alias.forEach(i))}else{const h=n.indexOf(u);h>-1&&(n.splice(h,1),u.record.name&&r.delete(u.record.name),u.children.forEach(i),u.alias.forEach(i))}}function l(){return n}function a(u){const h=hd(u,n);n.splice(h,0,u),u.record.name&&!ui(u)&&r.set(u.record.name,u)}function c(u,h){let g,v={},S,L;if("name"in u&&u.name){if(g=r.get(u.name),!g)throw yn(1,{location:u});L=g.record.name,v=me(ci(h.params,g.keys.filter(m=>!m.optional).concat(g.parent?g.parent.keys.filter(m=>m.optional):[]).map(m=>m.name)),u.params&&ci(u.params,g.keys.map(m=>m.name))),S=g.stringify(v)}else if(u.path!=null)S=u.path,g=n.find(m=>m.re.test(S)),g&&(v=g.parse(S),L=g.record.name);else{if(g=h.name?r.get(h.name):n.find(m=>m.re.test(h.path)),!g)throw yn(1,{location:u,currentLocation:h});L=g.record.name,v=me({},h.params,u.params),S=g.stringify(v)}const O=[];let w=g;for(;w;)O.unshift(w.record),w=w.parent;return{name:L,path:S,params:v,matched:O,meta:pd(O)}}e.forEach(u=>s(u));function f(){n.length=0,r.clear()}return{addRoute:s,resolve:c,removeRoute:i,clearRoutes:f,getRoutes:l,getRecordMatcher:o}}function ci(e,t){const n={};for(const r of t)r in e&&(n[r]=e[r]);return n}function fd(e){return{path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:void 0,beforeEnter:e.beforeEnter,props:dd(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:"components"in e?e.components||null:e.component&&{default:e.component}}}function dd(e){const t={},n=e.props||!1;if("component"in e)t.default=n;else for(const r in e.components)t[r]=typeof n=="object"?n[r]:n;return t}function ui(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function pd(e){return e.reduce((t,n)=>me(t,n.meta),{})}function fi(e,t){const n={};for(const r in e)n[r]=r in t?t[r]:e[r];return n}function hd(e,t){let n=0,r=t.length;for(;n!==r;){const s=n+r>>1;Gl(e,t[s])<0?r=s:n=s+1}const o=md(e);return o&&(r=t.lastIndexOf(o,r-1)),r}function md(e){let t=e;for(;t=t.parent;)if(Jl(t)&&Gl(e,t)===0)return t}function Jl({record:e}){return!!(e.name||e.components&&Object.keys(e.components).length||e.redirect)}function gd(e){const t={};if(e===""||e==="?")return t;const r=(e[0]==="?"?e.slice(1):e).split("&");for(let o=0;o s&&Po(s)):[r&&Po(r)]).forEach(s=>{s!==void 0&&(t+=(t.length?"&":"")+n,s!=null&&(t+="="+s))})}return t}function vd(e){const t={};for(const n in e){const r=e[n];r!==void 0&&(t[n]=it(r)?r.map(o=>o==null?null:""+o):r==null?r:""+r)}return t}const _d=Symbol(""),pi=Symbol(""),Gr=Symbol(""),ts=Symbol(""),To=Symbol("");function On(){let e=[];function t(r){return e.push(r),()=>{const o=e.indexOf(r);o>-1&&e.splice(o,1)}}function n(){e=[]}return{add:t,list:()=>e.slice(),reset:n}}function Tt(e,t,n,r,o,s=i=>i()){const i=r&&(r.enterCallbacks[o]=r.enterCallbacks[o]||[]);return()=>new Promise((l,a)=>{const c=h=>{h===!1?a(yn(4,{from:n,to:t})):h instanceof Error?a(h):td(h)?a(yn(2,{from:t,to:h})):(i&&r.enterCallbacks[o]===i&&typeof h=="function"&&i.push(h),l())},f=s(()=>e.call(r&&r.instances[o],t,n,c));let u=Promise.resolve(f);e.length<3&&(u=u.then(c)),u.catch(h=>a(h))})}function ho(e,t,n,r,o=s=>s()){const s=[];for(const i of e)for(const l in i.components){let a=i.components[l];if(!(t!=="beforeRouteEnter"&&!i.instances[l]))if(bd(a)){const f=(a.__vccOpts||a)[t];f&&s.push(Tt(f,n,r,i,l,o))}else{let c=a();s.push(()=>c.then(f=>{if(!f)return Promise.reject(new Error(`Couldn't resolve component "${l}" at "${i.path}"`));const u=Ef(f)?f.default:f;i.components[l]=u;const g=(u.__vccOpts||u)[t];return g&&Tt(g,n,r,i,l,o)()}))}}return s}function bd(e){return typeof e=="object"||"displayName"in e||"props"in e||"__vccOpts"in e}function hi(e){const t=Ve(Gr),n=Ve(ts),r=R(()=>{const a=tn(e.to);return t.resolve(a)}),o=R(()=>{const{matched:a}=r.value,{length:c}=a,f=a[c-1],u=n.matched;if(!f||!u.length)return-1;const h=u.findIndex(bn.bind(null,f));if(h>-1)return h;const g=mi(a[c-2]);return c>1&&mi(f)===g&&u[u.length-1].path!==g?u.findIndex(bn.bind(null,a[c-2])):h}),s=R(()=>o.value>-1&&Sd(n.params,r.value.params)),i=R(()=>o.value>-1&&o.value===n.matched.length-1&&Wl(n.params,r.value.params));function l(a={}){return kd(a)?t[tn(e.replace)?"replace":"push"](tn(e.to)).catch(Fn):Promise.resolve()}return{route:r,href:R(()=>r.value.href),isActive:s,isExactActive:i,navigate:l}}const yd=pe({name:"RouterLink",compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"}},useLink:hi,setup(e,{slots:t}){const n=Zn(hi(e)),{options:r}=Ve(Gr),o=R(()=>({[gi(e.activeClass,r.linkActiveClass,"router-link-active")]:n.isActive,[gi(e.exactActiveClass,r.linkExactActiveClass,"router-link-exact-active")]:n.isExactActive}));return()=>{const s=t.default&&t.default(n);return e.custom?s:ie("a",{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:o.value},s)}}}),wd=yd;function kd(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function Sd(e,t){for(const n in t){const r=t[n],o=e[n];if(typeof r=="string"){if(r!==o)return!1}else if(!it(o)||o.length!==r.length||r.some((s,i)=>s!==o[i]))return!1}return!0}function mi(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const gi=(e,t,n)=>e??t??n,Ed=pe({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},compatConfig:{MODE:3},setup(e,{attrs:t,slots:n}){const r=Ve(To),o=R(()=>e.route||r.value),s=Ve(pi,0),i=R(()=>{let c=tn(s);const{matched:f}=o.value;let u;for(;(u=f[c])&&!u.components;)c++;return c}),l=R(()=>o.value.matched[i.value]);Mt(pi,R(()=>i.value+1)),Mt(_d,l),Mt(To,o);const a=le();return Ne(()=>[a.value,l.value,e.name],([c,f,u],[h,g,v])=>{f&&(f.instances[u]=c,g&&g!==f&&c&&c===h&&(f.leaveGuards.size||(f.leaveGuards=g.leaveGuards),f.updateGuards.size||(f.updateGuards=g.updateGuards))),c&&f&&(!g||!bn(f,g)||!h)&&(f.enterCallbacks[u]||[]).forEach(S=>S(c))},{flush:"post"}),()=>{const c=o.value,f=e.name,u=l.value,h=u&&u.components[f];if(!h)return vi(n.default,{Component:h,route:c});const g=u.props[f],v=g?g===!0?c.params:typeof g=="function"?g(c):g:null,L=ie(h,me({},v,t,{onVnodeUnmounted:O=>{O.component.isUnmounted&&(u.instances[f]=null)},ref:a}));return vi(n.default,{Component:L,route:c})||L}}});function vi(e,t){if(!e)return null;const n=e(t);return n.length===1?n[0]:n}const xd=Ed;function Cd(e){const t=ud(e.routes,e),n=e.parseQuery||gd,r=e.stringifyQuery||di,o=e.history,s=On(),i=On(),l=On(),a=kn(vt);let c=vt;an&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const f=fo.bind(null,C=>""+C),u=fo.bind(null,Nf),h=fo.bind(null,Un);function g(C,W){let V,G;return Ul(C)?(V=t.getRecordMatcher(C),G=W):G=C,t.addRoute(G,V)}function v(C){const W=t.getRecordMatcher(C);W&&t.removeRoute(W)}function S(){return t.getRoutes().map(C=>C.record)}function L(C){return!!t.getRecordMatcher(C)}function O(C,W){if(W=me({},W||a.value),typeof C=="string"){const p=po(n,C,W.path),_=t.resolve({path:p.path},W),P=o.createHref(p.fullPath);return me(p,_,{params:h(_.params),hash:Un(p.hash),redirectedFrom:void 0,href:P})}let V;if(C.path!=null)V=me({},C,{path:po(n,C.path,W.path).path});else{const p=me({},C.params);for(const _ in p)p[_]==null&&delete p[_];V=me({},C,{params:u(p)}),W.params=u(W.params)}const G=t.resolve(V,W),ce=C.hash||"";G.params=f(h(G.params));const ge=Df(r,me({},C,{hash:Mf(ce),path:G.path})),d=o.createHref(ge);return me({fullPath:ge,hash:ce,query:r===di?vd(C.query):C.query||{}},G,{redirectedFrom:void 0,href:d})}function w(C){return typeof C=="string"?po(n,C,a.value.path):me({},C)}function m(C,W){if(c!==C)return yn(8,{from:W,to:C})}function k(C){return $(C)}function N(C){return k(me(w(C),{replace:!0}))}function M(C){const W=C.matched[C.matched.length-1];if(W&&W.redirect){const{redirect:V}=W;let G=typeof V=="function"?V(C):V;return typeof G=="string"&&(G=G.includes("?")||G.includes("#")?G=w(G):{path:G},G.params={}),me({query:C.query,hash:C.hash,params:G.path!=null?{}:C.params},G)}}function $(C,W){const V=c=O(C),G=a.value,ce=C.state,ge=C.force,d=C.replace===!0,p=M(V);if(p)return $(me(w(p),{state:typeof p=="object"?me({},ce,p.state):ce,force:ge,replace:d}),W||V);const _=V;_.redirectedFrom=W;let P;return!ge&&Vf(r,G,V)&&(P=yn(16,{to:_,from:G}),We(G,G,!0,!1)),(P?Promise.resolve(P):x(_,G)).catch(E=>gt(E)?gt(E,2)?E:He(E):q(E,_,G)).then(E=>{if(E){if(gt(E,2))return $(me({replace:d},w(E.to),{state:typeof E.to=="object"?me({},ce,E.to.state):ce,force:ge}),W||_)}else E=y(_,G,!0,d,ce);return D(_,G,E),E})}function b(C,W){const V=m(C,W);return V?Promise.reject(V):Promise.resolve()}function z(C){const W=St.values().next().value;return W&&typeof W.runWithContext=="function"?W.runWithContext(C):C()}function x(C,W){let V;const[G,ce,ge]=Ld(C,W);V=ho(G.reverse(),"beforeRouteLeave",C,W);for(const p of G)p.leaveGuards.forEach(_=>{V.push(Tt(_,C,W))});const d=b.bind(null,C,W);return V.push(d),Ke(V).then(()=>{V=[];for(const p of s.list())V.push(Tt(p,C,W));return V.push(d),Ke(V)}).then(()=>{V=ho(ce,"beforeRouteUpdate",C,W);for(const p of ce)p.updateGuards.forEach(_=>{V.push(Tt(_,C,W))});return V.push(d),Ke(V)}).then(()=>{V=[];for(const p of ge)if(p.beforeEnter)if(it(p.beforeEnter))for(const _ of p.beforeEnter)V.push(Tt(_,C,W));else V.push(Tt(p.beforeEnter,C,W));return V.push(d),Ke(V)}).then(()=>(C.matched.forEach(p=>p.enterCallbacks={}),V=ho(ge,"beforeRouteEnter",C,W,z),V.push(d),Ke(V))).then(()=>{V=[];for(const p of i.list())V.push(Tt(p,C,W));return V.push(d),Ke(V)}).catch(p=>gt(p,8)?p:Promise.reject(p))}function D(C,W,V){l.list().forEach(G=>z(()=>G(C,W,V)))}function y(C,W,V,G,ce){const ge=m(C,W);if(ge)return ge;const d=W===vt,p=an?history.state:{};V&&(G||d?o.replace(C.fullPath,me({scroll:d&&p&&p.scroll},ce)):o.push(C.fullPath,ce)),a.value=C,We(C,W,V,d),He()}let H;function X(){H||(H=o.listen((C,W,V)=>{if(!ct.listening)return;const G=O(C),ce=M(G);if(ce){$(me(ce,{replace:!0}),G).catch(Fn);return}c=G;const ge=a.value;an&&Jf(oi(ge.fullPath,V.delta),qr()),x(G,ge).catch(d=>gt(d,12)?d:gt(d,2)?($(d.to,G).then(p=>{gt(p,20)&&!V.delta&&V.type===qn.pop&&o.go(-1,!1)}).catch(Fn),Promise.reject()):(V.delta&&o.go(-V.delta,!1),q(d,G,ge))).then(d=>{d=d||y(G,ge,!1),d&&(V.delta&&!gt(d,8)?o.go(-V.delta,!1):V.type===qn.pop&>(d,20)&&o.go(-1,!1)),D(G,ge,d)}).catch(Fn)}))}let te=On(),I=On(),J;function q(C,W,V){He(C);const G=I.list();return G.length?G.forEach(ce=>ce(C,W,V)):console.error(C),Promise.reject(C)}function ve(){return J&&a.value!==vt?Promise.resolve():new Promise((C,W)=>{te.add([C,W])})}function He(C){return J||(J=!C,X(),te.list().forEach(([W,V])=>C?V(C):W()),te.reset()),C}function We(C,W,V,G){const{scrollBehavior:ce}=e;if(!an||!ce)return Promise.resolve();const ge=!V&&Yf(oi(C.fullPath,0))||(G||!V)&&history.state&&history.state.scroll||null;return Sn().then(()=>ce(C,W,ge)).then(d=>d&&Gf(d)).catch(d=>q(d,C,W))}const Fe=C=>o.go(C);let kt;const St=new Set,ct={currentRoute:a,listening:!0,addRoute:g,removeRoute:v,clearRoutes:t.clearRoutes,hasRoute:L,getRoutes:S,resolve:O,options:e,push:k,replace:N,go:Fe,back:()=>Fe(-1),forward:()=>Fe(1),beforeEach:s.add,beforeResolve:i.add,afterEach:l.add,onError:I.add,isReady:ve,install(C){const W=this;C.component("RouterLink",wd),C.component("RouterView",xd),C.config.globalProperties.$router=W,Object.defineProperty(C.config.globalProperties,"$route",{enumerable:!0,get:()=>tn(a)}),an&&!kt&&a.value===vt&&(kt=!0,k(o.location).catch(ce=>{}));const V={};for(const ce in vt)Object.defineProperty(V,ce,{get:()=>a.value[ce],enumerable:!0});C.provide(Gr,W),C.provide(ts,Qi(V)),C.provide(To,a);const G=C.unmount;St.add(C),C.unmount=function(){St.delete(C),St.size<1&&(c=vt,H&&H(),H=null,a.value=vt,kt=!1,J=!1),G()}}};function Ke(C){return C.reduce((W,V)=>W.then(()=>z(V)),Promise.resolve())}return ct}function Ld(e,t){const n=[],r=[],o=[],s=Math.max(t.matched.length,e.matched.length);for(let i=0;i bn(c,l))?r.push(l):n.push(l));const a=e.matched[i];a&&(t.matched.find(c=>bn(c,a))||o.push(a))}return[n,r,o]}function Wt(){return Ve(Gr)}function wt(e){return Ve(ts)}var ns=Symbol(""),ht=()=>{const e=Ve(ns);if(!e)throw new Error("useClientData() is called without provider.");return e},Pd=()=>ht().pageComponent,xn=()=>ht().pageData,at=()=>ht().pageFrontmatter,Ad=()=>ht().pageHead,Td=()=>ht().pageLang,Od=()=>ht().pageLayout,Cn=()=>ht().routeLocale,Rd=()=>ht().routes,Yl=()=>ht().siteData,rs=()=>ht().siteLocaleData,Id=Symbol(""),Oo=kn(kf),gn=kn(Sf),Ql=(e,t)=>{const n=df(e,t);if(gn.value[n])return n;const r=encodeURI(n);if(gn.value[r])return r;const o=Oo.value[n]||Oo.value[r];return o||n},Gn=(e,t)=>{const{pathname:n,hashAndQueries:r}=Hl(e),o=Ql(n,t),s=o+r;return gn.value[o]?{...gn.value[o],path:s,notFound:!1}:{...gn.value["/404.html"],path:s,notFound:!0}},Md=(e,t)=>{const{pathname:n,hashAndQueries:r}=Hl(e);return Ql(n,t)+r},$d=e=>{if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget){const t=e.currentTarget.getAttribute("target");if(t!=null&&t.match(/\b_blank\b/i))return}return e.preventDefault(),!0}},Jr=pe({name:"RouteLink",props:{to:{type:String,required:!0},active:Boolean,activeClass:{type:String,default:"route-link-active"}},slots:Object,setup(e,{slots:t}){const n=Wt(),r=wt(),o=R(()=>e.to.startsWith("#")||e.to.startsWith("?")?e.to:`/learning-kotlin/${Md(e.to,r.path).substring(1)}`);return()=>{var s;return ie("a",{class:["route-link",{[e.activeClass]:e.active}],href:o.value,onClick:(i={})=>{$d(i)&&n.push(e.to).catch()}},(s=t.default)==null?void 0:s.call(t))}}}),Ln=pe({name:"AutoLink",props:{config:{type:Object,required:!0}},slots:Object,setup(e,{slots:t}){const n=pc(e,"config"),r=wt(),o=Yl(),s=R(()=>or(n.value.link)),i=R(()=>n.value.target||(s.value?"_blank":void 0)),l=R(()=>i.value==="_blank"),a=R(()=>!s.value&&!l.value),c=R(()=>n.value.rel||(l.value?"noopener noreferrer":null)),f=R(()=>n.value.ariaLabel??n.value.text),u=R(()=>{if(n.value.exact)return!1;const g=Object.keys(o.value.locales);return g.length?g.every(v=>v!==n.value.link):n.value.link!=="/"}),h=R(()=>a.value?n.value.activeMatch?(n.value.activeMatch instanceof RegExp?n.value.activeMatch:new RegExp(n.value.activeMatch,"u")).test(r.path):u.value?r.path.startsWith(n.value.link):r.path===n.value.link:!1);return()=>{const{before:g,after:v,default:S}=t,L=(S==null?void 0:S(n.value))||[g==null?void 0:g(n.value),n.value.text,v==null?void 0:v(n.value)];return a.value?ie(Jr,{class:"auto-link",to:n.value.link,active:h.value,"aria-label":f.value},()=>L):ie("a",{class:"auto-link external-link",href:n.value.link,"aria-label":f.value,rel:c.value,target:i.value},L)}}}),os=pe({name:"ClientOnly",setup(e,t){const n=le(!1);return $e(()=>{n.value=!0}),()=>{var r,o;return n.value?(o=(r=t.slots).default)==null?void 0:o.call(r):null}}}),ss=pe({name:"Content",props:{path:{type:String,required:!1,default:""}},setup(e){const t=Pd(),n=R(()=>{if(!e.path)return t.value;const r=Gn(e.path);return Ec(()=>r.loader().then(({comp:o})=>o))});return()=>ie(n.value)}}),Hd="Layout",Nd="en-US",Gt=Zn({resolveLayouts:e=>e.reduce((t,n)=>({...t,...n.layouts}),{}),resolvePageHead:(e,t,n)=>{const r=ot(t.description)?t.description:n.description,o=[...Array.isArray(t.head)?t.head:[],...n.head,["title",{},e],["meta",{name:"description",content:r}]];return _f(o)},resolvePageHeadTitle:(e,t)=>[e.title,t.title].filter(n=>!!n).join(" | "),resolvePageLang:(e,t)=>e.lang||t.lang||Nd,resolvePageLayout:(e,t)=>{const n=ot(e.frontmatter.layout)?e.frontmatter.layout:Hd;if(!t[n])throw new Error(`[vuepress] Cannot resolve layout: ${n}`);return t[n]},resolveRouteLocale:(e,t)=>pf(e,decodeURI(t)),resolveSiteLocaleData:({base:e,locales:t,...n},r)=>{var o;return{...n,...t[r],head:[...((o=t[r])==null?void 0:o.head)??[],...n.head??[]]}}}),mt=(e={})=>e,Yr=e=>Ur(e)?e:`/learning-kotlin/${jl(e)}`;function Qr(e){return Fi()?(ja(e),!0):!1}function pt(e){return typeof e=="function"?e():tn(e)}const is=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const jd=Object.prototype.toString,Fd=e=>jd.call(e)==="[object Object]",Ro=()=>{};function Xl(e,t){function n(...r){return new Promise((o,s)=>{Promise.resolve(e(()=>t.apply(this,r),{fn:t,thisArg:this,args:r})).then(o).catch(s)})}return n}const Zl=e=>e();function Dd(e,t={}){let n,r,o=Ro;const s=l=>{clearTimeout(l),o(),o=Ro};return l=>{const a=pt(e),c=pt(t.maxWait);return n&&s(n),a<=0||c!==void 0&&c<=0?(r&&(s(r),r=null),Promise.resolve(l())):new Promise((f,u)=>{o=t.rejectOnCancel?u:f,c&&!r&&(r=setTimeout(()=>{n&&s(n),r=null,f(l())},c)),n=setTimeout(()=>{r&&s(r),r=null,f(l())},a)})}}function Vd(e=Zl){const t=le(!0);function n(){t.value=!1}function r(){t.value=!0}const o=(...s)=>{t.value&&e(...s)};return{isActive:Nr(t),pause:n,resume:r,eventFilter:o}}function Bd(e){let t;function n(){return t||(t=e()),t}return n.reset=async()=>{const r=t;t=void 0,r&&await r},n}function zd(e){return Wr()}function Wd(e,t=200,n={}){return Xl(Dd(t,n),e)}function Kd(e,t,n={}){const{eventFilter:r=Zl,...o}=n;return Ne(e,Xl(r,t),o)}function Ud(e,t,n={}){const{eventFilter:r,...o}=n,{eventFilter:s,pause:i,resume:l,isActive:a}=Vd(r);return{stop:Kd(e,t,{...o,eventFilter:s}),pause:i,resume:l,isActive:a}}function ls(e,t=!0,n){zd()?$e(e,n):t?e():Sn(e)}function qd(e,t,n={}){const{immediate:r=!0}=n,o=le(!1);let s=null;function i(){s&&(clearTimeout(s),s=null)}function l(){o.value=!1,i()}function a(...c){i(),o.value=!0,s=setTimeout(()=>{o.value=!1,s=null,e(...c)},pt(t))}return r&&(o.value=!0,is&&a()),Qr(l),{isPending:Nr(o),start:a,stop:l}}function Gd(e=!1,t={}){const{truthyValue:n=!0,falsyValue:r=!1}=t,o=je(e),s=le(e);function i(l){if(arguments.length)return s.value=l,s.value;{const a=pt(n);return s.value=s.value===a?pt(r):a,s.value}}return o?i:[s,i]}function Xt(e){var t;const n=pt(e);return(t=n==null?void 0:n.$el)!=null?t:n}const Ft=is?window:void 0,ea=is?window.navigator:void 0;function lt(...e){let t,n,r,o;if(typeof e[0]=="string"||Array.isArray(e[0])?([n,r,o]=e,t=Ft):[t,n,r,o]=e,!t)return Ro;Array.isArray(n)||(n=[n]),Array.isArray(r)||(r=[r]);const s=[],i=()=>{s.forEach(f=>f()),s.length=0},l=(f,u,h,g)=>(f.addEventListener(u,h,g),()=>f.removeEventListener(u,h,g)),a=Ne(()=>[Xt(t),pt(o)],([f,u])=>{if(i(),!f)return;const h=Fd(u)?{...u}:u;s.push(...n.flatMap(g=>r.map(v=>l(f,g,v,h))))},{immediate:!0,flush:"post"}),c=()=>{a(),i()};return Qr(c),c}function Jd(){const e=le(!1),t=Wr();return t&&$e(()=>{e.value=!0},t),e}function Xr(e){const t=Jd();return R(()=>(t.value,!!e()))}function as(e,t={}){const{window:n=Ft}=t,r=Xr(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function");let o;const s=le(!1),i=c=>{s.value=c.matches},l=()=>{o&&("removeEventListener"in o?o.removeEventListener("change",i):o.removeListener(i))},a=lu(()=>{r.value&&(l(),o=n.matchMedia(pt(e)),"addEventListener"in o?o.addEventListener("change",i):o.addListener(i),s.value=o.matches)});return Qr(()=>{a(),l(),o=void 0}),s}function _i(e,t={}){const{controls:n=!1,navigator:r=ea}=t,o=Xr(()=>r&&"permissions"in r);let s;const i=typeof e=="string"?{name:e}:e,l=le(),a=()=>{s&&(l.value=s.state)},c=Bd(async()=>{if(o.value){if(!s)try{s=await r.permissions.query(i),lt(s,"change",a),a()}catch{l.value="prompt"}return s}});return c(),n?{state:l,isSupported:o,query:c}:l}function Yd(e={}){const{navigator:t=ea,read:n=!1,source:r,copiedDuring:o=1500,legacy:s=!1}=e,i=Xr(()=>t&&"clipboard"in t),l=_i("clipboard-read"),a=_i("clipboard-write"),c=R(()=>i.value||s),f=le(""),u=le(!1),h=qd(()=>u.value=!1,o);function g(){i.value&&O(l.value)?t.clipboard.readText().then(w=>{f.value=w}):f.value=L()}c.value&&n&<(["copy","cut"],g);async function v(w=pt(r)){c.value&&w!=null&&(i.value&&O(a.value)?await t.clipboard.writeText(w):S(w),f.value=w,u.value=!0,h.start())}function S(w){const m=document.createElement("textarea");m.value=w??"",m.style.position="absolute",m.style.opacity="0",document.body.appendChild(m),m.select(),document.execCommand("copy"),m.remove()}function L(){var w,m,k;return(k=(m=(w=document==null?void 0:document.getSelection)==null?void 0:w.call(document))==null?void 0:m.toString())!=null?k:""}function O(w){return w==="granted"||w==="prompt"}return{isSupported:c,text:f,copied:u,copy:v}}const hr=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},mr="__vueuse_ssr_handlers__",Qd=Xd();function Xd(){return mr in hr||(hr[mr]=hr[mr]||{}),hr[mr]}function Zd(e,t){return Qd[e]||t}function ep(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}const tp={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},bi="vueuse-storage";function ta(e,t,n,r={}){var o;const{flush:s="pre",deep:i=!0,listenToStorageChanges:l=!0,writeDefaults:a=!0,mergeDefaults:c=!1,shallow:f,window:u=Ft,eventFilter:h,onError:g=x=>{console.error(x)},initOnMounted:v}=r,S=(f?kn:le)(typeof t=="function"?t():t);if(!n)try{n=Zd("getDefaultStorage",()=>{var x;return(x=Ft)==null?void 0:x.localStorage})()}catch(x){g(x)}if(!n)return S;const L=pt(t),O=ep(L),w=(o=r.serializer)!=null?o:tp[O],{pause:m,resume:k}=Ud(S,()=>M(S.value),{flush:s,deep:i,eventFilter:h});u&&l&&ls(()=>{lt(u,"storage",b),lt(u,bi,z),v&&b()}),v||b();function N(x,D){u&&u.dispatchEvent(new CustomEvent(bi,{detail:{key:e,oldValue:x,newValue:D,storageArea:n}}))}function M(x){try{const D=n.getItem(e);if(x==null)N(D,null),n.removeItem(e);else{const y=w.write(x);D!==y&&(n.setItem(e,y),N(D,y))}}catch(D){g(D)}}function $(x){const D=x?x.newValue:n.getItem(e);if(D==null)return a&&L!=null&&n.setItem(e,w.write(L)),L;if(!x&&c){const y=w.read(D);return typeof c=="function"?c(y,L):O==="object"&&!Array.isArray(y)?{...L,...y}:y}else return typeof D!="string"?D:w.read(D)}function b(x){if(!(x&&x.storageArea!==n)){if(x&&x.key==null){S.value=L;return}if(!(x&&x.key!==e)){m();try{(x==null?void 0:x.newValue)!==w.write(S.value)&&(S.value=$(x))}catch(D){g(D)}finally{x?Sn(k):k()}}}}function z(x){b(x.detail)}return S}function np(e){return as("(prefers-color-scheme: dark)",e)}function rp(e,t,n={}){const{window:r=Ft,...o}=n;let s;const i=Xr(()=>r&&"ResizeObserver"in r),l=()=>{s&&(s.disconnect(),s=void 0)},a=R(()=>Array.isArray(e)?e.map(u=>Xt(u)):[Xt(e)]),c=Ne(a,u=>{if(l(),i.value&&r){s=new ResizeObserver(t);for(const h of u)h&&s.observe(h,o)}},{immediate:!0,flush:"post"}),f=()=>{l(),c()};return Qr(f),{isSupported:i,stop:f}}function op(e,t={width:0,height:0},n={}){const{window:r=Ft,box:o="content-box"}=n,s=R(()=>{var u,h;return(h=(u=Xt(e))==null?void 0:u.namespaceURI)==null?void 0:h.includes("svg")}),i=le(t.width),l=le(t.height),{stop:a}=rp(e,([u])=>{const h=o==="border-box"?u.borderBoxSize:o==="content-box"?u.contentBoxSize:u.devicePixelContentBoxSize;if(r&&s.value){const g=Xt(e);if(g){const v=g.getBoundingClientRect();i.value=v.width,l.value=v.height}}else if(h){const g=Array.isArray(h)?h:[h];i.value=g.reduce((v,{inlineSize:S})=>v+S,0),l.value=g.reduce((v,{blockSize:S})=>v+S,0)}else i.value=u.contentRect.width,l.value=u.contentRect.height},n);ls(()=>{const u=Xt(e);u&&(i.value="offsetWidth"in u?u.offsetWidth:t.width,l.value="offsetHeight"in u?u.offsetHeight:t.height)});const c=Ne(()=>Xt(e),u=>{i.value=u?t.width:0,l.value=u?t.height:0});function f(){a(),c()}return{width:i,height:l,stop:f}}function sp(e={}){const{window:t=Ft,behavior:n="auto"}=e;if(!t)return{x:le(0),y:le(0)};const r=le(t.scrollX),o=le(t.scrollY),s=R({get(){return r.value},set(l){scrollTo({left:l,behavior:n})}}),i=R({get(){return o.value},set(l){scrollTo({top:l,behavior:n})}});return lt(t,"scroll",()=>{r.value=t.scrollX,o.value=t.scrollY},{capture:!1,passive:!0}),{x:s,y:i}}function ip(e={}){const{window:t=Ft,initialWidth:n=Number.POSITIVE_INFINITY,initialHeight:r=Number.POSITIVE_INFINITY,listenOrientation:o=!0,includeScrollbar:s=!0}=e,i=le(n),l=le(r),a=()=>{t&&(s?(i.value=t.innerWidth,l.value=t.innerHeight):(i.value=t.document.documentElement.clientWidth,l.value=t.document.documentElement.clientHeight))};if(a(),ls(a),lt("resize",a,{passive:!0}),o){const c=as("(orientation: portrait)");Ne(c,()=>a())}return{width:i,height:l}}const yi=async(e,t)=>{const{path:n,query:r}=e.currentRoute.value,{scrollBehavior:o}=e.options;e.options.scrollBehavior=void 0,await e.replace({path:n,query:r,hash:t}),e.options.scrollBehavior=o},lp=({headerLinkSelector:e,headerAnchorSelector:t,delay:n,offset:r=5})=>{const o=Wt();lt("scroll",Wd(()=>{var v,S;const i=Math.max(window.scrollY,document.documentElement.scrollTop,document.body.scrollTop);if(Math.abs(i-0)u.some(O=>O.hash===L.hash));for(let L=0;L =(((v=O.parentElement)==null?void 0:v.offsetTop)??0)-r,k=!w||i<(((S=w.parentElement)==null?void 0:S.offsetTop)??0)-r;if(!(m&&k))continue;const M=decodeURIComponent(o.currentRoute.value.hash),$=decodeURIComponent(O.hash);if(M===$)return;if(f){for(let b=L+1;b {const t=Cn();return R(()=>e[t.value]??{})},hp=()=>{const e=Rd();return R(()=>Object.keys(e.value))},mo=(e,t)=>{var r;const n=(r=Wr())==null?void 0:r.appContext.components;return n?e in n||Xe(e)in n||Qn(Xe(e))in n:!1},na=e=>new Promise(t=>setTimeout(t,e)),mp=({selector:e=[...new Array(6)].map((r,o)=>`#vp-content h${o+1}`).join(","),levels:t=2,ignore:n=[]}={})=>{const r=Array.from(document.querySelectorAll(e)).filter(o=>o.id&&o.hasChildNodes()).map(o=>{const s=Number(o.tagName[1]);return{element:o,title:gp(o,n),link:"#"+o.id,slug:o.id,level:s}});return vp(r,t)},gp=(e,t=[])=>{let n="";if(t.length){const r=e.cloneNode(!0);r.querySelectorAll(t.join(",")).forEach(o=>o.remove()),n=r.textContent||""}else n=e.textContent||"";return n.trim()},vp=(e,t=2)=>{if(t===!1)return[];const[n,r]=typeof t=="number"?[t,t]:t==="deep"?[2,6]:t;e=e.filter(s=>s.level>=n&&s.level<=r);const o=[];e:for(let s=0;s =0;l--){const a=e[l];if(a.level ot(e)&&e.startsWith(t),_p=Object.keys,oa=e=>ra(e,"/");var bp={"/en/":{backToTop:"Back to top"},"/fr/":{backToTop:"Retour en haut"},"/":{backToTop:"Back to top"}};const yp=pe({name:"BackToTop",setup(){const e=at(),t=cs(bp),n=kn(),{height:r}=op(n),{height:o}=ip(),{y:s}=sp(),i=R(()=>e.value.backToTop!==!1&&s.value>100),l=R(()=>s.value/(r.value-o.value)*100);return $e(()=>{n.value=document.body}),()=>ie(En,{name:"back-to-top"},()=>i.value?ie("button",{type:"button",class:"vp-back-to-top-button","aria-label":t.value.backToTop,onClick:()=>{window.scrollTo({top:0,behavior:"smooth"})}},[ie("span",{class:"vp-scroll-progress",role:"progressbar","aria-labelledby":"loadinglabel","aria-valuenow":l.value},ie("svg",ie("circle",{cx:"26",cy:"26",r:"24",fill:"none",stroke:"currentColor","stroke-width":"4","stroke-dasharray":`${Math.PI*l.value*.48} ${Math.PI*(100-l.value)*.48}`}))),ie("div",{class:"back-to-top-icon"})]):null)}}),wp=mt({rootComponents:[yp]}),kp=Object.freeze(Object.defineProperty({__proto__:null,default:wp},Symbol.toStringTag,{value:"Module"})),Sp=/language-(shellscript|shell|bash|sh|zsh)/,Ep=({delay:e=500,duration:t=2e3,locales:n,selector:r,showInMobile:o,ignoreSelector:s=[],transform:i})=>{const l=as("(max-width: 419px)"),a=R(()=>!l.value||o),c=cs(n),f=xn(),u=L=>{var w;if(L.hasAttribute("copy-code-registered"))return;const O=document.createElement("button");O.type="button",O.classList.add("vp-copy-code-button"),O.setAttribute("aria-label",c.value.copy),O.setAttribute("data-copied",c.value.copied),(w=L.parentElement)==null||w.insertBefore(O,L),L.setAttribute("copy-code-registered","")};Ne(()=>[f.value.path,a.value],async()=>{document.body.classList.toggle("copy-code-disabled",!a.value),a.value&&(await Sn(),await na(e),document.querySelectorAll(r.join(",")).forEach(u))},{immediate:!0});const{copy:g}=Yd({legacy:!0}),v=new WeakMap,S=(L,O,w)=>{const m=O.cloneNode(!0);s.length&&m.querySelectorAll(s.join(",")).forEach(N=>N.remove()),i&&i(m);let k=m.textContent||"";Sp.test(L.className)&&(k=k.replace(/^ *(\$|>) /gm,"")),g(k).then(()=>{if(t<=0)return;w.classList.add("copied"),clearTimeout(v.get(w));const N=setTimeout(()=>{w.classList.remove("copied"),w.blur(),v.delete(w)},t);v.set(w,N)})};lt("click",L=>{const O=L.target;if(a.value&&O.matches('div[class*="language-"] > button.vp-copy-code-button')){const w=O.parentElement,m=O.nextElementSibling;if(!w||!m)return;S(w,m,O)}})};var xp={"/en/":{copy:"Copy code",copied:"Copied"},"/fr/":{copy:"Copier le code",copied:"Copié"},"/":{copy:"Copy code",copied:"Copied"}},Cp=['.theme-default-content div[class*="language-"] pre'];const Lp=mt({setup:()=>{Ep({selector:Cp,locales:xp,duration:2e3,delay:500,showInMobile:!1})}}),Pp=Object.freeze(Object.defineProperty({__proto__:null,default:Lp},Symbol.toStringTag,{value:"Module"}));/** + * NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress + * @license MIT + */const ue={settings:{minimum:.08,easing:"ease",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,barSelector:'[role="bar"]',parent:"body",template:''},status:null,set:e=>{const t=ue.isStarted();e=go(e,ue.settings.minimum,1),ue.status=e===1?null:e;const n=ue.render(!t),r=n.querySelector(ue.settings.barSelector),o=ue.settings.speed,s=ue.settings.easing;return n.offsetWidth,Ap(i=>{gr(r,{transform:"translate3d("+wi(e)+"%,0,0)",transition:"all "+o+"ms "+s}),e===1?(gr(n,{transition:"none",opacity:"1"}),n.offsetWidth,setTimeout(()=>{gr(n,{transition:"all "+o+"ms linear",opacity:"0"}),setTimeout(()=>{ue.remove(),i()},o)},o)):setTimeout(()=>i(),o)}),ue},isStarted:()=>typeof ue.status=="number",start:()=>{ue.status||ue.set(0);const e=()=>{setTimeout(()=>{ue.status&&(ue.trickle(),e())},ue.settings.trickleSpeed)};return ue.settings.trickle&&e(),ue},done:e=>!e&&!ue.status?ue:ue.inc(.3+.5*Math.random()).set(1),inc:e=>{let t=ue.status;return t?(typeof e!="number"&&(e=(1-t)*go(Math.random()*t,.1,.95)),t=go(t+e,0,.994),ue.set(t)):ue.start()},trickle:()=>ue.inc(Math.random()*ue.settings.trickleRate),render:e=>{if(ue.isRendered())return document.getElementById("nprogress");ki(document.documentElement,"nprogress-busy");const t=document.createElement("div");t.id="nprogress",t.innerHTML=ue.settings.template;const n=t.querySelector(ue.settings.barSelector),r=e?"-100":wi(ue.status||0),o=document.querySelector(ue.settings.parent);return gr(n,{transition:"all 0 linear",transform:"translate3d("+r+"%,0,0)"}),o!==document.body&&ki(o,"nprogress-custom-parent"),o==null||o.appendChild(t),t},remove:()=>{Si(document.documentElement,"nprogress-busy"),Si(document.querySelector(ue.settings.parent),"nprogress-custom-parent");const e=document.getElementById("nprogress");e&&Tp(e)},isRendered:()=>!!document.getElementById("nprogress")},go=(e,t,n)=>e n?n:e,wi=e=>(-1+e)*100,Ap=function(){const e=[];function t(){const n=e.shift();n&&n(t)}return function(n){e.push(n),e.length===1&&t()}}(),gr=function(){const e=["Webkit","O","Moz","ms"],t={};function n(i){return i.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(l,a){return a.toUpperCase()})}function r(i){const l=document.body.style;if(i in l)return i;let a=e.length;const c=i.charAt(0).toUpperCase()+i.slice(1);let f;for(;a--;)if(f=e[a]+c,f in l)return f;return i}function o(i){return i=n(i),t[i]??(t[i]=r(i))}function s(i,l,a){l=o(l),i.style[l]=a}return function(i,l){for(const a in l){const c=l[a];c!==void 0&&Object.prototype.hasOwnProperty.call(l,a)&&s(i,a,c)}}}(),sa=(e,t)=>(typeof e=="string"?e:us(e)).indexOf(" "+t+" ")>=0,ki=(e,t)=>{const n=us(e),r=n+t;sa(n,t)||(e.className=r.substring(1))},Si=(e,t)=>{const n=us(e);if(!sa(e,t))return;const r=n.replace(" "+t+" "," ");e.className=r.substring(1,r.length-1)},us=e=>(" "+(e.className||"")+" ").replace(/\s+/gi," "),Tp=e=>{e&&e.parentNode&&e.parentNode.removeChild(e)},Op=()=>{$e(()=>{const e=Wt(),t=new Set;t.add(e.currentRoute.value.path),e.beforeEach(n=>{t.has(n.path)||ue.start()}),e.afterEach(n=>{t.add(n.path),ue.done()})})},Rp=mt({setup(){Op()}}),Ip=Object.freeze(Object.defineProperty({__proto__:null,default:Rp},Symbol.toStringTag,{value:"Module"})),Mp=Object.freeze(Object.defineProperty({__proto__:null},Symbol.toStringTag,{value:"Module"})),$p=JSON.parse(`{"logo":"logo_worldline.png","repo":"worldline/learning-kotlin","locales":{"/en/":{"selectLanguageName":"English","sidebar":["/en/presentation/","/en/kotlin-features/","/en/backend-development/","/en/front-development/","/en/other-technologies/","/en/kotlin-features-advanced/","/en/workshops/"]},"/fr/":{"selectLanguageName":"Français","sidebar":["/fr/presentation/","/fr/kotlin-features/","/fr/backend-development/","/fr/front-development/","/fr/other-technologies/","/fr/kotlin-features-advanced/","/fr/workshops/"]},"/":{"selectLanguageName":"English"}},"colorMode":"auto","colorModeSwitch":true,"navbar":[],"selectLanguageText":"Languages","selectLanguageAriaLabel":"Select language","sidebar":"heading","sidebarDepth":2,"editLink":true,"editLinkText":"Edit this page","lastUpdated":true,"lastUpdatedText":"Last Updated","contributors":true,"contributorsText":"Contributors","notFound":["There's nothing here.","How did we get here?","That's a Four-Oh-Four.","Looks like we've got some broken links."],"backToHome":"Take me home","openInNewWindow":"open in new window","toggleColorMode":"toggle color mode","toggleSidebar":"toggle sidebar"}`),Hp=le($p),ia=()=>Hp,la=Symbol(""),Np=()=>{const e=Ve(la);if(!e)throw new Error("useThemeLocaleData() is called without provider.");return e},jp=(e,t)=>{const{locales:n,...r}=e;return{...r,...n==null?void 0:n[t]}},Fp=mt({enhance({app:e}){const t=ia(),n=e._context.provides[ns],r=R(()=>jp(t.value,n.routeLocale.value));e.provide(la,r),Object.defineProperties(e.config.globalProperties,{$theme:{get(){return t.value}},$themeLocale:{get(){return r.value}}})}}),Dp=Object.freeze(Object.defineProperty({__proto__:null,default:Fp},Symbol.toStringTag,{value:"Module"})),Vp=()=>ia(),Le=()=>Np(),aa=Symbol(""),Bp=e=>{const t=(n=e.value)=>{const r=window.document.documentElement;r.classList.toggle("dark",n),r.dataset.theme=n?"dark":"light"};$e(()=>{Ne(e,t,{immediate:!0})}),Br(()=>t())},fs=()=>{const e=Ve(aa);if(!e)throw new Error("useDarkMode() is called without provider.");return e},zp=()=>{const e=Le(),t=np(),n=ta("vuepress-color-scheme",e.value.colorMode),r=R({get(){return e.value.colorModeSwitch?n.value==="auto"?t.value:n.value==="dark":e.value.colorMode==="dark"},set(o){o===t.value?n.value="auto":n.value=o?"dark":"light"}});Mt(aa,r),Bp(r)};let vo=null,Rn=null;const Wp={wait:()=>vo,pending:()=>{vo=new Promise(e=>Rn=e)},resolve:()=>{Rn==null||Rn(),vo=null,Rn=null}},ca=()=>Wp,wn=(e,t)=>{const{notFound:n,meta:r,path:o}=Gn(e,t);return n?{text:o,link:o}:{text:r.title||o,link:o}},Ei=e=>decodeURI(e).replace(/#.*$/,"").replace(/(index)?\.(md|html)$/,""),Kp=(e,t)=>{if(t.hash===e)return!0;const n=Ei(t.path),r=Ei(e);return n===r},ua=(e,t)=>e.link&&Kp(e.link,t)?!0:"children"in e?e.children.some(n=>ua(n,t)):!1,fa=e=>!uf(e)&&!or(e),da=e=>!Ur(e)||/github\.com/.test(e)?"GitHub":/bitbucket\.org/.test(e)?"Bitbucket":/gitlab\.com/.test(e)?"GitLab":/gitee\.com/.test(e)?"Gitee":null,Up={GitHub:":repo/edit/:branch/:path",GitLab:":repo/-/edit/:branch/:path",Gitee:":repo/edit/:branch/:path",Bitbucket:":repo/src/:branch/:path?mode=edit&spa=0&at=:branch&fileviewer=file-view-default"},qp=({docsRepo:e,editLinkPattern:t})=>{if(t)return t;const n=da(e);return n!==null?Up[n]:null},Gp=({docsRepo:e,docsBranch:t,docsDir:n,filePathRelative:r,editLinkPattern:o})=>{if(!r)return null;const s=qp({docsRepo:e,editLinkPattern:o});return s?s.replace(/:repo/,Ur(e)?e:`https://github.com/${e}`).replace(/:branch/,t).replace(/:path/,jl(`${Nl(n)}/${r}`)):null},vn=(e="",t="")=>oa(t)||or(t)?t:`${bf(e)}${t}`,wr=le([]),Jp=()=>{const e=Wt(),t=Le(),n=at(),r=R(()=>n.value.sidebarDepth??t.value.sidebarDepth??2);e.beforeEach((s,i)=>{s.path!==i.path&&(wr.value=[])});const o=()=>{if(r.value<=0){wr.value=[];return}wr.value=mp({selector:[...new Array(6)].map((s,i)=>`.theme-default-content h${i+1}`).join(","),levels:[2,r.value+1],ignore:[".vp-badge"]})};Ne(r,o),$e(o)},Yp=()=>wr,pa=Symbol("sidebarItems"),ds=()=>{const e=Ve(pa);if(!e)throw new Error("useSidebarItems() is called without provider.");return e},Qp=()=>{const e=Le(),t=at(),n=xn(),r=wt(),o=Cn(),s=Yp(),i=R(()=>t.value.home?!1:t.value.sidebar??e.value.sidebar??"heading"),l=R(()=>Xp(i.value,n.value,r.path,o.value,s.value));Mt(pa,l)},Xp=(e,t,n,r,o)=>e===!1?[]:e==="heading"?ha(t,o):Array.isArray(e)?ma(e,o,n,r):Zo(e)?eh(e,t,o,n):[],Zp=e=>({text:e.title,link:e.link,children:ps(e.children)}),ps=e=>e?e.map(t=>Zp(t)):[],ha=(e,t)=>[{text:e.title,children:ps(t)}],ma=(e,t,n,r="")=>{const o=(s,i)=>{var a;const l=ot(s)?wn(vn(i,s)):ot(s.link)?{...s,link:fa(s.link)?wn(vn(i,s.link)).link:s.link}:s;if("children"in l)return{...l,children:l.children.map(c=>o(c,vn(i,l.prefix)))};if(l.link===n){const c=((a=t[0])==null?void 0:a.level)===1?t[0].children:t;return{...l,children:ps(c)}}return l};return e.map(s=>o(s,r))},eh=(e,t,n,r)=>{const o=_p(e).sort((s,i)=>i.length-s.length);for(const s of o)if(ra(decodeURI(r),s)){const i=e[s];return i?i==="heading"?ha(t,n):ma(i,n,r,s):[]}return console.warn(`${decodeURI(r)} is missing sidebar config.`),[]},th=pe({__name:"Badge",props:{type:{default:"tip"},text:{default:""},vertical:{default:void 0}},setup(e,{expose:t}){t();const n={};return Object.defineProperty(n,"__isScriptSetup",{enumerable:!1,value:!0}),n}}),ke=(e,t)=>{const n=e.__vccOpts||e;for(const[r,o]of t)n[r]=o;return n};function nh(e,t,n,r,o,s){return K(),Z("span",{class:qe(["vp-badge",n.type]),style:Xn({verticalAlign:n.vertical})},[Se(e.$slots,"default",{},()=>[jt(Ee(n.text),1)])],6)}const rh=ke(th,[["render",nh],["__file","Badge.vue"]]),oh=pe({name:"CodeGroup",slots:Object,setup(e,{slots:t}){const n=le([]),r=le(-1),o=ta("vuepress-code-group",{}),s=R(()=>n.value.map(c=>c.innerText).join(","));$e(()=>{Ne(()=>o.value[s.value],(c=-1)=>{r.value!==c&&(r.value=c)},{immediate:!0}),Ne(r,c=>{o.value[s.value]!==c&&(o.value[s.value]=c)})});const i=(c=r.value)=>{c {c>0?r.value=c-1:r.value=n.value.length-1,n.value[r.value].focus()},a=(c,f)=>{c.key===" "||c.key==="Enter"?(c.preventDefault(),r.value=f):c.key==="ArrowRight"?(c.preventDefault(),i(f)):c.key==="ArrowLeft"&&(c.preventDefault(),l(f))};return()=>{var f;const c=(((f=t.default)==null?void 0:f.call(t))||[]).filter(u=>u.type.name==="CodeGroupItem").map(u=>(u.props===null&&(u.props={}),u));return c.length===0?null:(r.value<0||r.value>c.length-1?(r.value=c.findIndex(u=>u.props.active===""||u.props.active===!0),r.value===-1&&(r.value=0)):c.forEach((u,h)=>{u.props.active=h===r.value}),ie("div",{class:"code-group"},[ie("div",{class:"code-group-nav",role:"tablist"},c.map((u,h)=>{const g=h===r.value;return ie("button",{ref:v=>{v&&(n.value[h]=v)},class:{"code-group-nav-tab":!0,active:g},role:"tab",ariaSelected:g,onClick:()=>r.value=h,onKeydown:v=>a(v,h)},u.props.title)})),c]))}}}),sh=pe({name:"CodeGroupItem",__name:"CodeGroupItem",props:{title:{},active:{type:Boolean}},setup(e,{expose:t}){t();const n={};return Object.defineProperty(n,"__isScriptSetup",{enumerable:!1,value:!0}),n}});function ih(e,t,n,r,o,s){return K(),Z("div",{class:qe(["code-group-item",{active:n.active}]),role:"tabpanel"},[Se(e.$slots,"default")],2)}const lh=ke(sh,[["render",ih],["__file","CodeGroupItem.vue"]]),ah=pe({__name:"VPHomeFeatures",setup(e,{expose:t}){t();const n=at(),r=R(()=>n.value.features??[]),o={frontmatter:n,features:r};return Object.defineProperty(o,"__isScriptSetup",{enumerable:!1,value:!0}),o}}),ch={key:0,class:"vp-features"};function uh(e,t,n,r,o,s){return r.features.length?(K(),Z("div",ch,[(K(!0),Z(_e,null,Ht(r.features,i=>(K(),Z("div",{key:i.title,class:"vp-feature"},[ne("h2",null,Ee(i.title),1),ne("p",null,Ee(i.details),1)]))),128))])):Te("",!0)}const fh=ke(ah,[["render",uh],["__file","VPHomeFeatures.vue"]]),dh=pe({__name:"VPHomeFooter",setup(e,{expose:t}){t();const n=at(),r=R(()=>n.value.footer),o=R(()=>n.value.footerHtml),s={frontmatter:n,footer:r,footerHtml:o};return Object.defineProperty(s,"__isScriptSetup",{enumerable:!1,value:!0}),s}}),ph=["innerHTML"],hh=["textContent"];function mh(e,t,n,r,o,s){return r.footer?(K(),Z(_e,{key:0},[r.footerHtml?(K(),Z("div",{key:0,class:"vp-footer",innerHTML:r.footer},null,8,ph)):(K(),Z("div",{key:1,class:"vp-footer",textContent:Ee(r.footer)},null,8,hh))],64)):Te("",!0)}const gh=ke(dh,[["render",mh],["__file","VPHomeFooter.vue"]]),vh=pe({__name:"VPHomeHero",setup(e,{expose:t}){t();const n=at(),r=rs(),o=fs(),s=R(()=>o.value&&n.value.heroImageDark!==void 0?n.value.heroImageDark:n.value.heroImage),i=R(()=>n.value.heroAlt||a.value||"hero"),l=R(()=>n.value.heroHeight||280),a=R(()=>n.value.heroText===null?null:n.value.heroText||r.value.title||"Hello"),c=R(()=>n.value.tagline===null?null:n.value.tagline||r.value.description||"Welcome to your VuePress site"),f=R(()=>Array.isArray(n.value.actions)?n.value.actions.map(({text:g,link:v,type:S="primary"})=>({text:g,link:v,type:S})):[]),h={frontmatter:n,siteLocale:r,isDarkMode:o,heroImage:s,heroAlt:i,heroHeight:l,heroText:a,tagline:c,actions:f,HomeHeroImage:()=>{if(!s.value)return null;const g=ie("img",{class:"vp-hero-image",src:Yr(s.value),alt:i.value,height:l.value});return n.value.heroImageDark===void 0?g:ie(os,()=>g)},get AutoLink(){return Ln}};return Object.defineProperty(h,"__isScriptSetup",{enumerable:!1,value:!0}),h}}),_h={class:"vp-hero"},bh={key:0,id:"main-title"},yh={key:1,class:"vp-hero-description"},wh={key:2,class:"vp-hero-actions"};function kh(e,t,n,r,o,s){return K(),Z("header",_h,[oe(r.HomeHeroImage),r.heroText?(K(),Z("h1",bh,Ee(r.heroText),1)):Te("",!0),r.tagline?(K(),Z("p",yh,Ee(r.tagline),1)):Te("",!0),r.actions.length?(K(),Z("p",wh,[(K(!0),Z(_e,null,Ht(r.actions,i=>(K(),xe(r.AutoLink,{key:i.text,class:qe(["vp-hero-action-button",[i.type]]),config:i},null,8,["class","config"]))),128))])):Te("",!0)])}const Sh=ke(vh,[["render",kh],["__file","VPHomeHero.vue"]]),Eh=pe({__name:"VPHome",setup(e,{expose:t}){t();const n={VPHomeFeatures:fh,VPHomeFooter:gh,VPHomeHero:Sh,get Content(){return ss}};return Object.defineProperty(n,"__isScriptSetup",{enumerable:!1,value:!0}),n}}),xh={class:"vp-home"},Ch={class:"theme-default-content"};function Lh(e,t,n,r,o,s){return K(),Z("main",xh,[oe(r.VPHomeHero),oe(r.VPHomeFeatures),ne("div",Ch,[oe(r.Content)]),oe(r.VPHomeFooter)])}const Ph=ke(Eh,[["render",Lh],["__file","VPHome.vue"]]),Ah="719px",Th={mobile:Ah};var Jn;(function(e){e.MOBILE="mobile"})(Jn||(Jn={}));const Oh={[Jn.MOBILE]:Number.parseInt(Th.mobile.replace("px",""),10)},ga=(e,t)=>{const n=Oh[e];Number.isInteger(n)&&(lt("orientationchange",()=>t(n),!1),lt("resize",()=>t(n),!1),$e(()=>{t(n)}))},Rh=pe({__name:"VPNavbarBrand",setup(e,{expose:t}){t();const n=Cn(),r=rs(),o=Le(),s=fs(),i=R(()=>o.value.home||n.value),l=R(()=>r.value.title),a=R(()=>s.value&&o.value.logoDark!==void 0?o.value.logoDark:o.value.logo),c=R(()=>o.value.logoAlt??l.value),f=R(()=>l.value.toLocaleUpperCase().trim()===c.value.toLocaleUpperCase().trim()),h={routeLocale:n,siteLocale:r,themeLocale:o,isDarkMode:s,navbarBrandLink:i,navbarBrandTitle:l,navbarBrandLogo:a,navbarBrandLogoAlt:c,navBarLogoAltMatchesTitle:f,NavbarBrandLogo:()=>{if(!a.value)return null;const g=ie("img",{class:"vp-site-logo",src:Yr(a.value),alt:c.value});return o.value.logoDark===void 0?g:ie(os,()=>g)},get RouteLink(){return Jr}};return Object.defineProperty(h,"__isScriptSetup",{enumerable:!1,value:!0}),h}}),Ih=["aria-hidden"];function Mh(e,t,n,r,o,s){return K(),xe(r.RouteLink,{to:r.navbarBrandLink},{default:Ae(()=>[oe(r.NavbarBrandLogo),r.navbarBrandTitle?(K(),Z("span",{key:0,class:qe(["vp-site-name",{"vp-hide-mobile":r.navbarBrandLogo}]),"aria-hidden":r.navBarLogoAltMatchesTitle},Ee(r.navbarBrandTitle),11,Ih)):Te("",!0)]),_:1},8,["to"])}const $h=ke(Rh,[["render",Mh],["__file","VPNavbarBrand.vue"]]),va=(e,t="")=>ot(e)?wn(vn(t,e)):"children"in e?{...e,children:e.children.map(n=>va(n,vn(t,e.prefix)))}:{...e,link:fa(e.link)?wn(vn(t,e.link)).link:e.link},Hh=()=>{const e=Le();return R(()=>(e.value.navbar||[]).map(t=>va(t)))},Nh=()=>{const e=Le(),t=R(()=>e.value.repo),n=R(()=>t.value?da(t.value):null),r=R(()=>t.value&&!Ur(t.value)?`https://github.com/${t.value}`:t.value),o=R(()=>r.value?e.value.repoLabel?e.value.repoLabel:n.value===null?"Source":n.value:null);return R(()=>!r.value||!o.value?[]:[{text:o.value,link:r.value}])},jh=()=>{const e=wt(),t=hp(),n=Cn(),r=Yl(),o=rs(),s=Vp(),i=Le();return R(()=>{const l=Object.keys(r.value.locales);if(l.length<2)return[];const a=e.path,c=e.fullPath;return[{text:`${i.value.selectLanguageText}`,ariaLabel:`${i.value.selectLanguageAriaLabel??i.value.selectLanguageText}`,children:l.map(u=>{var O,w;const h=((O=r.value.locales)==null?void 0:O[u])??{},g=((w=s.value.locales)==null?void 0:w[u])??{},v=`${h.lang}`,S=g.selectLanguageName??v;if(v===o.value.lang)return{text:S,activeMatch:".",link:e.fullPath};const L=a.replace(n.value,u);return{text:S,link:t.value.some(m=>m===L)?c.replace(a,L):g.home??u}})}]})},Fh=pe({__name:"VPDropdownTransition",setup(e,{expose:t}){t();const o={setHeight:s=>{s.style.height=s.scrollHeight+"px"},unsetHeight:s=>{s.style.height=""}};return Object.defineProperty(o,"__isScriptSetup",{enumerable:!1,value:!0}),o}});function Dh(e,t,n,r,o,s){return K(),xe(En,{name:"vp-dropdown",onEnter:r.setHeight,onAfterEnter:r.unsetHeight,onBeforeLeave:r.setHeight},{default:Ae(()=>[Se(e.$slots,"default")]),_:3})}const _a=ke(Fh,[["render",Dh],["__file","VPDropdownTransition.vue"]]),Vh=pe({__name:"VPNavbarDropdown",props:{item:{}},setup(e,{expose:t}){t();const n=e,{item:r}=Uo(n),o=wt(),s=le(!1),i=R(()=>r.value.ariaLabel||r.value.text),l=(f,u)=>u[u.length-1]===f,a=f=>{const u=f.detail===0;s.value=u?!s.value:!1};Ne(()=>o.path,()=>{s.value=!1});const c={props:n,item:r,route:o,open:s,dropdownAriaLabel:i,isLastItemOfArray:l,handleDropdown:a,VPDropdownTransition:_a,get AutoLink(){return Ln}};return Object.defineProperty(c,"__isScriptSetup",{enumerable:!1,value:!0}),c}}),Bh=["aria-label"],zh={class:"title"},Wh=ne("span",{class:"arrow down"},null,-1),Kh=["aria-label"],Uh={class:"title"},qh={class:"vp-navbar-dropdown"},Gh={class:"vp-navbar-dropdown-subtitle"},Jh={key:1},Yh={class:"vp-navbar-dropdown-subitem-wrapper"};function Qh(e,t,n,r,o,s){return K(),Z("div",{class:qe(["vp-navbar-dropdown-wrapper",{open:r.open}])},[ne("button",{class:"vp-navbar-dropdown-title",type:"button","aria-label":r.dropdownAriaLabel,onClick:r.handleDropdown},[ne("span",zh,Ee(r.item.text),1),Wh],8,Bh),ne("button",{class:"vp-navbar-dropdown-title-mobile",type:"button","aria-label":r.dropdownAriaLabel,onClick:t[0]||(t[0]=i=>r.open=!r.open)},[ne("span",Uh,Ee(r.item.text),1),ne("span",{class:qe(["arrow",r.open?"down":"right"])},null,2)],8,Kh),oe(r.VPDropdownTransition,null,{default:Ae(()=>[Cr(ne("ul",qh,[(K(!0),Z(_e,null,Ht(r.item.children,i=>(K(),Z("li",{key:i.text,class:"vp-navbar-dropdown-item"},["children"in i?(K(),Z(_e,{key:0},[ne("h4",Gh,[i.link?(K(),xe(r.AutoLink,{key:0,config:i,onFocusout:l=>r.isLastItemOfArray(i,r.item.children)&&i.children.length===0&&(r.open=!1)},null,8,["config","onFocusout"])):(K(),Z("span",Jh,Ee(i.text),1))]),ne("ul",Yh,[(K(!0),Z(_e,null,Ht(i.children,l=>(K(),Z("li",{key:l.link,class:"vp-navbar-dropdown-subitem"},[oe(r.AutoLink,{config:l,onFocusout:a=>r.isLastItemOfArray(l,i.children)&&r.isLastItemOfArray(i,r.item.children)&&(r.open=!1)},null,8,["config","onFocusout"])]))),128))])],64)):(K(),xe(r.AutoLink,{key:1,config:i,onFocusout:l=>r.isLastItemOfArray(i,r.item.children)&&(r.open=!1)},null,8,["config","onFocusout"]))]))),128))],512),[[Ir,r.open]])]),_:1})],2)}const Xh=ke(Vh,[["render",Qh],["__file","VPNavbarDropdown.vue"]]),Zh=pe({__name:"VPNavbarItems",setup(e,{expose:t}){t();const n=Hh(),r=jh(),o=Nh(),s=le(!1),i=R(()=>Le().value.navbarLabel??"site navigation"),l=R(()=>[...n.value,...r.value,...o.value]);ga(Jn.MOBILE,c=>{s.value=window.innerWidth (K(),Z("div",{key:i.text,class:"vp-navbar-item"},["children"in i?(K(),xe(r.VPNavbarDropdown,{key:0,class:qe({mobile:r.isMobile}),item:i},null,8,["class","item"])):(K(),xe(r.AutoLink,{key:1,config:i},null,8,["config"]))]))),128))],8,em)):Te("",!0)}const ba=ke(Zh,[["render",tm],["__file","VPNavbarItems.vue"]]),nm={},rm={class:"dark-icon",viewBox:"0 0 32 32"},om=ne("path",{d:"M13.502 5.414a15.075 15.075 0 0 0 11.594 18.194a11.113 11.113 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1.002 1.002 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.072 13.072 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3z",fill:"currentColor"},null,-1),sm=[om];function im(e,t){return K(),Z("svg",rm,sm)}const lm=ke(nm,[["render",im],["__file","VPDarkIcon.vue"]]),am={},cm={class:"light-icon",viewBox:"0 0 32 32"},um=bu(' ',9),fm=[um];function dm(e,t){return K(),Z("svg",cm,fm)}const pm=ke(am,[["render",dm],["__file","VPLightIcon.vue"]]),hm=pe({__name:"VPToggleColorModeButton",setup(e,{expose:t}){t();const n=Le(),r=fs(),s={themeLocale:n,isDarkMode:r,toggleColorMode:()=>{r.value=!r.value},VPDarkIcon:lm,VPLightIcon:pm};return Object.defineProperty(s,"__isScriptSetup",{enumerable:!1,value:!0}),s}}),mm=["title"];function gm(e,t,n,r,o,s){return K(),Z("button",{class:"vp-toggle-color-mode-button",title:r.themeLocale.toggleColorMode,onClick:r.toggleColorMode},[Cr(oe(r.VPLightIcon,null,null,512),[[Ir,!r.isDarkMode]]),Cr(oe(r.VPDarkIcon,null,null,512),[[Ir,r.isDarkMode]])],8,mm)}const vm=ke(hm,[["render",gm],["__file","VPToggleColorModeButton.vue"]]),_m=pe({__name:"VPToggleSidebarButton",emits:["toggle"],setup(e,{expose:t}){t();const r={themeLocale:Le()};return Object.defineProperty(r,"__isScriptSetup",{enumerable:!1,value:!0}),r}}),bm=["title"],ym=ne("div",{class:"icon","aria-hidden":"true"},[ne("span"),ne("span"),ne("span")],-1),wm=[ym];function km(e,t,n,r,o,s){return K(),Z("div",{class:"vp-toggle-sidebar-button",title:r.themeLocale.toggleSidebar,"aria-expanded":"false",role:"button",tabindex:"0",onClick:t[0]||(t[0]=i=>e.$emit("toggle"))},wm,8,bm)}const Sm=ke(_m,[["render",km],["__file","VPToggleSidebarButton.vue"]]),Em=pe({__name:"VPNavbar",emits:["toggle-sidebar"],setup(e,{expose:t}){t();const n=Le(),r=le(null),o=le(null),s=le(0),i=R(()=>s.value?{maxWidth:s.value+"px"}:{}),l=(c,f)=>{var g,v,S;const u=(S=(v=(g=c==null?void 0:c.ownerDocument)==null?void 0:g.defaultView)==null?void 0:v.getComputedStyle(c,null))==null?void 0:S[f],h=Number.parseInt(u,10);return Number.isNaN(h)?0:h};ga(Jn.MOBILE,c=>{var u;const f=l(r.value,"paddingLeft")+l(r.value,"paddingRight");window.innerWidth e.$emit("toggle-sidebar"))}),ne("span",Cm,[oe(r.VPNavbarBrand)],512),ne("div",{class:"vp-navbar-items-wrapper",style:Xn(r.linksWrapperStyle)},[Se(e.$slots,"before"),oe(r.VPNavbarItems,{class:"vp-hide-mobile"}),Se(e.$slots,"after"),r.themeLocale.colorModeSwitch?(K(),xe(r.VPToggleColorModeButton,{key:0})):Te("",!0),oe(i)],4)],512)}const Pm=ke(Em,[["render",Lm],["__file","VPNavbar.vue"]]),Am=()=>{const e=Le(),t=xn(),n=at();return R(()=>{var o;return n.value.contributors??e.value.contributors??!0?((o=t.value.git)==null?void 0:o.contributors)??null:null})},Tm=()=>{const e=Le(),t=xn(),n=at();return R(()=>{if(!(n.value.editLink??e.value.editLink??!0))return null;const{repo:o,docsRepo:s=o,docsBranch:i="main",docsDir:l="",editLinkText:a}=e.value;if(!s)return null;const c=Gp({docsRepo:s,docsBranch:i,docsDir:l,filePathRelative:t.value.filePathRelative,editLinkPattern:n.value.editLinkPattern??e.value.editLinkPattern});return c?{text:a??"Edit this page",link:c}:null})},Om=()=>{const e=Le(),t=xn(),n=at();return R(()=>{var s,i;return!(n.value.lastUpdated??e.value.lastUpdated??!0)||!((s=t.value.git)!=null&&s.updatedTime)?null:new Date((i=t.value.git)==null?void 0:i.updatedTime).toLocaleString()})},Rm={},Im={class:"edit-icon",viewBox:"0 0 1024 1024"},Mm=ne("g",{fill:"currentColor"},[ne("path",{d:"M430.818 653.65a60.46 60.46 0 0 1-50.96-93.281l71.69-114.012 7.773-10.365L816.038 80.138A60.46 60.46 0 0 1 859.225 62a60.46 60.46 0 0 1 43.186 18.138l43.186 43.186a60.46 60.46 0 0 1 0 86.373L588.879 565.55l-8.637 8.637-117.466 68.234a60.46 60.46 0 0 1-31.958 11.229z"}),ne("path",{d:"M728.802 962H252.891A190.883 190.883 0 0 1 62.008 771.98V296.934a190.883 190.883 0 0 1 190.883-192.61h267.754a60.46 60.46 0 0 1 0 120.92H252.891a69.962 69.962 0 0 0-69.098 69.099V771.98a69.962 69.962 0 0 0 69.098 69.098h475.911A69.962 69.962 0 0 0 797.9 771.98V503.363a60.46 60.46 0 1 1 120.922 0V771.98A190.883 190.883 0 0 1 728.802 962z"})],-1),$m=[Mm];function Hm(e,t){return K(),Z("svg",Im,$m)}const Nm=ke(Rm,[["render",Hm],["__file","VPEditIcon.vue"]]),jm=pe({__name:"VPPageMeta",setup(e,{expose:t}){t();const n=Le(),r=Tm(),o=Om(),s=Am(),i={themeLocale:n,editLink:r,lastUpdated:o,contributors:s,VPEditIcon:Nm,get AutoLink(){return Ln}};return Object.defineProperty(i,"__isScriptSetup",{enumerable:!1,value:!0}),i}}),Fm={class:"vp-page-meta"},Dm={key:0,class:"vp-meta-item edit-link"},Vm={class:"vp-meta-item git-info"},Bm={key:0,class:"vp-meta-item last-updated"},zm={class:"meta-item-label"},Wm={class:"meta-item-info"},Km={key:1,class:"vp-meta-item contributors"},Um={class:"meta-item-label"},qm={class:"meta-item-info"},Gm=["title"];function Jm(e,t,n,r,o,s){const i=Go("ClientOnly");return K(),Z("footer",Fm,[r.editLink?(K(),Z("div",Dm,[oe(r.AutoLink,{class:"label",config:r.editLink},{before:Ae(()=>[oe(r.VPEditIcon)]),_:1},8,["config"])])):Te("",!0),ne("div",Vm,[r.lastUpdated?(K(),Z("div",Bm,[ne("span",zm,Ee(r.themeLocale.lastUpdatedText)+": ",1),oe(i,null,{default:Ae(()=>[ne("span",Wm,Ee(r.lastUpdated),1)]),_:1})])):Te("",!0),r.contributors&&r.contributors.length?(K(),Z("div",Km,[ne("span",Um,Ee(r.themeLocale.contributorsText)+": ",1),ne("span",qm,[(K(!0),Z(_e,null,Ht(r.contributors,(l,a)=>(K(),Z(_e,{key:a},[ne("span",{class:"contributor",title:`email: ${l.email}`},Ee(l.name),9,Gm),a!==r.contributors.length-1?(K(),Z(_e,{key:0},[jt(", ")],64)):Te("",!0)],64))),128))])])):Te("",!0)])])}const Ym=ke(jm,[["render",Jm],["__file","VPPageMeta.vue"]]),Qm=()=>{const e=Wt(),t=wt();return n=>{n&&(oa(n)?t.path!==n&&e.push(n):or(n)?window==null||window.open(n):e.push(encodeURI(n)))}},xi=(e,t)=>e===!1?!1:ot(e)?wn(e,t):Zo(e)?{...e,link:wn(e.link,t).link}:null,Io=(e,t,n)=>{const r=e.findIndex(s=>s.link===t);if(r!==-1){const s=e[r+n];return s?s.link?s:"prefix"in s&&!Gn(s.prefix).notFound?{...s,link:s.prefix}:null:null}for(const s of e)if("children"in s){const i=Io(s.children,t,n);if(i)return i}const o=e.findIndex(s=>"prefix"in s&&s.prefix===t);if(o!==-1){const s=e[o+n];return s?s.link?s:"prefix"in s&&!Gn(s.prefix).notFound?{...s,link:s.prefix}:null:null}return null},Xm=()=>{const e=at(),t=Le(),n=ds(),r=wt(),o=R(()=>{const i=xi(e.value.prev,r.path);return i===!1?null:i??(t.value.prev===!1?null:Io(n.value,r.path,-1))}),s=R(()=>{const i=xi(e.value.next,r.path);return i===!1?null:i??(t.value.next===!1?null:Io(n.value,r.path,1))});return{prevLink:o,nextLink:s}},Zm=pe({__name:"VPPageNav",setup(e,{expose:t}){t();const n=Le(),r=Qm(),{prevLink:o,nextLink:s}=Xm(),i=R(()=>Le().value.pageNavbarLabel??"page navigation");lt("keydown",a=>{a.altKey&&(a.key==="ArrowRight"?s.value&&(r(s.value.link),a.preventDefault()):a.key==="ArrowLeft"&&o.value&&(r(o.value.link),a.preventDefault()))});const l={themeLocale:n,navigate:r,prevLink:o,nextLink:s,navbarLabel:i,get AutoLink(){return Ln}};return Object.defineProperty(l,"__isScriptSetup",{enumerable:!1,value:!0}),l}}),eg=["aria-label"],tg={class:"hint"},ng=ne("span",{class:"arrow left"},null,-1),rg={class:"link"},og={class:"hint"},sg=ne("span",{class:"arrow right"},null,-1),ig={class:"link"};function lg(e,t,n,r,o,s){return r.prevLink||r.nextLink?(K(),Z("nav",{key:0,class:"vp-page-nav","aria-label":r.navbarLabel},[r.prevLink?(K(),xe(r.AutoLink,{key:0,class:"prev",config:r.prevLink},{default:Ae(()=>[ne("div",tg,[ng,jt(" "+Ee(r.themeLocale.prev??"Prev"),1)]),ne("div",rg,[ne("span",null,Ee(r.prevLink.text),1)])]),_:1},8,["config"])):Te("",!0),r.nextLink?(K(),xe(r.AutoLink,{key:1,class:"next",config:r.nextLink},{default:Ae(()=>[ne("div",og,[jt(Ee(r.themeLocale.next??"Next")+" ",1),sg]),ne("div",ig,[ne("span",null,Ee(r.nextLink.text),1)])]),_:1},8,["config"])):Te("",!0)],8,eg)):Te("",!0)}const ag=ke(Zm,[["render",lg],["__file","VPPageNav.vue"]]),cg=pe({__name:"VPPage",setup(e,{expose:t}){t(),Jp();const n={VPPageMeta:Ym,VPPageNav:ag,get Content(){return ss}};return Object.defineProperty(n,"__isScriptSetup",{enumerable:!1,value:!0}),n}}),ug={class:"vp-page"},fg={class:"theme-default-content"};function dg(e,t,n,r,o,s){return K(),Z("main",ug,[Se(e.$slots,"top"),ne("div",fg,[Se(e.$slots,"content-top"),oe(r.Content),Se(e.$slots,"content-bottom")]),oe(r.VPPageMeta),oe(r.VPPageNav),Se(e.$slots,"bottom")])}const pg=ke(cg,[["render",dg],["__file","VPPage.vue"]]),hg=pe({__name:"VPSidebarItem",props:{item:{},depth:{default:0}},setup(e,{expose:t}){t();const n=e,{item:r,depth:o}=Uo(n),s=wt(),i=Wt(),l=R(()=>"collapsible"in r.value&&r.value.collapsible),a=R(()=>ua(r.value,s)),c=R(()=>({"vp-sidebar-item":!0,"vp-sidebar-heading":o.value===0,active:a.value,collapsible:l.value})),f=R(()=>l.value?a.value:!0),[u,h]=Gd(f.value),g=L=>{l.value&&(L.preventDefault(),h())},v=i.afterEach(L=>{Sn(()=>{u.value=f.value})});Vr(()=>{v()});const S={props:n,item:r,depth:o,route:s,router:i,collapsible:l,isActive:a,itemClass:c,isOpenDefault:f,isOpen:u,toggleIsOpen:h,onClick:g,unregisterRouterHook:v,VPDropdownTransition:_a,get AutoLink(){return Ln}};return Object.defineProperty(S,"__isScriptSetup",{enumerable:!1,value:!0}),S}}),mg={class:"vp-sidebar-children"};function gg(e,t,n,r,o,s){const i=Go("VPSidebarItem",!0);return K(),Z("li",null,[r.item.link?(K(),xe(r.AutoLink,{key:0,class:qe(r.itemClass),config:r.item},null,8,["class","config"])):(K(),Z("p",{key:1,tabindex:"0",class:qe(r.itemClass),onClick:r.onClick,onKeydown:nf(r.onClick,["enter"])},[jt(Ee(r.item.text)+" ",1),r.collapsible?(K(),Z("span",{key:0,class:qe(["arrow",r.isOpen?"down":"right"])},null,2)):Te("",!0)],34)),"children"in r.item&&r.item.children.length?(K(),xe(r.VPDropdownTransition,{key:2},{default:Ae(()=>[Cr(ne("ul",mg,[(K(!0),Z(_e,null,Ht(r.item.children,l=>(K(),xe(i,{key:`${r.depth}${l.text}${l.link}`,item:l,depth:r.depth+1},null,8,["item","depth"]))),128))],512),[[Ir,r.isOpen]])]),_:1})):Te("",!0)])}const vg=ke(hg,[["render",gg],["__file","VPSidebarItem.vue"]]),_g=pe({__name:"VPSidebarItems",setup(e,{expose:t}){t();const n=wt(),r=ds();$e(()=>{Ne(()=>n.hash,s=>{const i=document.querySelector(".vp-sidebar");if(!i)return;const l=document.querySelector(`.vp-sidebar a.vp-sidebar-item[href="${n.path}${s}"]`);if(!l)return;const{top:a,height:c}=i.getBoundingClientRect(),{top:f,height:u}=l.getBoundingClientRect();fa+c&&l.scrollIntoView(!1)})});const o={route:n,sidebarItems:r,VPSidebarItem:vg};return Object.defineProperty(o,"__isScriptSetup",{enumerable:!1,value:!0}),o}}),bg={key:0,class:"vp-sidebar-items"};function yg(e,t,n,r,o,s){return r.sidebarItems.length?(K(),Z("ul",bg,[(K(!0),Z(_e,null,Ht(r.sidebarItems,i=>(K(),xe(r.VPSidebarItem,{key:`${i.text}${i.link}`,item:i},null,8,["item"]))),128))])):Te("",!0)}const wg=ke(_g,[["render",yg],["__file","VPSidebarItems.vue"]]),kg=pe({__name:"VPSidebar",setup(e,{expose:t}){t();const n={VPNavbarItems:ba,VPSidebarItems:wg};return Object.defineProperty(n,"__isScriptSetup",{enumerable:!1,value:!0}),n}}),Sg={class:"vp-sidebar"};function Eg(e,t,n,r,o,s){return K(),Z("aside",Sg,[oe(r.VPNavbarItems),Se(e.$slots,"top"),oe(r.VPSidebarItems),Se(e.$slots,"bottom")])}const xg=ke(kg,[["render",Eg],["__file","VPSidebar.vue"]]),Cg=pe({__name:"Layout",setup(e,{expose:t}){t();const n=xn(),r=at(),o=Le(),s=R(()=>r.value.navbar!==!1&&o.value.navbar!==!1),i=ds(),l=le(!1),a=m=>{l.value=typeof m=="boolean"?m:!l.value},c={x:0,y:0},f=m=>{c.x=m.changedTouches[0].clientX,c.y=m.changedTouches[0].clientY},u=m=>{const k=m.changedTouches[0].clientX-c.x,N=m.changedTouches[0].clientY-c.y;Math.abs(k)>Math.abs(N)&&Math.abs(k)>40&&(k>0&&c.x<=80?a(!0):a(!1))},h=R(()=>r.value.externalLinkIcon??o.value.externalLinkIcon??!0),g=R(()=>[{"no-navbar":!s.value,"no-sidebar":!i.value.length,"sidebar-open":l.value,"external-link-icon":h.value},r.value.pageClass]);let v;$e(()=>{v=Wt().afterEach(()=>{a(!1)})}),Br(()=>{v()});const S=ca(),L=S.resolve,O=S.pending,w={page:n,frontmatter:r,themeLocale:o,shouldShowNavbar:s,sidebarItems:i,isSidebarOpen:l,toggleSidebar:a,touchStart:c,onTouchStart:f,onTouchEnd:u,enableExternalLinkIcon:h,containerClass:g,get unregisterRouterHook(){return v},set unregisterRouterHook(m){v=m},scrollPromise:S,onBeforeEnter:L,onBeforeLeave:O,VPHome:Ph,VPNavbar:Pm,VPPage:pg,VPSidebar:xg};return Object.defineProperty(w,"__isScriptSetup",{enumerable:!1,value:!0}),w}});function Lg(e,t,n,r,o,s){return K(),Z("div",{class:qe(["vp-theme-container",r.containerClass]),onTouchstart:r.onTouchStart,onTouchend:r.onTouchEnd},[Se(e.$slots,"navbar",{},()=>[r.shouldShowNavbar?(K(),xe(r.VPNavbar,{key:0,onToggleSidebar:r.toggleSidebar},{before:Ae(()=>[Se(e.$slots,"navbar-before")]),after:Ae(()=>[Se(e.$slots,"navbar-after")]),_:3})):Te("",!0)]),ne("div",{class:"vp-sidebar-mask",onClick:t[0]||(t[0]=i=>r.toggleSidebar(!1))}),Se(e.$slots,"sidebar",{},()=>[oe(r.VPSidebar,null,{top:Ae(()=>[Se(e.$slots,"sidebar-top")]),bottom:Ae(()=>[Se(e.$slots,"sidebar-bottom")]),_:3})]),Se(e.$slots,"page",{},()=>[r.frontmatter.home?(K(),xe(r.VPHome,{key:0})):(K(),xe(En,{key:1,name:"fade-slide-y",mode:"out-in",onBeforeEnter:r.onBeforeEnter,onBeforeLeave:r.onBeforeLeave},{default:Ae(()=>[(K(),xe(r.VPPage,{key:r.page.path},{top:Ae(()=>[Se(e.$slots,"page-top")]),"content-top":Ae(()=>[Se(e.$slots,"page-content-top")]),"content-bottom":Ae(()=>[Se(e.$slots,"page-content-bottom")]),bottom:Ae(()=>[Se(e.$slots,"page-bottom")]),_:3}))]),_:3},8,["onBeforeEnter","onBeforeLeave"]))])],34)}const Pg=ke(Cg,[["render",Lg],["__file","Layout.vue"]]),Ag=pe({__name:"NotFound",setup(e,{expose:t}){t();const n=Cn(),r=Le(),o=r.value.notFound??["Not Found"],s=()=>o[Math.floor(Math.random()*o.length)],i=r.value.home??n.value,l=r.value.backToHome??"Back to home",a={routeLocale:n,themeLocale:r,messages:o,getMsg:s,homeLink:i,homeText:l,get RouteLink(){return Jr}};return Object.defineProperty(a,"__isScriptSetup",{enumerable:!1,value:!0}),a}}),Tg=e=>(bc("data-v-303656ba"),e=e(),yc(),e),Og={class:"vp-theme-container"},Rg={class:"page"},Ig={class:"theme-default-content"},Mg=Tg(()=>ne("h1",null,"404",-1));function $g(e,t,n,r,o,s){return K(),Z("div",Og,[ne("main",Rg,[ne("div",Ig,[Mg,ne("blockquote",null,Ee(r.getMsg()),1),oe(r.RouteLink,{to:r.homeLink},{default:Ae(()=>[jt(Ee(r.homeText),1)]),_:1},8,["to"])])])])}const Hg=ke(Ag,[["render",$g],["__scopeId","data-v-303656ba"],["__file","NotFound.vue"]]),Ng=mt({enhance({app:e,router:t}){mo("Badge")||e.component("Badge",rh),mo("CodeGroup")||e.component("CodeGroup",oh),mo("CodeGroupItem")||e.component("CodeGroupItem",lh),e.component("VPSearch",()=>{const r=e.component("Docsearch")||e.component("SearchBox");return r?ie(r):null});const n=t.options.scrollBehavior;t.options.scrollBehavior=async(...r)=>(await ca().wait(),n(...r))},setup(){zp(),Qp()},layouts:{Layout:Pg,NotFound:Hg}}),jg=Object.freeze(Object.defineProperty({__proto__:null,default:Ng},Symbol.toStringTag,{value:"Module"})),Fg=e=>e instanceof Element?document.activeElement===e&&(["TEXTAREA","SELECT","INPUT"].includes(e.tagName)||e.hasAttribute("contenteditable")):!1,Dg=(e,t)=>t.some(n=>{if(ot(n))return n===e.key;const{key:r,ctrl:o=!1,shift:s=!1,alt:i=!1}=n;return r===e.key&&o===e.ctrlKey&&s===e.shiftKey&&i===e.altKey}),Vg=/[^\x00-\x7F]/,Bg=e=>e.split(/\s+/g).map(t=>t.trim()).filter(t=>!!t),Ci=e=>e.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),Li=(e,t)=>{const n=t.join(" "),r=Bg(e);if(Vg.test(e))return r.some(i=>n.toLowerCase().indexOf(i)>-1);const o=e.endsWith(" ");return new RegExp(r.map((i,l)=>r.length===l+1&&!o?`(?=.*\\b${Ci(i)})`:`(?=.*\\b${Ci(i)}\\b)`).join("")+".+","gi").test(n)},zg=({input:e,hotKeys:t})=>{if(t.value.length===0)return;const n=r=>{e.value&&Dg(r,t.value)&&!Fg(r.target)&&(r.preventDefault(),e.value.focus())};$e(()=>{document.addEventListener("keydown",n)}),Vr(()=>{document.removeEventListener("keydown",n)})},Wg=[{title:"Welcome",headers:[{level:2,title:"Who we are",slug:"who-we-are",link:"#who-we-are",children:[]},{level:2,title:"Prerequisites",slug:"prerequisites",link:"#prerequisites",children:[]},{level:2,title:"Useful links",slug:"useful-links",link:"#useful-links",children:[]}],path:"/",pathLocale:"/",extraFields:[]},{title:"Welcome",headers:[{level:2,title:"Who we are",slug:"who-we-are",link:"#who-we-are",children:[]},{level:2,title:"Prerequisites",slug:"prerequisites",link:"#prerequisites",children:[]},{level:2,title:"Useful links",slug:"useful-links",link:"#useful-links",children:[]}],path:"/en/",pathLocale:"/en/",extraFields:[]},{title:"",headers:[{level:2,title:"Prerequisites",slug:"prerequisites",link:"#prerequisites",children:[]},{level:2,title:"Liens utiles",slug:"liens-utiles",link:"#liens-utiles",children:[]}],path:"/fr/",pathLocale:"/fr/",extraFields:[]},{title:"📚 Backend development",headers:[{level:2,title:"Ktor",slug:"ktor",link:"#ktor",children:[{level:3,title:"🧪 develop an API with Ktor",slug:"🧪-develop-an-api-with-ktor",link:"#🧪-develop-an-api-with-ktor",children:[]}]},{level:2,title:"Spring framework",slug:"spring-framework",link:"#spring-framework",children:[{level:3,title:"🧪 Spring boot part 1 - develop the same API with Spring Boot",slug:"🧪-spring-boot-part-1-develop-the-same-api-with-spring-boot",link:"#🧪-spring-boot-part-1-develop-the-same-api-with-spring-boot",children:[]},{level:3,title:"🧪 Spring boot part 2 - adding a database",slug:"🧪-spring-boot-part-2-adding-a-database",link:"#🧪-spring-boot-part-2-adding-a-database",children:[]},{level:3,title:"🧪 Spring boot part 2 - adding tests",slug:"🧪-spring-boot-part-2-adding-tests",link:"#🧪-spring-boot-part-2-adding-tests",children:[]}]},{level:2,title:"node.js",slug:"node-js",link:"#node-js",children:[{level:3,title:"🧪 Getting started with Kotlin/JS and Express",slug:"🧪-getting-started-with-kotlin-js-and-express",link:"#🧪-getting-started-with-kotlin-js-and-express",children:[]},{level:3,title:"🧪 Adding a post endpoint and an external Kotlin/JS definition",slug:"🧪-adding-a-post-endpoint-and-an-external-kotlin-js-definition",link:"#🧪-adding-a-post-endpoint-and-an-external-kotlin-js-definition",children:[]},{level:3,title:"🧪 Adding more endpoints",slug:"🧪-adding-more-endpoints",link:"#🧪-adding-more-endpoints",children:[]}]},{level:2,title:"🎯 Solutions",slug:"🎯-solutions",link:"#🎯-solutions",children:[]},{level:2,title:"📖 Further readings",slug:"📖-further-readings",link:"#📖-further-readings",children:[]}],path:"/en/backend-development/",pathLocale:"/en/",extraFields:[]},{title:"📚 Frontend development",headers:[{level:2,title:"Kotlin Multiplatform (KMP)",slug:"kotlin-multiplatform-kmp",link:"#kotlin-multiplatform-kmp",children:[]},{level:2,title:"Kotlin/JS and Kotlin/WASM",slug:"kotlin-js-and-kotlin-wasm",link:"#kotlin-js-and-kotlin-wasm",children:[{level:3,title:"🧪 Kotlin/WASM web app",slug:"🧪-kotlin-wasm-web-app",link:"#🧪-kotlin-wasm-web-app",children:[]},{level:3,title:"Kotlin/JS and Kotlin/WASM common points",slug:"kotlin-js-and-kotlin-wasm-common-points",link:"#kotlin-js-and-kotlin-wasm-common-points",children:[]}]},{level:2,title:"Compose multiplatform",slug:"compose-multiplatform",link:"#compose-multiplatform",children:[{level:3,title:"🧪 Create a Compose multiplatform app",slug:"🧪-create-a-compose-multiplatform-app",link:"#🧪-create-a-compose-multiplatform-app",children:[]},{level:3,title:"🧪 Playing with the Compose multiplatform API",slug:"🧪-playing-with-the-compose-multiplatform-api",link:"#🧪-playing-with-the-compose-multiplatform-api",children:[]}]},{level:2,title:"🎯 Solutions",slug:"🎯-solutions",link:"#🎯-solutions",children:[]},{level:2,title:"📖 Further reading",slug:"📖-further-reading",link:"#📖-further-reading",children:[]}],path:"/en/front-development/",pathLocale:"/en/",extraFields:[]},{title:"📚 Kotlin language features",headers:[{level:2,title:"Basic features",slug:"basic-features",link:"#basic-features",children:[{level:3,title:"Basic constructs (variables, control flow)",slug:"basic-constructs-variables-control-flow",link:"#basic-constructs-variables-control-flow",children:[]},{level:3,title:"Functions",slug:"functions",link:"#functions",children:[]},{level:3,title:"Null safety",slug:"null-safety",link:"#null-safety",children:[]},{level:3,title:"Enumerations",slug:"enumerations",link:"#enumerations",children:[]},{level:3,title:"🧪 Exercises",slug:"🧪-exercises",link:"#🧪-exercises",children:[]}]},{level:2,title:"Intermediate features",slug:"intermediate-features",link:"#intermediate-features",children:[{level:3,title:"Object oriented programming",slug:"object-oriented-programming",link:"#object-oriented-programming",children:[]},{level:3,title:"Data class",slug:"data-class",link:"#data-class",children:[]},{level:3,title:"Functional programming",slug:"functional-programming",link:"#functional-programming",children:[]},{level:3,title:"Kotlin and Java interoperability",slug:"kotlin-and-java-interoperability",link:"#kotlin-and-java-interoperability",children:[]},{level:3,title:"🧪 Exercises",slug:"🧪-exercises-1",link:"#🧪-exercises-1",children:[]}]},{level:2,title:"📖 Further reading",slug:"📖-further-reading",link:"#📖-further-reading",children:[]}],path:"/en/kotlin-features/",pathLocale:"/en/",extraFields:[]},{title:"📚 Advanced and other Kotlin features",headers:[{level:2,title:"Delegated properties",slug:"delegated-properties",link:"#delegated-properties",children:[]},{level:2,title:"Concurrency and Coroutines",slug:"concurrency-and-coroutines",link:"#concurrency-and-coroutines",children:[]},{level:2,title:"Function literal with receiver and Type-safe builders",slug:"function-literal-with-receiver-and-type-safe-builders",link:"#function-literal-with-receiver-and-type-safe-builders",children:[]},{level:2,title:"🧪 Exercises",slug:"🧪-exercises",link:"#🧪-exercises",children:[]}],path:"/en/kotlin-features-advanced/",pathLocale:"/en/",extraFields:[]},{title:"🛠 Let's make a cross-plaform app !",headers:[],path:"/en/other-technologies/",pathLocale:"/en/",extraFields:[]},{title:"🚀 Presentation of Kotlin",headers:[{level:2,title:"Some features",slug:"some-features",link:"#some-features",children:[]},{level:2,title:"History",slug:"history",link:"#history",children:[]},{level:2,title:"Some numbers and facts",slug:"some-numbers-and-facts",link:"#some-numbers-and-facts",children:[]},{level:2,title:"Why switch from Java to Kotlin",slug:"why-switch-from-java-to-kotlin",link:"#why-switch-from-java-to-kotlin",children:[]},{level:2,title:"A decision tree to help you decide if you should use Kotlin",slug:"a-decision-tree-to-help-you-decide-if-you-should-use-kotlin",link:"#a-decision-tree-to-help-you-decide-if-you-should-use-kotlin",children:[]},{level:2,title:"Prerequisites",slug:"prerequisites",link:"#prerequisites",children:[]},{level:2,title:"📖 Further reading",slug:"📖-further-reading",link:"#📖-further-reading",children:[]}],path:"/en/presentation/",pathLocale:"/en/",extraFields:[]},{title:"📅 Workshops",headers:[{level:2,title:"(2023) Android makers : Kotlin Beyond Android",slug:"_2023-android-makers-kotlin-beyond-android",link:"#_2023-android-makers-kotlin-beyond-android",children:[{level:3,title:"Link",slug:"link",link:"#link",children:[]},{level:3,title:"Agenda",slug:"agenda",link:"#agenda",children:[]}]},{level:2,title:"(2023) JNation : Let's discover the possibilities of Kotlin",slug:"_2023-jnation-let-s-discover-the-possibilities-of-kotlin",link:"#_2023-jnation-let-s-discover-the-possibilities-of-kotlin",children:[{level:3,title:"Link",slug:"link-1",link:"#link-1",children:[]},{level:3,title:"Agenda",slug:"agenda-1",link:"#agenda-1",children:[]}]},{level:2,title:"(2023) Mobile DevOps summit",slug:"_2023-mobile-devops-summit",link:"#_2023-mobile-devops-summit",children:[{level:3,title:"Agenda",slug:"agenda-2",link:"#agenda-2",children:[]}]},{level:2,title:"(2024) MiXit",slug:"_2024-mixit",link:"#_2024-mixit",children:[{level:3,title:"Agenda",slug:"agenda-3",link:"#agenda-3",children:[]}]}],path:"/en/workshops/",pathLocale:"/en/",extraFields:[]},{title:"📚 Développement du backend",headers:[{level:2,title:"Ktor",slug:"ktor",link:"#ktor",children:[{level:3,title:"TP : développer une API avec Ktor",slug:"tp-developper-une-api-avec-ktor",link:"#tp-developper-une-api-avec-ktor",children:[]}]},{level:2,title:"node.js",slug:"node-js",link:"#node-js",children:[{level:3,title:"TP : API Rest avec Kotlin/JS et Express",slug:"tp-api-rest-avec-kotlin-js-et-express",link:"#tp-api-rest-avec-kotlin-js-et-express",children:[]}]},{level:2,title:"Spring framework",slug:"spring-framework",link:"#spring-framework",children:[{level:3,title:"TP : Spring boot part 1 - développer la même API avec Spring Boot",slug:"tp-spring-boot-part-1-developper-la-meme-api-avec-spring-boot",link:"#tp-spring-boot-part-1-developper-la-meme-api-avec-spring-boot",children:[]},{level:3,title:"TP : Spring boot partie 2 - ajouter une base de données",slug:"tp-spring-boot-partie-2-ajouter-une-base-de-donnees",link:"#tp-spring-boot-partie-2-ajouter-une-base-de-donnees",children:[]},{level:3,title:"TP : Spring boot partie 3 - ajouter des tests",slug:"tp-spring-boot-partie-3-ajouter-des-tests",link:"#tp-spring-boot-partie-3-ajouter-des-tests",children:[]},{level:3,title:"Projets terminés",slug:"projets-termines",link:"#projets-termines",children:[]}]},{level:2,title:"Aller plus loin",slug:"aller-plus-loin",link:"#aller-plus-loin",children:[]},{level:2,title:"Lien et références",slug:"lien-et-references",link:"#lien-et-references",children:[]}],path:"/fr/backend-development/",pathLocale:"/fr/",extraFields:[]},{title:"📚 Développement frontend",headers:[{level:2,title:"KMP",slug:"kmp",link:"#kmp",children:[]},{level:2,title:"Kotlin/JS et Kotlin/WASM",slug:"kotlin-js-et-kotlin-wasm",link:"#kotlin-js-et-kotlin-wasm",children:[{level:3,title:"🧪 Application web Kotlin/WASM",slug:"🧪-application-web-kotlin-wasm",link:"#🧪-application-web-kotlin-wasm",children:[]},{level:3,title:"🧪 Application web KotlinJS",slug:"🧪-application-web-kotlinjs",link:"#🧪-application-web-kotlinjs",children:[]}]},{level:2,title:"Compose",slug:"compose",link:"#compose",children:[{level:3,title:"🧪 Compose Web",slug:"🧪-compose-web",link:"#🧪-compose-web",children:[]},{level:3,title:"🧪 Compose desktop + Android app",slug:"🧪-compose-desktop-android-app",link:"#🧪-compose-desktop-android-app",children:[]}]},{level:2,title:"Pour aller plus loin",slug:"pour-aller-plus-loin",link:"#pour-aller-plus-loin",children:[]}],path:"/fr/front-development/",pathLocale:"/fr/",extraFields:[]},{title:"📚 Fonctionnalités du langage Kotlin",headers:[{level:2,title:"Caractéristiques de base",slug:"caracteristiques-de-base",link:"#caracteristiques-de-base",children:[{level:3,title:"Constructions de base (variables, flux de contrôle)",slug:"constructions-de-base-variables-flux-de-controle",link:"#constructions-de-base-variables-flux-de-controle",children:[]},{level:3,title:"Les fonctions",slug:"les-fonctions",link:"#les-fonctions",children:[]},{level:3,title:"Null safety",slug:"null-safety",link:"#null-safety",children:[]},{level:3,title:"Énumérations",slug:"enumerations",link:"#enumerations",children:[]},{level:3,title:"Exercices",slug:"exercices",link:"#exercices",children:[]}]},{level:2,title:"Fonctionnalités intermédiaires",slug:"fonctionnalites-intermediaires",link:"#fonctionnalites-intermediaires",children:[{level:3,title:"Programmation orientée objet",slug:"programmation-orientee-objet",link:"#programmation-orientee-objet",children:[]},{level:3,title:"Data class",slug:"data-class",link:"#data-class",children:[]},{level:3,title:"Programmation fonctionnelle",slug:"programmation-fonctionnelle",link:"#programmation-fonctionnelle",children:[]},{level:3,title:"Kotlin and Java interoperability",slug:"kotlin-and-java-interoperability",link:"#kotlin-and-java-interoperability",children:[]},{level:3,title:"Exercices",slug:"exercices-1",link:"#exercices-1",children:[]}]},{level:2,title:"Plus d'exercices et de lecture",slug:"plus-d-exercices-et-de-lecture",link:"#plus-d-exercices-et-de-lecture",children:[]}],path:"/fr/kotlin-features/",pathLocale:"/fr/",extraFields:[]},{title:"📚 Fonctionnalités avancées de Kotlin",headers:[{level:2,title:"Propriétés déléguées",slug:"proprietes-deleguees",link:"#proprietes-deleguees",children:[]},{level:2,title:"Concurrence et coroutines",slug:"concurrence-et-coroutines",link:"#concurrence-et-coroutines",children:[]},{level:2,title:"Littéral de fonction avec récepteur et constructeurs de type sécurisé",slug:"litteral-de-fonction-avec-recepteur-et-constructeurs-de-type-securise",link:"#litteral-de-fonction-avec-recepteur-et-constructeurs-de-type-securise",children:[]},{level:2,title:"Exercises",slug:"exercises",link:"#exercises",children:[{level:3,title:"Exercise 1",slug:"exercise-1",link:"#exercise-1",children:[]}]}],path:"/fr/kotlin-features-advanced/",pathLocale:"/fr/",extraFields:[]},{title:"🛠 Construisons une app multiplateforme !",headers:[],path:"/fr/other-technologies/",pathLocale:"/fr/",extraFields:[]},{title:"🚀 Présentation de Kotlin",headers:[{level:2,title:"Certaines fonctionnalités",slug:"certaines-fonctionnalites",link:"#certaines-fonctionnalites",children:[]},{level:2,title:"Histoire",slug:"histoire",link:"#histoire",children:[]},{level:2,title:"Quelques chiffres et faits",slug:"quelques-chiffres-et-faits",link:"#quelques-chiffres-et-faits",children:[]},{level:2,title:"Pourquoi passer de Java à Kotlin",slug:"pourquoi-passer-de-java-a-kotlin",link:"#pourquoi-passer-de-java-a-kotlin",children:[]},{level:2,title:"Sources et plus de lecture",slug:"sources-et-plus-de-lecture",link:"#sources-et-plus-de-lecture",children:[]}],path:"/fr/presentation/",pathLocale:"/fr/",extraFields:[]},{title:"📅 Workshops",headers:[{level:2,title:"Android makers 2023: Kotlin Beyond Android",slug:"android-makers-2023-kotlin-beyond-android",link:"#android-makers-2023-kotlin-beyond-android",children:[{level:3,title:"Liens",slug:"liens",link:"#liens",children:[]},{level:3,title:"Agenda",slug:"agenda",link:"#agenda",children:[]}]},{level:2,title:"Mobile DevOps summit 2023",slug:"mobile-devops-summit-2023",link:"#mobile-devops-summit-2023",children:[{level:3,title:"Agenda",slug:"agenda-1",link:"#agenda-1",children:[]}]},{level:2,title:"Devoxx Morocco 2023",slug:"devoxx-morocco-2023",link:"#devoxx-morocco-2023",children:[{level:3,title:"Agenda",slug:"agenda-2",link:"#agenda-2",children:[]}]},{level:2,title:"(2024) MiXit",slug:"_2024-mixit",link:"#_2024-mixit",children:[{level:3,title:"Agenda",slug:"agenda-3",link:"#agenda-3",children:[]}]}],path:"/fr/workshops/",pathLocale:"/fr/",extraFields:[]},{title:"",headers:[],path:"/404.html",pathLocale:"/",extraFields:[]}],Kg=le(Wg),Ug=()=>Kg,qg=({searchIndex:e,routeLocale:t,query:n,maxSuggestions:r})=>{const o=R(()=>e.value.filter(s=>s.pathLocale===t.value));return R(()=>{const s=n.value.trim().toLowerCase();if(!s)return[];const i=[],l=(a,c)=>{Li(s,[c.title])&&i.push({link:`${a.path}#${c.slug}`,title:a.title,header:c.title});for(const f of c.children){if(i.length>=r.value)return;l(a,f)}};for(const a of o.value){if(i.length>=r.value)break;if(Li(s,[a.title,...a.extraFields])){i.push({link:a.path,title:a.title});continue}for(const c of a.headers){if(i.length>=r.value)break;l(a,c)}}return i})},Gg=e=>{const t=le(0);return{focusIndex:t,focusNext:()=>{t.value {t.value>0?t.value-=1:t.value=e.value.length-1}}},Jg=pe({name:"SearchBox",props:{locales:{type:Object,default:()=>({})},hotKeys:{type:Array,default:()=>[]},maxSuggestions:{type:Number,default:5}},setup(e){const{locales:t,hotKeys:n,maxSuggestions:r}=Uo(e),o=Wt(),s=Cn(),i=Ug(),l=le(null),a=le(!1),c=le(""),f=R(()=>t.value[s.value]??{}),u=qg({searchIndex:i,routeLocale:s,query:c,maxSuggestions:r}),{focusIndex:h,focusNext:g,focusPrev:v}=Gg(u);zg({input:l,hotKeys:n});const S=R(()=>a.value&&!!u.value.length),L=()=>{S.value&&v()},O=()=>{S.value&&g()},w=m=>{if(!S.value)return;const k=u.value[m];k&&o.push(k.link).then(()=>{c.value="",h.value=0})};return()=>ie("form",{class:"search-box",role:"search"},[ie("input",{ref:l,type:"search",placeholder:f.value.placeholder,autocomplete:"off",spellcheck:!1,value:c.value,onFocus:()=>a.value=!0,onBlur:()=>a.value=!1,onInput:m=>c.value=m.target.value,onKeydown:m=>{switch(m.key){case"ArrowUp":{L();break}case"ArrowDown":{O();break}case"Enter":{m.preventDefault(),w(h.value);break}}}}),S.value&&ie("ul",{class:"suggestions",onMouseleave:()=>h.value=-1},u.value.map(({link:m,title:k,header:N},M)=>ie("li",{class:["suggestion",{focus:h.value===M}],onMouseenter:()=>h.value=M,onMousedown:()=>w(M)},ie("a",{href:m,onClick:$=>$.preventDefault()},[ie("span",{class:"page-title"},k),N&&ie("span",{class:"page-header"},`> ${N}`)]))))])}});var Yg=["s","/"],Qg={"/en/":{placeholder:"Search"},"/fr/":{placeholder:"Rechercher"}};const Xg=Qg,Zg=Yg,ev=5,tv=mt({enhance({app:e}){e.component("SearchBox",t=>ie(Jg,{locales:Xg,hotKeys:Zg,maxSuggestions:ev,...t}))}}),nv=Object.freeze(Object.defineProperty({__proto__:null,default:tv},Symbol.toStringTag,{value:"Module"}));function rv(e){return{all:e=e||new Map,on:function(t,n){var r=e.get(t);r?r.push(n):e.set(t,[n])},off:function(t,n){var r=e.get(t);r&&(n?r.splice(r.indexOf(n)>>>0,1):e.set(t,[]))},emit:function(t,n){var r=e.get(t);r&&r.slice().map(function(o){o(n)}),(r=e.get("*"))&&r.slice().map(function(o){o(t,n)})}}}const ov=()=>{navigator.serviceWorker.getRegistration().then(e=>{e&&e.active&&(e==null||e.addEventListener("updatefound",()=>{window.location.reload(!0)}))})},sv=async(e,t={},n=!0)=>{const{register:r}=await Pe(async()=>{const{register:o}=await import("./index-DTEEl-sV.js");return{register:o}},[]);r(e,{ready(o){var s;n&&console.info("[Service Worker]: active"),(s=t.ready)==null||s.call(t,o)},registered(o){var s;n&&console.log("[Service Worker]: registered"),(s=t.registered)==null||s.call(t,o)},cached(o){var s;n&&console.log("[Service Worker]: cached"),(s=t.cached)==null||s.call(t,o)},async updatefound(o){var s;await navigator.serviceWorker.getRegistration()&&(n&&console.log("[Service Worker]: update found"),(s=t.updatefound)==null||s.call(t,o))},updated(o){var s;n&&console.log("[Service Worker]: updated"),(s=t.updated)==null||s.call(t,o)},offline(){var o;n&&console.log("[Service Worker]: offline"),(o=t.offline)==null||o.call(t)},error(o){var s;n&&console.error("[Service Worker]: ",o),(s=t.error)==null||s.call(t,o)}})},ya=Symbol(""),iv=()=>{const e=Ve(ya);if(!e)throw new Error("usePwaEvent() is called without provider.");return e},lv=async(e,t)=>sv(Yr(e),{ready(n){t.emit("ready",n)},registered(n){t.emit("registered",n)},cached(n){t.emit("cached",n)},updatefound(n){t.emit("updatefound",n)},updated(n){const r="service-worker-version",o=Number(localStorage.getItem(r)||0);localStorage.setItem(r,(o+1).toString()),localStorage.removeItem("manifest"),t.emit("updated",n)},offline(){t.emit("offline")},error(n){t.emit("error",n)}}),av=(e,t=!1)=>{const n=rv();Mt(ya,n),$e(async()=>{var o;let r=!1;(o=navigator.serviceWorker)!=null&&o.controller&&navigator.serviceWorker.addEventListener("controllerchange",()=>{r||(r=!0,window.location.reload())}),t&&ov(),await lv(e,n)})},cv=()=>{$e(()=>{if(window.matchMedia("(display-mode: standalone)").matches){const t=document.head.querySelector('meta[name="viewport"]');if(t){t.setAttribute("content","width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover");return}const n=document.createElement("meta");n.name="viewport",n.content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover",document.head.appendChild(n)}})},wa=({name:e="",color:t="currentColor"},{slots:n})=>{var r;return ie("svg",{xmlns:"http://www.w3.org/2000/svg",class:["icon",`${e}-icon`],viewBox:"0 0 1024 1024",fill:t,"aria-label":`${e} icon`},(r=n.default)==null?void 0:r.call(n))};wa.displayName="SVGWrapper";const ka=()=>ie(wa,{name:"update"},()=>ie("path",{d:"M949.949 146.25v255.826c0 21.981-13.989 35.97-35.97 35.97H658.154c-13.988 0-25.983-7.992-33.973-21.981-5.997-13.989-4-27.977 7.991-39.97l79.942-77.946c-55.954-51.973-121.918-77.955-199.863-77.955-37.975 0-75.95 8.002-113.924 21.99-37.975 15.985-67.948 37.976-91.934 63.957-25.982 23.987-47.973 53.96-63.957 91.934-29.983 73.955-29.983 153.895 0 227.85 15.984 37.976 37.975 67.947 63.957 91.934 23.986 25.982 53.959 47.973 91.934 63.956 37.974 13.989 75.95 21.991 113.924 21.991 45.967 0 87.942-9.998 127.913-29.982 41.976-17.99 75.951-45.967 101.931-83.943 7.993-4 11.994-5.995 13.989-5.995 5.997 0 9.998 1.994 13.988 5.995l77.958 77.946c3.989 4 5.986 7.993 5.986 11.994 0 1.994-1.996 5.995-3.99 11.994-43.973 51.962-93.941 91.934-151.9 117.914-53.958 25.983-115.92 39.972-185.874 39.972-61.961 0-119.921-11.984-169.89-33.973-57.96-25.985-105.923-57.963-139.896-93.943-35.98-33.972-67.958-81.936-93.94-139.897-45.967-101.93-45.967-237.846 0-339.777 25.982-57.96 57.96-105.923 93.94-139.896 33.973-35.98 81.936-67.958 139.896-93.94 49.968-21.99 107.928-33.974 169.89-33.974 55.963 0 109.923 9.988 161.885 29.973 53.97 21.99 101.933 51.963 139.908 89.938l73.954-73.944c9.987-9.998 23.987-13.988 39.971-8.002 13.988 8.002 21.98 19.995 21.98 33.984z"}));ka.displayName="UpdateIcon";const uv=pe({name:"PwaFoundPopup",props:{locales:{type:Object,required:!0}},slots:Object,setup(e,{slots:t}){const n=cs(e.locales),r=le(!1),o=()=>{r.value&&(window.location.reload(!0),r.value=!1)};return $e(()=>{const s=iv();s.on("updatefound",()=>{navigator.serviceWorker.getRegistration().then(i=>{i&&i.active&&(r.value=!0)})}),s.on("updated",()=>{r.value=!1})}),()=>ie(En,{name:"popup"},()=>{var s;return((s=t.default)==null?void 0:s.call(t,{found:r.value,refresh:o}))||(r.value?ie("button",{type:"button",class:"sw-hint-popup",tabindex:0,onClick:()=>o()},[n.value.hint,ie("span",{class:"icon-wrapper"},ie(ka))]):null)})}});var fv={"/en/":{install:"Install",iOSInstall:"Tap the share button and then 'Add to Home Screen'",cancel:"Cancel",close:"Close",prevImage:"Previous Image",nextImage:"Next Image",desc:"Description",feature:"Key Features",explain:"This app can be installed on your PC or mobile device. This will allow this web app to look and behave like any other installed app. You will find it in your app lists and be able to pin it to your home screen, start menus or task bars. This installed web app will also be able to safely interact with other apps and your operating system. ",hint:"New content found.",update:"New content is available."},"/fr/":{install:"Installer",iOSInstall:"Appuyez sur le bouton partager puis 'Ajouter à l'écran d'accueil'",cancel:"Annuler",close:"Fermer",prevImage:"Image précédente",nextImage:"Image suivante",desc:"Description",feature:"Composants clés",explain:"Cette app peut être installée sur PC ou smartphone. Cela permettra de rendre cette page web comme n'importe quelle autre application. Vous la trouverez dans votre liste d'application et serez capable de la pin sur votre écran principal et divers menus. L'application web installée sera capable d'interagir avec les autres applications et le système d'exploitation.",hint:"New content found.",update:"New content is available."},"/":{install:"Install",iOSInstall:"Tap the share button and then 'Add to Home Screen'",cancel:"Cancel",close:"Close",prevImage:"Previous Image",nextImage:"Next Image",desc:"Description",feature:"Key Features",explain:"This app can be installed on your PC or mobile device. This will allow this web app to look and behave like any other installed app. You will find it in your app lists and be able to pin it to your home screen, start menus or task bars. This installed web app will also be able to safely interact with other apps and your operating system. ",hint:"New content found.",update:"New content is available."}};const dv=fv,pv=()=>ie(uv,{locales:dv}),hv=mt({setup:()=>{av("service-worker.js",!1),cv()},rootComponents:[pv]}),mv=Object.freeze(Object.defineProperty({__proto__:null,default:hv},Symbol.toStringTag,{value:"Module"}));/*! medium-zoom 1.1.0 | MIT License | https://github.com/francoischalifour/medium-zoom */var Jt=Object.assign||function(e){for(var t=1;t 1&&arguments[1]!==void 0?arguments[1]:{},r=window.Promise||function(y){function H(){}y(H,H)},o=function(y){var H=y.target;if(H===z){v();return}m.indexOf(H)!==-1&&S({target:H})},s=function(){if(!(N||!b.original)){var y=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0;Math.abs(M-y)>$.scrollOffset&&setTimeout(v,150)}},i=function(y){var H=y.key||y.keyCode;(H==="Escape"||H==="Esc"||H===27)&&v()},l=function(){var y=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},H=y;if(y.background&&(z.style.background=y.background),y.container&&y.container instanceof Object&&(H.container=Jt({},$.container,y.container)),y.template){var X=kr(y.template)?y.template:document.querySelector(y.template);H.template=X}return $=Jt({},$,H),m.forEach(function(te){te.dispatchEvent(ln("medium-zoom:update",{detail:{zoom:x}}))}),x},a=function(){var y=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};return e(Jt({},$,y))},c=function(){for(var y=arguments.length,H=Array(y),X=0;X 0?H.reduce(function(I,J){return[].concat(I,Ai(J))},[]):m;return te.forEach(function(I){I.classList.remove("medium-zoom-image"),I.dispatchEvent(ln("medium-zoom:detach",{detail:{zoom:x}}))}),m=m.filter(function(I){return te.indexOf(I)===-1}),x},u=function(y,H){var X=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};return m.forEach(function(te){te.addEventListener("medium-zoom:"+y,H,X)}),k.push({type:"medium-zoom:"+y,listener:H,options:X}),x},h=function(y,H){var X=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};return m.forEach(function(te){te.removeEventListener("medium-zoom:"+y,H,X)}),k=k.filter(function(te){return!(te.type==="medium-zoom:"+y&&te.listener.toString()===H.toString())}),x},g=function(){var y=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},H=y.target,X=function(){var I={width:document.documentElement.clientWidth,height:document.documentElement.clientHeight,left:0,top:0,right:0,bottom:0},J=void 0,q=void 0;if($.container)if($.container instanceof Object)I=Jt({},I,$.container),J=I.width-I.left-I.right-$.margin*2,q=I.height-I.top-I.bottom-$.margin*2;else{var ve=kr($.container)?$.container:document.querySelector($.container),He=ve.getBoundingClientRect(),We=He.width,Fe=He.height,kt=He.left,St=He.top;I=Jt({},I,{width:We,height:Fe,left:kt,top:St})}J=J||I.width-$.margin*2,q=q||I.height-$.margin*2;var ct=b.zoomedHd||b.original,Ke=Pi(ct)?J:ct.naturalWidth||J,C=Pi(ct)?q:ct.naturalHeight||q,W=ct.getBoundingClientRect(),V=W.top,G=W.left,ce=W.width,ge=W.height,d=Math.min(Math.max(ce,Ke),J)/ce,p=Math.min(Math.max(ge,C),q)/ge,_=Math.min(d,p),P=(-G+(J-ce)/2+$.margin+I.left)/_,E=(-V+(q-ge)/2+$.margin+I.top)/_,T="scale("+_+") translate3d("+P+"px, "+E+"px, 0)";b.zoomed.style.transform=T,b.zoomedHd&&(b.zoomedHd.style.transform=T)};return new r(function(te){if(H&&m.indexOf(H)===-1){te(x);return}var I=function We(){N=!1,b.zoomed.removeEventListener("transitionend",We),b.original.dispatchEvent(ln("medium-zoom:opened",{detail:{zoom:x}})),te(x)};if(b.zoomed){te(x);return}if(H)b.original=H;else if(m.length>0){var J=m;b.original=J[0]}else{te(x);return}if(b.original.dispatchEvent(ln("medium-zoom:open",{detail:{zoom:x}})),M=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,N=!0,b.zoomed=_v(b.original),document.body.appendChild(z),$.template){var q=kr($.template)?$.template:document.querySelector($.template);b.template=document.createElement("div"),b.template.appendChild(q.content.cloneNode(!0)),document.body.appendChild(b.template)}if(b.original.parentElement&&b.original.parentElement.tagName==="PICTURE"&&b.original.currentSrc&&(b.zoomed.src=b.original.currentSrc),document.body.appendChild(b.zoomed),window.requestAnimationFrame(function(){document.body.classList.add("medium-zoom--opened")}),b.original.classList.add("medium-zoom-image--hidden"),b.zoomed.classList.add("medium-zoom-image--opened"),b.zoomed.addEventListener("click",v),b.zoomed.addEventListener("transitionend",I),b.original.getAttribute("data-zoom-src")){b.zoomedHd=b.zoomed.cloneNode(),b.zoomedHd.removeAttribute("srcset"),b.zoomedHd.removeAttribute("sizes"),b.zoomedHd.removeAttribute("loading"),b.zoomedHd.src=b.zoomed.getAttribute("data-zoom-src"),b.zoomedHd.onerror=function(){clearInterval(ve),console.warn("Unable to reach the zoom image target "+b.zoomedHd.src),b.zoomedHd=null,X()};var ve=setInterval(function(){b.zoomedHd.complete&&(clearInterval(ve),b.zoomedHd.classList.add("medium-zoom-image--opened"),b.zoomedHd.addEventListener("click",v),document.body.appendChild(b.zoomedHd),X())},10)}else if(b.original.hasAttribute("srcset")){b.zoomedHd=b.zoomed.cloneNode(),b.zoomedHd.removeAttribute("sizes"),b.zoomedHd.removeAttribute("loading");var He=b.zoomedHd.addEventListener("load",function(){b.zoomedHd.removeEventListener("load",He),b.zoomedHd.classList.add("medium-zoom-image--opened"),b.zoomedHd.addEventListener("click",v),document.body.appendChild(b.zoomedHd),X()})}else X()})},v=function(){return new r(function(y){if(N||!b.original){y(x);return}var H=function X(){b.original.classList.remove("medium-zoom-image--hidden"),document.body.removeChild(b.zoomed),b.zoomedHd&&document.body.removeChild(b.zoomedHd),document.body.removeChild(z),b.zoomed.classList.remove("medium-zoom-image--opened"),b.template&&document.body.removeChild(b.template),N=!1,b.zoomed.removeEventListener("transitionend",X),b.original.dispatchEvent(ln("medium-zoom:closed",{detail:{zoom:x}})),b.original=null,b.zoomed=null,b.zoomedHd=null,b.template=null,y(x)};N=!0,document.body.classList.remove("medium-zoom--opened"),b.zoomed.style.transform="",b.zoomedHd&&(b.zoomedHd.style.transform=""),b.template&&(b.template.style.transition="opacity 150ms",b.template.style.opacity=0),b.original.dispatchEvent(ln("medium-zoom:close",{detail:{zoom:x}})),b.zoomed.addEventListener("transitionend",H)})},S=function(){var y=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},H=y.target;return b.original?v():g({target:H})},L=function(){return $},O=function(){return m},w=function(){return b.original},m=[],k=[],N=!1,M=0,$=n,b={original:null,zoomed:null,zoomedHd:null,template:null};Object.prototype.toString.call(t)==="[object Object]"?$=t:(t||typeof t=="string")&&c(t),$=Jt({margin:0,background:"#fff",scrollOffset:40,container:null,template:null},$);var z=vv($.background);document.addEventListener("click",o),document.addEventListener("keyup",i),document.addEventListener("scroll",s),window.addEventListener("resize",v);var x={open:g,close:v,toggle:S,update:l,clone:a,attach:c,detach:f,on:u,off:h,getOptions:L,getImages:O,getZoomedImage:w};return x};function yv(e,t){t===void 0&&(t={});var n=t.insertAt;if(!(typeof document>"u")){var r=document.head||document.getElementsByTagName("head")[0],o=document.createElement("style");o.type="text/css",n==="top"&&r.firstChild?r.insertBefore(o,r.firstChild):r.appendChild(o),o.styleSheet?o.styleSheet.cssText=e:o.appendChild(document.createTextNode(e))}}var wv=".medium-zoom-overlay{position:fixed;top:0;right:0;bottom:0;left:0;opacity:0;transition:opacity .3s;will-change:opacity}.medium-zoom--opened .medium-zoom-overlay{cursor:pointer;cursor:zoom-out;opacity:1}.medium-zoom-image{cursor:pointer;cursor:zoom-in;transition:transform .3s cubic-bezier(.2,0,.2,1)!important}.medium-zoom-image--hidden{visibility:hidden}.medium-zoom-image--opened{position:relative;cursor:pointer;cursor:zoom-out;will-change:transform}";yv(wv);const kv=Symbol("mediumZoom");var Sv={};const Ev=":not(a) > img",xv=Sv,Cv=500,Lv=mt({enhance({app:e,router:t}){const n=bv(xv);n.refresh=(r=Ev)=>{n.detach(),n.attach(r)},e.provide(kv,n),t.afterEach(()=>{na(Cv).then(()=>n.refresh())})}}),Pv=Object.freeze(Object.defineProperty({__proto__:null,default:Lv},Symbol.toStringTag,{value:"Module"})),Av=Object.freeze(Object.defineProperty({__proto__:null},Symbol.toStringTag,{value:"Module"})),Tv=mt({enhance:({app:e})=>{},setup:()=>{}}),Ov=Object.freeze(Object.defineProperty({__proto__:null,default:Tv},Symbol.toStringTag,{value:"Module"})),_r=[pp,kp,Pp,Ip,Mp,Dp,jg,nv,mv,Pv,Av,Ov].map(e=>e.default).filter(Boolean),Rv=JSON.parse(`{"base":"/learning-kotlin/","lang":"en-US","title":"","description":"","head":[["link",{"rel":"icon","href":"/learning-kotlin/favicon.ico"}],["link",{"rel":"manifest","href":"/learning-kotlin/manifest.webmanifest"}],["meta",{"name":"theme-color","content":"#2176d6"}]],"locales":{"/en/":{"lang":"en-US","title":"Learning Kotlin","description":"Learning Kotlin"},"/fr/":{"lang":"fr-FR","title":"Apprendre Kotlin","description":"Une formation d'introduction à Kotlin"}}}`);var Mn=kn(Rv),Iv=ed,Mv=()=>{const e=Cd({history:Iv(Nl("/learning-kotlin/")),routes:[{name:"vuepress-route",path:"/:catchAll(.*)",components:{}}],scrollBehavior:(t,n,r)=>r||(t.hash?{el:t.hash}:{top:0})});return e.beforeResolve(async(t,n)=>{if(t.path!==n.path||n===vt){const r=Gn(t.fullPath);if(r.path!==t.fullPath)return r.path;const o=await r.loader();t.meta={...r.meta,_pageChunk:o}}else t.path===n.path&&(t.meta=n.meta)}),e},$v=e=>{e.component("ClientOnly",os),e.component("Content",ss),e.component("RouteLink",Jr)},Hv=(e,t,n)=>{const r=R(()=>t.currentRoute.value.path),o=uc((L,O)=>({get(){return L(),t.currentRoute.value.meta._pageChunk},set(w){t.currentRoute.value.meta._pageChunk=w,O()}})),s=R(()=>Gt.resolveLayouts(n)),i=R(()=>Gt.resolveRouteLocale(Mn.value.locales,r.value)),l=R(()=>Gt.resolveSiteLocaleData(Mn.value,i.value)),a=R(()=>o.value.comp),c=R(()=>o.value.data),f=R(()=>c.value.frontmatter),u=R(()=>Gt.resolvePageHeadTitle(c.value,l.value)),h=R(()=>Gt.resolvePageHead(u.value,f.value,l.value)),g=R(()=>Gt.resolvePageLang(c.value,l.value)),v=R(()=>Gt.resolvePageLayout(c.value,s.value)),S={layouts:s,pageData:c,pageComponent:a,pageFrontmatter:f,pageHead:h,pageHeadTitle:u,pageLang:g,pageLayout:v,redirects:Oo,routeLocale:i,routePath:r,routes:gn,siteData:Mn,siteLocaleData:l};return e.provide(ns,S),Object.defineProperties(e.config.globalProperties,{$frontmatter:{get:()=>f.value},$head:{get:()=>h.value},$headTitle:{get:()=>u.value},$lang:{get:()=>g.value},$page:{get:()=>c.value},$routeLocale:{get:()=>i.value},$site:{get:()=>Mn.value},$siteLocale:{get:()=>l.value},$withBase:{get:()=>Yr}}),S},Nv=()=>{const e=Ad(),t=Td();let n=[];const r=()=>{e.value.forEach(i=>{const l=jv(i);l&&n.push(l)})},o=()=>{const i=[];return e.value.forEach(l=>{const a=Fv(l);a&&i.push(a)}),i},s=()=>{document.documentElement.lang=t.value;const i=o();n.forEach((l,a)=>{const c=i.findIndex(f=>l.isEqualNode(f));c===-1?(l.remove(),delete n[a]):i.splice(c,1)}),i.forEach(l=>document.head.appendChild(l)),n=[...n.filter(l=>!!l),...i]};Mt(Id,s),$e(()=>{r(),Ne(e,s,{immediate:!1})})},jv=([e,t,n=""])=>{const r=Object.entries(t).map(([l,a])=>ot(a)?`[${l}=${JSON.stringify(a)}]`:a===!0?`[${l}]`:"").join(""),o=`head > ${e}${r}`;return Array.from(document.querySelectorAll(o)).find(l=>l.innerText===n)||null},Fv=([e,t,n])=>{if(!ot(e))return null;const r=document.createElement(e);return Zo(t)&&Object.entries(t).forEach(([o,s])=>{ot(s)?r.setAttribute(o,s):s===!0&&r.setAttribute(o,"")}),ot(n)&&r.appendChild(document.createTextNode(n)),r},Dv=sf,Vv=async()=>{var n;const e=Dv({name:"Vuepress",setup(){var s;Nv();for(const i of _r)(s=i.setup)==null||s.call(i);const r=_r.flatMap(({rootComponents:i=[]})=>i.map(l=>ie(l))),o=Od();return()=>[ie(o.value),r]}}),t=Mv();$v(e),Hv(e,t,_r);for(const r of _r)await((n=r.enhance)==null?void 0:n.call(r,{app:e,router:t,siteData:Mn}));return e.use(t),{app:e,router:t}};Vv().then(({app:e,router:t})=>{t.isReady().then(()=>{e.mount("#app")})});export{ke as _,bu as a,oe as b,Z as c,Vv as createVueApp,ne as d,jt as e,K as o,Go as r,Ae as w}; diff --git a/assets/compose-multiplaform-Dfyu_rxB.gif b/assets/compose-multiplaform-Dfyu_rxB.gif new file mode 100644 index 00000000..ffa4079f Binary files /dev/null and b/assets/compose-multiplaform-Dfyu_rxB.gif differ diff --git a/assets/compose-multiplaform-web-wH6XfCHb.gif b/assets/compose-multiplaform-web-wH6XfCHb.gif new file mode 100644 index 00000000..b85ee793 Binary files /dev/null and b/assets/compose-multiplaform-web-wH6XfCHb.gif differ diff --git a/assets/hello-compose-demo-B4DIIuDy.gif b/assets/hello-compose-demo-B4DIIuDy.gif new file mode 100644 index 00000000..d4fe51b5 Binary files /dev/null and b/assets/hello-compose-demo-B4DIIuDy.gif differ diff --git a/assets/index-DTEEl-sV.js b/assets/index-DTEEl-sV.js new file mode 100644 index 00000000..69326545 --- /dev/null +++ b/assets/index-DTEEl-sV.js @@ -0,0 +1 @@ +var v=function(){return!!(window.location.hostname==="localhost"||window.location.hostname==="[::1]"||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/))},c;typeof window<"u"&&(typeof Promise<"u"?c=new Promise(function(t){return window.addEventListener("load",t)}):c={then:function(t){return window.addEventListener("load",t)}});function s(t,n){n===void 0&&(n={});var i=n.registrationOptions;i===void 0&&(i={}),delete n.registrationOptions;var e=function(r){for(var f=[],a=arguments.length-1;a-- >0;)f[a]=arguments[a+1];n&&n[r]&&n[r].apply(n,f)};"serviceWorker"in navigator&&c.then(function(){v()?(l(t,e,i),navigator.serviceWorker.ready.then(function(r){e("ready",r)}).catch(function(r){return o(e,r)})):(u(t,e,i),navigator.serviceWorker.ready.then(function(r){e("ready",r)}).catch(function(r){return o(e,r)}))})}function o(t,n){navigator.onLine||t("offline"),t("error",n)}function u(t,n,i){navigator.serviceWorker.register(t,i).then(function(e){if(n("registered",e),e.waiting){n("updated",e);return}e.onupdatefound=function(){n("updatefound",e);var r=e.installing;r.onstatechange=function(){r.state==="installed"&&(navigator.serviceWorker.controller?n("updated",e):n("cached",e))}}}).catch(function(e){return o(n,e)})}function l(t,n,i){fetch(t).then(function(e){e.status===404?(n("error",new Error("Service worker not found at "+t)),d()):e.headers.get("content-type").indexOf("javascript")===-1?(n("error",new Error("Expected "+t+" to have javascript content-type, but received "+e.headers.get("content-type"))),d()):u(t,n,i)}).catch(function(e){return o(n,e)})}function d(){"serviceWorker"in navigator&&navigator.serviceWorker.ready.then(function(t){t.unregister()}).catch(function(t){return o(emit,t)})}export{s as register,d as unregister}; diff --git a/assets/index.html-09oChKdI.js b/assets/index.html-09oChKdI.js new file mode 100644 index 00000000..d7ff231e --- /dev/null +++ b/assets/index.html-09oChKdI.js @@ -0,0 +1 @@ +import{_ as e}from"./kotlin-used-for-Bdlavnqs.js";import{_ as i,c as t,o as l,a as n}from"./app-DAFlGqDu.js";const r={},a=n(' 🚀 Présentation de Kotlin
Kotlin est un langage de programmation moderne développé par JetBrains.
Certaines fonctionnalités
- Kotlin est capable de compiler vers différentes cibles : la JVM, JS, Android, iOS, Les OS de bureau, etc.
- Pris en charge officiellement par de grands frameworks back-end, tels que Spring et Quarkus,
- Langage préféré pour le développement Android,
- Kotlin Native compile en code natif,
- Kotlin peut être considéré comme un langage fullstack
- Interopérable avec Java.
- Fournit des fonctionnalités modernes telles que la null safety à la compilation.
- Prend en charge la programmation orientée objet et la programmation fonctionnelle.
- Processus d'évolution open source: Kotlin KEEP.
Histoire
- 15 février 2016 : sortie de Kotlin 1.0.
- 4 janvier 2017 : Spring a introduit le support de Kotlin dans Spring 5.
- Lors de Google I/O 2017, Google a annoncé une prise en charge officielle de Kotlin sur Android.
- Le 7 mai 2019, Google a annoncé que Kotlin était le langage préféré des développeurs d'applications Android.
- Juin 2022 : Kotlin 1.7 est sorti avec la version du nouveau compilateur K2.
Quelques chiffres et faits
- En 2022, Kotlin est utilisé par 7,8 % des experts de l'industrie.
- Selon StackOverflow, Kotlin était le 4ème langage le plus apprécié en 2020 avec 62,9 % des votes.
- Kotlin est désormais répertorié parmi les 20 meilleurs langages de programmation par Redmonk.
- Kotlin fait partie des 3 principaux langages vers lesquels la plupart des entreprises prévoient de migrer leurs applications en 2022.
- Kotlin est utilisé par des entreprises mondiales telles que Google, Netflix, Amazon, Trello, etc.
- Plusieurs entreprises dont Google, Pinterest et Uber ont migré leurs applications de Java vers Kotlin.
Veuillez trouver plus de statiques ici :
Pourquoi passer de Java à Kotlin
Voici quelques arguments qui motivent le passage de Java (version 17 LTS au moment de la rédaction) à Kotlin.
- Kotlin prend en charge plus de cibles que Java.
- Kotlin protège des références null à la compilation (les
Optional
Java sont ne sont pas de protections à la compilation).- Les chaînes de caractères Kotlin prennent en charge l'interpolation.
- Les fonctionnalités de programmation fonctionnelle de Kotlin sont meilleures. Il permet même de définir des constructeurs et des DSL (Domain Specific Language) dont le typage est sécurisé (type-safe).
- Kotlin peut être mélangé avec du code Java, facilitant ainsi le processus de migration.
Vous pouvez lire plus d'arguments dans ces articles :
- Java Vs Kotlin : Lequel vaut-il mieux apprendre en 2022 ?
- [8 raisons pour lesquelles vous devriez passer à Kotlin depuis Java] (https://www.geeksforgeeks.org/8-reasons-why-you-should-switch-to-kotlin-from-java)
Sources et plus de lecture
',19),o=[a];function s(u,p){return l(),t("div",null,o)}const f=i(r,[["render",s],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/fr/presentation/","title":"🚀 Présentation de Kotlin","lang":"fr-FR","frontmatter":{},"headers":[{"level":2,"title":"Certaines fonctionnalités","slug":"certaines-fonctionnalites","link":"#certaines-fonctionnalites","children":[]},{"level":2,"title":"Histoire","slug":"histoire","link":"#histoire","children":[]},{"level":2,"title":"Quelques chiffres et faits","slug":"quelques-chiffres-et-faits","link":"#quelques-chiffres-et-faits","children":[]},{"level":2,"title":"Pourquoi passer de Java à Kotlin","slug":"pourquoi-passer-de-java-a-kotlin","link":"#pourquoi-passer-de-java-a-kotlin","children":[]},{"level":2,"title":"Sources et plus de lecture","slug":"sources-et-plus-de-lecture","link":"#sources-et-plus-de-lecture","children":[]}],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"fr/presentation/README.md"}');export{f as comp,h as data}; diff --git a/assets/index.html-7_H8SzH5.js b/assets/index.html-7_H8SzH5.js new file mode 100644 index 00000000..378a6623 --- /dev/null +++ b/assets/index.html-7_H8SzH5.js @@ -0,0 +1 @@ +import{_ as e}from"./logo_worldline-dinT9MYm.js";import{_ as t,c as i,o as a,a as n}from"./app-DAFlGqDu.js";const r={},l=n('
- Kotlin sur Wikipedia
- Présentation de la prise en charge de Kotlin dans Spring Framework 5.0
- 8 statistiques et faits à connaître sur Kotlin en 2022
- Pourquoi devriez-vous apprendre Kotlin en 2022 ?
Welcome
Who we are
We design payments technology that powers the growth of millions of businesses around the world. Engineering the next frontiers in payments technology
- Leader in payment and secured transactions
- Over 50 billion transactions/year
- 7000+ engineers in over 40 countries
- A huge & diverse tech-stack
Prerequisites
- Basic knowledge of object-oriented language like Java
- Prepare your development environment and install stuff before the session (see Tooling section)
Useful links
',10),o=[l];function s(d,c){return a(),i("div",null,o)}const u=t(r,[["render",s],["__file","index.html.vue"]]),m=JSON.parse(`{"path":"/","title":"Welcome","lang":"en-US","frontmatter":{"home":true,"heroImage":"./kotlin_logo.png","tagline":"A beginner's guide to a modern programming language","actions":[{"text":"Get started →","link":"/en/presentation/","type":"primary"}],"features":[{"title":"Language features","details":"null safety, extensions, lambdas, Java interoperability and more"},{"title":"Backend development","details":"With Ktor, spring and NodeJS"},{"title":"Frontend development","details":"Compose multiplatform, Kotlin/JS, Kotlin/WASM and JVM frameworks"},{"title":"Cross-platform development","details":"With KMP and Compose multiplatform"},{"title":"Advanced Kotlin","details":"Coroutines, delegates, Function literal with receiver, DSLs and more"},{"title":"Practical exercises and solutions","details":"All chapters have a set of exercises"}],"footer":"Worldline, 2023"},"headers":[{"level":2,"title":"Who we are","slug":"who-we-are","link":"#who-we-are","children":[]},{"level":2,"title":"Prerequisites","slug":"prerequisites","link":"#prerequisites","children":[]},{"level":2,"title":"Useful links","slug":"useful-links","link":"#useful-links","children":[]}],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"index.md"}`);export{u as comp,m as data}; diff --git a/assets/index.html-BRh3fjnU.js b/assets/index.html-BRh3fjnU.js new file mode 100644 index 00000000..acded941 --- /dev/null +++ b/assets/index.html-BRh3fjnU.js @@ -0,0 +1 @@ +import{_ as e}from"./kotlin-used-for-Bdlavnqs.js";import{_ as i,c as t,o,a as n}from"./app-DAFlGqDu.js";const a="/learning-kotlin/assets/kotlin-decision-tree-4i7nEr1Z.svg",r={},l=n('🚀 Presentation of Kotlin
Kotlin is a modern programming language developed by JetBrains.
Some features
- Kotlin compiles to many targets: the JVM, JS, WASM, Android, iOS, Desktop, native code, etc.
- Many server frameworks officially support Kotlin such as Spring and Quarkus.
- It is the first-class language for writing Android.
- Kotlin can compile to native with Kotlin native or with GraalVM.
- KMP allows to share code between different platforms.
- Kotlin can be considered as both fullstack and corss-platform language.
- Inter-operable with Java.
- Provides modern features such as compile-time null safety and data classes.
- Supports Object oriented programming and functional programming.
- Kotlin KEEP is its open source evolution process.
History
- February 15, 2016: Kotlin 1.0 was released.
- January 04, 2017: Spring introduced Kotlin support in Spring 5.
- At Google I/O 2017, Google announced first-class support for Kotlin on Android.
- On 7 May 2019, Google announced that Kotlin the preferred language for Android app developers.
- June 2022: Kotlin 1.7 was released with the version of the new K2 compiler.
Some numbers and facts
- As of 2022, Kotlin is used by 7.8% of the industry experts.
- According to StackOverflow, Kotlin was the 4th most beloved language in 2020 with 62.9% votes.
- Kotlin is now listed among the top 20 programming languages by Redmonk.
- Kotlin is among the top 3 languages that most businesses are planning to migrate their apps to in 2022.
- Kotlin is used by global companies like Google, NetFlix, Amazon, Trello, and more.
- Pinterest and Uber are among the companies that migrated their apps to Kotlin from Java.
Please find more statics here:
Why switch from Java to Kotlin
Here are some arguments that motivate switching from Java (version 17 LTS at the time of writing) to Kotlin.
- Kotlin supports more targets than Java.
- Kotlin has compile time null-safety (Java Optionals are runtime wrappers for nullable values and null annotations have less features).
- Kotlin strings support interpolation.
- Casting in Kotlin is smart.
- Kotlin functional programming features are better. It even allows to define Type-Safe builders and DSLs.
- Kotlin can be mixed with Java code, thus helping the migration process.
You can read more arguments in these articles:
- Java Vs Kotlin: Which One Is Better To Learn In 2022?
- 8 Reasons Why You Should Switch To Kotlin From Java
A decision tree to help you decide if you should use Kotlin
Prerequisites
- Recommended: IntelliJ IDEA (Both community or ultimate are OK),
- or GitPod by opening https://gitpod.io/#https://github.com/worldline/learning-kotlin to create a workspace and then browser to the material folder,
- or VSCode (with Kotlin and Java plugins) + JDK 17 (install via scoop.sh on windows or SDK man on Unix) + gradle
📖 Further reading
',23),s=[l];function h(d,u){return o(),t("div",null,s)}const m=i(r,[["render",h],["__file","index.html.vue"]]),f=JSON.parse('{"path":"/en/presentation/","title":"🚀 Presentation of Kotlin","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Some features","slug":"some-features","link":"#some-features","children":[]},{"level":2,"title":"History","slug":"history","link":"#history","children":[]},{"level":2,"title":"Some numbers and facts","slug":"some-numbers-and-facts","link":"#some-numbers-and-facts","children":[]},{"level":2,"title":"Why switch from Java to Kotlin","slug":"why-switch-from-java-to-kotlin","link":"#why-switch-from-java-to-kotlin","children":[]},{"level":2,"title":"A decision tree to help you decide if you should use Kotlin","slug":"a-decision-tree-to-help-you-decide-if-you-should-use-kotlin","link":"#a-decision-tree-to-help-you-decide-if-you-should-use-kotlin","children":[]},{"level":2,"title":"Prerequisites","slug":"prerequisites","link":"#prerequisites","children":[]},{"level":2,"title":"📖 Further reading","slug":"📖-further-reading","link":"#📖-further-reading","children":[]}],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"en/presentation/README.md"}');export{m as comp,f as data}; diff --git a/assets/index.html-BaJOs0HP.js b/assets/index.html-BaJOs0HP.js new file mode 100644 index 00000000..80f772d1 --- /dev/null +++ b/assets/index.html-BaJOs0HP.js @@ -0,0 +1,24 @@ +import{_ as e}from"./kotlin-wasm-webapp-R4_9ho9v.js";import{_ as n,c as t,o as a,a as o}from"./app-DAFlGqDu.js";const s="/learning-kotlin/assets/launch-android-app-BS4WBFFP.png",l="/learning-kotlin/assets/kmp-compose-desktop-CzocCLNL.png",i="/learning-kotlin/assets/hello-compose-demo-B4DIIuDy.gif",r={},p=o('
- Kotlin on Wikipedia
- Introducing Kotlin support in Spring Framework 5.0
- 8 stats and facts you should know about Kotlin in 2022
- Why should you learn Kotlin in 2022?
- Kotlin is awesome
- Awesome Kotlin
📚 Frontend development
Kotlin supports a wide selection of frontend frameworks across all platforms: mobile, desktop and web.
Please find below a glimpse of the possibilities that you can do right from IntelliJ:
- On the Desktop side
- Thanks to JVM support, Kotlin supports JavaFX (There was a Kotlin counterpart called tornadofx which is not maintained anymore).
- Compose Multiplatform brings Jetpack Compose to the desktop, the web and mobile.
- On the Web
- Ktor can use templates engines such as FreeMarker to create server pages.
- With KotlinJS, developers can create React, nodsjs, or vanilla JS Apps using Kotlin.
- Kotlin WASM compiles into Web Assembly. It can complement KotlinJS for computation intensive tasks.
- On Mobiles
- Android developers use the Jetpack Compose UI Framework or the legacy xml layouts. It is experimental on iOS.
Kotlin supports cross platform frontend development thanks to Kotlin MultiPlatform (KMP)
Kotlin Multiplatform (KMP)
"The Kotlin Multiplatform technology is designed to simplify the development of cross-platform projects. It reduces time spent writing and maintaining the same code for different platforms while retaining the flexibility and benefits of native programming." ₁
KMP relies on Kotlin native and other Kotlin features to help developers create projects that target multiple platforms using a common Kotlin code-base.
Many combinations of targets and use cases are possible:
- Full-Stack web apps: A project that contains a backend and a web app while sharing common logic.
- Multiplatform libraries
Kotlin/JS and Kotlin/WASM
- Kotlin/JS can also target the web and even use web frameworks (such as react) in Kolitn.
- Kotlin WASM is another possibility to target the web but this will generate WASM instead of pure JS code.
- It can be used for example to develop computation intensive libraries.
- Maybe we can do even more in the future with as all these technologies (Kotlin, WASM and Kotlin/WASM) evolve. - For example, WASI allows WASM to communicate with the operating system. - This means that me may see Kotlin/WASM project projects in the future that can target both the browser and the OS.
- Let's keep watching 😄.
🧪 Kotlin/WASM web app
- Let's create a Kotlin/WASM app. By cloning
git clone git@github.com:Kotlin/kotlin-wasm-examples.git
and opening the browser-example folder in your IDE.
- To get up to date information on how to start a Kotlin/WASM project, please refer to the official documentation for kotlin/wasm.
- Open the project and run the
wasmJsBrowserRun
task.- The development server should start and you can open your WASM powered webapp on http://localhost:8080/
- ⚠️ You may need to activate some flags on your browser for the app to work. If you see a blank page, please read the browser logs to check for the instructions.
- Please check the contents of src/wasmJsMain/kotlin/Simple.kt to understand how the page is coded.
- Next, let's check the generated wasm file which is available in build/js/packages/project_name/kotlin
- WASM being a binary format, we need to convert it first to text format.
- We can either install WABT (The WebAssembly Binary Toolkit or wabbit) and use the wasm2wattool
wasm2wat --enable-all -v .\\kotlin-wasm-demo-wasm.wasm -o wasm.wat
,- or use an online converter such as this one
- ❗ However, I couldn't get it to work (yet).
Kotlin/JS and Kotlin/WASM common points
Both Kotlin/WASM and Kotlin/JS IntelliJ work somewhat similarly.
- Both rely on the KMP plugin
- Kotlin/WASM is enabled by adding a
wasmjs
section in thebuild.gradle.kts
file, while Kotlin/JS is enabled by adding ajs
section.- The Kotlin code will compile to WASM and / or JS. Kotlin/JS generates only JS while Kotin/WASM generates both JS and WASM.
- In both cases, the entry point of the generated code is a JS file called module_name.js.
- The index.html in the resources folder loads the generated JS explained above (the one named module_name.js).
- The task
wasmBrowserDevelopmentRun
orjsWasmBrowserDevelopmentRun
run a local server that hosts both the index.html files and the generated JS and WASM files.Compose multiplatform
"Compose Multiplatform simplifies and accelerates UI development for Desktop and Web applications, and allows extensive UI code sharing between Android, iOS, Desktop and Web. It's a modern toolkit for building native UI. Quickly bring your app to life with less code, powerful tools, and intuitive Kotlin APIs."
Button( + onClick = { + logger.info("Pressed!") + } +) { + Text("Hello $platform") +} +
It is based on Android Jetpack Compose declarative UI approach ( which is similar also to iOS SwiftUI ) 1
Compose multiplatform vs Jetpack Compose
While very similar, Compose multiplatform is different from Jetpack Compose as the latter is only compatible with Android. Google provides a JetPack compose tutorial for Android development.
Compose HTML is not cross-platform
Compose HTML is UI a library targeting Kotlin/JS which is not compatible with Compose Multiplatform (it is a different API). For cross-platform UI development with Compose Multiplatform, compose Web is the choice.
🧪 Create a Compose multiplatform app
We'll create a multiplatform app using the official template. At the time of writing, this template does not include a compose web target.
- Please check that your environment is correctly setup as explained here.
- On Windows and Linux, we don't need to install iOS/macOS related tools but and we won't be able to run iOS/macOS targets.
- If we don't want to install Android Studio, we need at least to install the Android SDK either through the official installer or from the "Languages and Framework -> Android SDK" menu in the settings.
- Open the official template and either download a zip or use the "use this template" options on GitHub.
- Open the downloaded projet. You'll note that it contains these modules:
- a shared module (or subproject) that contains common code as well as
- and another module for earch targeted platform: androidApp, iOSApp and desktopApp (When web will be included in the template, we should also see a webApp project). These contain the source code of the apps itself (such as the main activity in Android, the
@main App
in iOS and the main function in desktopJVM) and well as platform specific resources that cannot be placed in the shared module. Some examples of such files are the AndroidManifest.xml for android and the info.plist in iOS.- In order to run the desktopApp, open a terminal on the project root folder and launch this command:
./gradlew desktopApp:run
.- In order to run the Android App, the simplest way is to launch it from IntelliJ . It is also possible define a gradle task that installs the app on the device and issues a command to the device to launch it.
- In order to run the iOS App, the simplest way is to run it on the simulator using IntelliJ. In order to run it on a real device, the TramID needs to be defined as explained here
🧪 Playing with the Compose multiplatform API
Compose multiplatform is a component based declarative UI framework. Each component is called a
Composable
and is defined as a function annotated with@Composable
.In compose multiplatform, the main component (the component at the root of the App) is usually found in shared/src/commonMain/Kotlin/App.kt.
- Take a look at shared/src/commonMain/Kotlin/App.kt, run the app and try to understand how compose works.
- Let's create a new composable called
RandomNumberList
.@Composable +fun RandomNumberList(){ + // Generate a list of random numbers + val myRandomValues = List(5) { Random.nextInt(0, 30) } + // LazyColumn is a vertically scrolling list that renders items on demand + LazyColumn { + items(myRandomValues.size){ + Text(text = "$it") + } + } +} +
- Place this composable below
AnimatedVisibility
andButton
and run the app./* +Button(onClick ... +AnimatedVisibility(showImage) { ... +*/ +RandomNumberList() +
- Exercise: Make the "Hello, .." button switch between showing the list and and the image.
🎯 Solutions
📖 Further reading
',44),c=[p];function m(d,u){return a(),t("div",null,c)}const g=n(r,[["render",m],["__file","index.html.vue"]]),k=JSON.parse('{"path":"/en/front-development/","title":"📚 Frontend development","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Kotlin Multiplatform (KMP)","slug":"kotlin-multiplatform-kmp","link":"#kotlin-multiplatform-kmp","children":[]},{"level":2,"title":"Kotlin/JS and Kotlin/WASM","slug":"kotlin-js-and-kotlin-wasm","link":"#kotlin-js-and-kotlin-wasm","children":[{"level":3,"title":"🧪 Kotlin/WASM web app","slug":"🧪-kotlin-wasm-web-app","link":"#🧪-kotlin-wasm-web-app","children":[]},{"level":3,"title":"Kotlin/JS and Kotlin/WASM common points","slug":"kotlin-js-and-kotlin-wasm-common-points","link":"#kotlin-js-and-kotlin-wasm-common-points","children":[]}]},{"level":2,"title":"Compose multiplatform","slug":"compose-multiplatform","link":"#compose-multiplatform","children":[{"level":3,"title":"🧪 Create a Compose multiplatform app","slug":"🧪-create-a-compose-multiplatform-app","link":"#🧪-create-a-compose-multiplatform-app","children":[]},{"level":3,"title":"🧪 Playing with the Compose multiplatform API","slug":"🧪-playing-with-the-compose-multiplatform-api","link":"#🧪-playing-with-the-compose-multiplatform-api","children":[]}]},{"level":2,"title":"🎯 Solutions","slug":"🎯-solutions","link":"#🎯-solutions","children":[]},{"level":2,"title":"📖 Further reading","slug":"📖-further-reading","link":"#📖-further-reading","children":[]}],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"en/front-development/README.md"}');export{g as comp,k as data}; diff --git a/assets/index.html-Bht4bXi_.js b/assets/index.html-Bht4bXi_.js new file mode 100644 index 00000000..2646d871 --- /dev/null +++ b/assets/index.html-Bht4bXi_.js @@ -0,0 +1,7 @@ +import{_ as l,c as I,o as g,a}from"./app-DAFlGqDu.js";const c={},i=a(`📚 Kotlin language features
Kotlin is an object oriented language with functional features. This chapter covers important and relevant features of the language slit into basic and intermediate. Another chapter covers advanced features.
Basic features
Basic constructs (variables, control flow)
- Kotlin is statically typed and supports implicit typing.
- Static typing: types cannot change on runtime (it is the opposite of dynamic typing).
- Implicit typing: the compiler can infer the type whenever possible.
var
creates mutable variables.val
creates immutable variables or constants.- Semi-colons are optional.
- Kotlin supports top level declaration of variables and functions (They can be declared outside of classes).
- String interpolation is available with this syntax
\${expression}
.if
, andwhen
statements are expressions (they can return a value).
when
is equivalent to theswitch
statement of other languages- The ternary operator is not available but the
if
statement replaces-it.- for-each is the only type of for loop available.
- Object oriented programming is supported as in Java with some additional features such as extensions.
- The compiler supports Null Safety. It allows to write code free from null pointer errors with a compile time guarantee.
- Functional programming is supported (Higher-order functions and functions as 1st class items, etc.).
Use val by default
Replace by
var
only if needed.▶️ this code highlights the above features.
Functions
In the this section, the terms 'argument' and 'parameter' are used interchangeably.
Functions in Kotlin have the following features:
- Declaration:
fun functionName(arg1: type1 = defaultvalue1, ...) : retunrType
.- Call a function by passing the value in the declaration order.
- Use argument labels for more clarity, however, it also allows for arbitrary ordering of arguments.
- Optional arguments have a default value and can be omitted during the call.
- Functions are first class items or citizen: they can be assigned to a variable, passed as a function parameter, or returned from a function.
- 💡 A function that takes a function as an argument or returns one is a higher order function.
- A function type can be expressed as follows:
(typeOfParam1, typeOfParam2, etc) -> returnType
(The empty return type isUnit
).- Anonymous functions use the following syntax
{ argName1, argName2, etc. -> // code }
- Also called or lambda functions or literal functions
- The last function argument can be put after the closing after the closing parenthesis
compute(9, 5) { x, y -> x * y }
▶️ this code highlights the above features.
The next section talk about null safety.
Null safety
In a nutshell, null safety is a compiler feature that eliminates the infamous Null pointer exception or npe. The Kotlin compiler reports errors and warnings when we manipulate nullable (also called optional) values. Here is a list of null safety features provided by Kotlin:
- All types are non-nullable by default; we cannot assign
null
to a variable or an argument.
- For example, this code fails
var s: String = null
.- A type can be made nullable by suffixing it with a ?. For example:
var s: String? = null
.- Kotlin forbids calling a method or a property of a non-nullable type, unless we do one of these possibilities:
- Use optional chaining with the ? suffix.
- Provide a default value with the elvis ?: operator.
- Smart-cast the nullable into a non-nullable.
- Use the !! operator that eliminates compiler checks. This should never be used.
Never unwrap with !!
Use other safe techniques instead.
▶️ this code illustrates null safety and how to use optional types.
Java \`Optional\` does not provide compile time null checks
Optional
wrap null values on runtime. The Java compiler (as of version 17) does not provide unwrapping features such as smart casting. It is still possible to have a npe like this:Optional<String> s = null; s.isPresent()
;Enumerations
Enumerations allow to work with a group of values in a type-safe fashion. Unlike Java enums, Kotlin enums are classes. Kotlin enum classes provide these features:
when
statements support enumerations.- Enum constants can declare their own anonymous classes with their corresponding methods, as well as with overriding base methods.
- An enum class can implement an interface but it cannot derive from a class
- There are methods for listing the defined enum constants and getting an enum constant by its name.
- Every enum constant has properties for obtaining its name and position (starting with 0).
▶️ this code illustrated the features above. For further reading please consult the official documentation.
🧪 Exercises
Exercise 1
Please click on this link to view the exercise
Please open to see the solution(s)
Exercise 2
Please click on this link to view the exercise
Please open to see the solution(s)
Intermediate features
Object oriented programming
Kotlin allows to write concise OOP code and has the following features:
- Available common features: classes, inheritance, interfaces, and abstract classes.
- Native support of properties: do not define getters and setters unless needed.
- Just add
get()
andset(value)
functions next to the property declaration.- Constructor arguments are defined next to the class name
class ClassName(arg1, atg2, )
- Prefixing a constructor arguments with
val
orvar
makes it a property (val
makes it read-only).- The constructor name is
init
and does not require parameters.- The compiler checks that all non-nullable properties are initialized by the end of the constructor.
- ⚠️ The compiler does not check the initialization of
lateinit
properties. Thus, accessing them before while uninitialized causes an exception.- Prefix classes with
open
to allow inheritance.- Kotlin enables the
public
access level by default.- The equality operator
==
callsequals()
(as opposed to Java which uses reference equality).- A companion object contains static methods and properties.
- Extensions add function and properties to existing classes.
- 💡 They replace inheritance in many situations.
- For example, we can add functions to the String class instead of creating a new
StringUtils
class.- Sealed classes and interfaces cannot be extended or implemented by third parties.
Do not define accessors unless needed
As opposed to Java, Kotlin supports properties and allows to add accessors later without refactoring the code that calls these properties. Thus, by default, just define the name of properties without accessors and use them directly.
▶️ this code illustrates some features.
Data class
Data classes are final (cannot be inherited from) classes that provide standard functionality:
equals()
andhashCode()
toString()
of the form"class(field=value, ...)"
componentN()
that correspond to the properties in their order of declaration.copy()
However, they have the following constraints:
- The primary constructor needs to have at least one parameter.
- All primary constructor parameters need to be marked as val or var.
- They cannot be abstract, open, sealed, or inner (💡 but extensions are possible).
▶️ this code illustrates some features.
Functional programming
General concepts
Functional programming revolves around these concepts: pure functions, recursion, referential transparency, immutable variables, functions as first-class citizens, and higher-order functions.
Let's briefly explain these concepts:
- Immutable variables means that we cannot change the value of a variable or its properties once it has been created. If we want to do so, we must create a new instance with the new value.
- Pure functions are functions that do not have side effects and will thus return always the same output given the same input.
- Functions are first class citizens: they can be assigned to a variable or used in higher-order functions (passed as a function parameter to another function or returned from a function).
- Referential transparency: means that an expression can be replaced by its result without changing the behavior of the program. Transparency refers to the fact that the implementation of the expression is irrelevant.
💡 Pure functional languages provide these features natively and enforces them (at build time).
Kotlin and functional programming
Kotlin is not a pure functional languages but it supports some features. For example, Kotlin does not have compile time verification of pure functions, but it provides immutable collections through the kotlinx.collections.immutable library.
listOf generates read-only lists, which are not immutable
A read-only list cannot add or remove elements, but it can change the underlying data.
@Test +fun givenReadOnlyList_whenCastToMutableList_checkNewElementsAdded(){ + val list: List<String> = listOf("This", "Is", "Totally", "Immutable") + (list as MutableList<String>)[2] = "Not" + assertEquals(listOf("This", "Is", "Not", "Immutable"), list) +} +
The Arrow-kt library add more functional programming features.
Declarative programming
Declarative programming is a famous style within functional programming. It consists of writing code as a chaining of function calls in this style
val result = f(x).g(y). ...
. Higher order functions replace many situation where we would use loops. This favors readable code which is easy to debug an maintain.▶️ this code show an example of list manipulation using declarative programming.
Kotlin and Java interoperability
- Kotlin is designed with Java interoperability in mind.
- Kotlin code may require some annotations to be called from Java.
- It is possible to mix Java and Kotlin in the same project.
- JetBrain's IntelliJ and Android Studio can convert to Kotlin when pasting java code.
- Kotlin generates Java records by annotating a data class with
@JvmRecord
and targeting JVM 16, among other requirement listed here.- It is much more easier and natural to call Java from Kotlin.
- For example: Java accessors are converted to Kotlin properties.
▶️ this code shows how to convert a Kotlin
List
to a JavaArrayList
.The official documentation provides exhaustive documentation on Kotlin and JVM integration
🧪 Exercises
Exercise 3
Please click on this link to view the exercise
Please open to see the solution(s)
Exercise 4
Please click on this link to view the exercise
Please open to see the solution(s)
Exercise 5
Please click on this link to view the exercise
Please open to see the solution(s)
Exercise 6
Please click on this link to view the exercise
Please open to see the solution(s)
Exercise 7
Please click on this link to view the exercise
Please open to see the solution(s)
📖 Further reading
`,77),e=[i];function n(b,s){return g(),I("div",null,e)}const G=l(c,[["render",n],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/en/kotlin-features/","title":"📚 Kotlin language features","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Basic features","slug":"basic-features","link":"#basic-features","children":[{"level":3,"title":"Basic constructs (variables, control flow)","slug":"basic-constructs-variables-control-flow","link":"#basic-constructs-variables-control-flow","children":[]},{"level":3,"title":"Functions","slug":"functions","link":"#functions","children":[]},{"level":3,"title":"Null safety","slug":"null-safety","link":"#null-safety","children":[]},{"level":3,"title":"Enumerations","slug":"enumerations","link":"#enumerations","children":[]},{"level":3,"title":"🧪 Exercises","slug":"🧪-exercises","link":"#🧪-exercises","children":[]}]},{"level":2,"title":"Intermediate features","slug":"intermediate-features","link":"#intermediate-features","children":[{"level":3,"title":"Object oriented programming","slug":"object-oriented-programming","link":"#object-oriented-programming","children":[]},{"level":3,"title":"Data class","slug":"data-class","link":"#data-class","children":[]},{"level":3,"title":"Functional programming","slug":"functional-programming","link":"#functional-programming","children":[]},{"level":3,"title":"Kotlin and Java interoperability","slug":"kotlin-and-java-interoperability","link":"#kotlin-and-java-interoperability","children":[]},{"level":3,"title":"🧪 Exercises","slug":"🧪-exercises-1","link":"#🧪-exercises-1","children":[]}]},{"level":2,"title":"📖 Further reading","slug":"📖-further-reading","link":"#📖-further-reading","children":[]}],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"en/kotlin-features/README.md"}');export{G as comp,d as data}; diff --git a/assets/index.html-BnOna0wn.js b/assets/index.html-BnOna0wn.js new file mode 100644 index 00000000..ccd82385 --- /dev/null +++ b/assets/index.html-BnOna0wn.js @@ -0,0 +1 @@ +import{_ as i,a as r,b as s,c as d}from"./qrcode-mixtit24-D-YZ7Myr.js";import{_ as p,r as u,c,d as e,b as n,w as o,e as l,a,o as f}from"./app-DAFlGqDu.js";const m="/learning-kotlin/assets/qrcode-devoxxma23-Beeff8NO.gif",h={},g=a('📅 Workshops
Android makers 2023: Kotlin Beyond Android
Liens
Lien du workshop: awl.li/am23-kt
Agenda
',7),k=e("li",null,[e("a",{href:"https://worldline.github.io/learning-kotlin-multiplatform/",target:"_blank",rel:"noopener noreferrer"},"Front-end development: Cross-platform Quiz App with Compose multiplatform")],-1),v=a('Mobile DevOps summit 2023
- Titre: From Android to Multiplatform: leveraging the full potential of Kotlin
- Lien: awl.li/mds23-kt
Agenda
',4),_=a('Devoxx Morocco 2023
- Titre: Let's discover the amazing possibilities of Kotlin
- Lien: awl.li/mds23-kt
Agenda
',4),b=a('(2024) MiXit
- Titre : Développement front et back en Kotlin. Une visite guidée de KMP
Agenda
',4),x=e("strong",null,"Développement backend",-1),w=e("li",null,[e("a",{href:"https://speakerdeck.com/yostane/kotlin-pour-le-developpement-backend",target:"_blank",rel:"noopener noreferrer"},"Présentation d'introduction à Kotlin pour le développement backend")],-1),A=e("strong",null,"Développement frontend",-1),K=e("strong",null,"Développement fullstack",-1),y=e("strong",null,"Autres fonctionnalités",-1);function M(q,D){const t=u("RouteLink");return f(),c("div",null,[g,e("ul",null,[e("li",null,[n(t,{to:"/fr/backend-development/#ktor"},{default:o(()=>[l("Backend development: Rest API with Ktor")]),_:1})]),e("li",null,[l("Optional: "),n(t,{to:"/fr/backend-development/#ktor"},{default:o(()=>[l("Backend development: Rest API with node.js and Kotlin/JS")]),_:1})]),e("li",null,[n(t,{to:"/fr/front-development/#kotlin-js-and-kotlin-wasm"},{default:o(()=>[l("Front-end development: Kotlin/WASM and Kotlin/JS webapp")]),_:1})]),e("li",null,[n(t,{to:"/fr/front-development/#compose"},{default:o(()=>[l("Front-end development: cross-platform Hello world App with Compose multiplatform")]),_:1})]),k,e("li",null,[n(t,{to:"/fr/other-technologies/#pw-add-a-ktor-server-app"},{default:o(()=>[l("Full-stack development: Quiz App with Compose multiplatform and Ktor server")]),_:1})])]),v,e("ul",null,[e("li",null,[n(t,{to:"/fr/other-technologies/#pw-add-a-ktor-server-app"},{default:o(()=>[l("Développement d'une application multi-plateformes")]),_:1})]),e("li",null,[l("Démonstration des autres possibilités: "),e("ul",null,[e("li",null,[n(t,{to:"/fr/backend-development/#ktor"},{default:o(()=>[l("API Rest avec Ktor")]),_:1})]),e("li",null,[n(t,{to:"/fr/backend-development/#ktor"},{default:o(()=>[l("API Rest avec node.js et Kotlin/JS")]),_:1})]),e("li",null,[n(t,{to:"/fr/front-development/#kotlin-js-and-kotlin-wasm"},{default:o(()=>[l("Webapp Kotlin/WASM et Kotlin/JS")]),_:1})])])])]),_,e("ul",null,[e("li",null,[n(t,{to:"/fr/presentation/#prerequisites"},{default:o(()=>[l("Prérequis")]),_:1})]),e("li",null,[l("Caractéristiques notables de Kotlin: "),n(t,{to:"/fr/kotlin-features/#null-safety"},{default:o(()=>[l("le null safety")]),_:1}),l(" et "),n(t,{to:"/fr/kotlin-features/#functions"},{default:o(()=>[l("les fonctions")]),_:1})]),e("li",null,[n(t,{to:"/fr/backend-development/#spring-framework"},{default:o(()=>[l("API Rest avec Spring boot")]),_:1})]),e("li",null,[n(t,{to:"/fr/backend-development/#ktor"},{default:o(()=>[l("API Rest avec Ktor")]),_:1})]),e("li",null,[n(t,{to:"/fr/front-development/#compose"},{default:o(()=>[l(`Développement d'une application multi-plateformes type "Hello World" avec Compose Multiplatform`)]),_:1})]),e("li",null,[n(t,{to:"/fr/other-technologies/#pw-add-a-ktor-server-app"},{default:o(()=>[l("Dévelopment sullstack d'une application de Quiz avec with Compose multiplatform en front et Ktor server en backend")]),_:1})]),e("li",null,[l("Autres fonctionnalités et possibilités: "),e("ul",null,[e("li",null,[n(t,{to:"/fr/front-development/#kotlin-js-and-kotlin-wasm"},{default:o(()=>[l("Kotlin/WASM")]),_:1})]),e("li",null,[n(t,{to:"/fr/backend-development/#nodejs"},{default:o(()=>[l("Serveur node.js avec Kotlin/JS")]),_:1})]),e("li",null,[n(t,{to:"/fr/kotlin-features-advanced/#concurrency-and-coroutines"},{default:o(()=>[l("Coroutines")]),_:1})])])])]),b,e("ul",null,[e("li",null,[n(t,{to:"/fr/presentation/#prerequisites"},{default:o(()=>[l("Prérequis")]),_:1})]),e("li",null,[l("Fonctionnalités notables: "),n(t,{to:"/fr/kotlin-features/#null-safety"},{default:o(()=>[l("null safety")]),_:1}),l(" et "),n(t,{to:"/fr/kotlin-features/#functions"},{default:o(()=>[l("les fonctions")]),_:1})]),e("li",null,[x,e("ul",null,[w,e("li",null,[n(t,{to:"/fr/backend-development/#spring-framework"},{default:o(()=>[l("API Rest avec Spring boot")]),_:1})]),e("li",null,[n(t,{to:"/fr/backend-development/#ktor"},{default:o(()=>[l("API Rest avec Ktor")]),_:1})])])]),e("li",null,[A,e("ul",null,[e("li",null,[n(t,{to:"/fr/front-development/#compose"},{default:o(()=>[l('Application "Hello World" avec Compose Multiplatform')]),_:1})])])]),e("li",null,[K,e("ul",null,[e("li",null,[n(t,{to:"/fr/other-technologies/#pw-add-a-ktor-server-app"},{default:o(()=>[l("Application de quiz avec Ktor + Compose Multiplatform")]),_:1})])])]),e("li",null,[y,e("ul",null,[e("li",null,[n(t,{to:"/fr/front-development/#kotlin-js-and-kotlin-wasm"},{default:o(()=>[l("Kotlin/WASM")]),_:1})]),e("li",null,[n(t,{to:"/fr/backend-development/#nodejs"},{default:o(()=>[l("Développement node.js en Kotlin")]),_:1})]),e("li",null,[n(t,{to:"/fr/kotlin-features-advanced/#concurrency-and-coroutines"},{default:o(()=>[l("Coroutines")]),_:1})])])])])])}const P=p(h,[["render",M],["__file","index.html.vue"]]),R=JSON.parse('{"path":"/fr/workshops/","title":"📅 Workshops","lang":"fr-FR","frontmatter":{},"headers":[{"level":2,"title":"Android makers 2023: Kotlin Beyond Android","slug":"android-makers-2023-kotlin-beyond-android","link":"#android-makers-2023-kotlin-beyond-android","children":[{"level":3,"title":"Liens","slug":"liens","link":"#liens","children":[]},{"level":3,"title":"Agenda","slug":"agenda","link":"#agenda","children":[]}]},{"level":2,"title":"Mobile DevOps summit 2023","slug":"mobile-devops-summit-2023","link":"#mobile-devops-summit-2023","children":[{"level":3,"title":"Agenda","slug":"agenda-1","link":"#agenda-1","children":[]}]},{"level":2,"title":"Devoxx Morocco 2023","slug":"devoxx-morocco-2023","link":"#devoxx-morocco-2023","children":[{"level":3,"title":"Agenda","slug":"agenda-2","link":"#agenda-2","children":[]}]},{"level":2,"title":"(2024) MiXit","slug":"_2024-mixit","link":"#_2024-mixit","children":[{"level":3,"title":"Agenda","slug":"agenda-3","link":"#agenda-3","children":[]}]}],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"fr/workshops/README.md"}');export{P as comp,R as data}; diff --git a/assets/index.html-Bxjju2_F.js b/assets/index.html-Bxjju2_F.js new file mode 100644 index 00000000..879dbaab --- /dev/null +++ b/assets/index.html-Bxjju2_F.js @@ -0,0 +1,35 @@ +import{_ as n}from"./kotlin-wasm-webapp-R4_9ho9v.js";import{_ as e,c as s,o as a,a as t}from"./app-DAFlGqDu.js";const l="/learning-kotlin/assets/kotlin-wasm-flag-BKaaN9Pq.png",o="/learning-kotlin/assets/wasm-build-conf-edit-CmamvRv7.png",i="/learning-kotlin/assets/wasm-run-configuration-x_w9-EC1.png",p="/learning-kotlin/assets/compose-multiplaform-web-wH6XfCHb.gif",r="/learning-kotlin/assets/compose-multiplaform-Dfyu_rxB.gif",u={},c=t('📚 Développement frontend
Kotlin supporte une large sélection de frameworks frontaux sur toutes les plateformes : mobile, desktop et web. Vous trouverez ci-dessous un aperçu des possibilités que vous pouvez faire directement à partir d'IntelliJ :
- Côté bureau
- Grâce au support de la JVM, Kotlin supporte JavaFX. 💡 Il existe même un équivalent en Kotlin appelé tornadofx.
- Compose Multiplatform apporte l'API Jetpack Compose sur le bureau, le web et le mobile.
- Sur le web
- Ktor peut utiliser des moteurs de modèles tels que FreeMarker pour créer des pages de serveur.
- Avec KotlinJS, les développeurs peuvent créer des applications React, nodsjs ou vanilla JS en utilisant Kotlin.
- Kotlin WASM se compile en Web Assembly. Il peut compléter KotlinJS pour les tâches à forte intensité de calcul.
- Compose Multiplatform apporte deux options sur le web: Compose web et Compose for Web Canvas.
- Sur les mobiles
- Les développeurs Android utilisent Jetpack Compose ou l'ancienne méthode de layout XML.
- Compose Multiplatform supporte Android de façon stable et iOS de façon expérimentale.
Comme nous pouvons le voir, Kotlin propose plusieurs options. L'option la plus séduisante en terme de partage de code est Compose Multiplatform. Ceci est possible notamment grâce à KMP
KMP
- KMP (Kotlin Multiplatform) permet de partager une base de code unique sur plusieurs cibles.
- KMP s'appuie sur Kotlin native et d'autres fonctionnalités de Kotlin pour aider les développeurs à créer des projets destinés à plusieurs plates-formes en utilisant une base de code Kotlin commune.
- De nombreuses combinaisons de cibles et de cas d'utilisation sont possibles :
- Full-Stack web apps : Un projet qui contient un backend et une application web tout en partageant une logique commune.
- Bibliothèques multiplateformes
- KMM est une ancienne dénomination qui est dépréciée.
Dans la suite de ce chapitre, nous explorerons les différentes possibilités individuellement et on fera un projet KMP dans le chapitre suivant.
Kotlin/JS et Kotlin/WASM
- Kotlin/JS peut également cibler le web et même utiliser des frameworks web (tels que react) dans Kolitn.
- Kotlin WASM est une autre possibilité de cibler le web, mais il génère WASM au lieu de code JS pur.
- Il peut être utilisé par exemple pour développer des bibliothèques à forte intensité de calcul.
- Nous pourrons peut-être faire encore plus à l'avenir grâce à l'évolution de toutes ces technologies (Kotlin, WASM et Kotlin/WASM). - Par exemple, [WASI] (https://wasi.dev/) permet à WASM de communiquer avec le système d'exploitation. - Cela signifie que je pourrais voir des projets Kotlin/WASM à l'avenir qui peuvent cibler à la fois le navigateur et le système d'exploitation.
- Continuons à observer 😄.
🧪 Application web Kotlin/WASM
- Les assistants de création de projet Kotlin/WASM et Kotlin/JS sur IntelliJ fonctionnent de manière similaire:
- L'IDE génère un fichier Kotlin qui sera compilé par la suite en WASM et/ou JS. Kotlin/JS ne génère que du JS tandis que Kotin/WASM génère à la fois du JS et du WASM.
- Dans les deux cas, le point d'entrée du code généré est un fichier JS appelé nom_du_module.js.
- L'IDE génère également dans le dossier des ressources un fichier index.html dont le but est de charger le JS généré (le fichier nom_du_module.js).
- La tâche
wasmBrowserDevelopmentRun
oujsWasmBrowserDevelopmentRun
lancera un serveur local qui hébergera à la fois les fichiers index.html et les fichiers JS et WASM générés.- Créons une application Kotlin/WASM. Tout d'abord, activez l'assistant Kotlin/WASM en activant kotlin.wasm.wizard dans le registre d'IntelliJ (ouvrez le registre en appuyant deux fois sur shift et en tapant "registry" dans la boîte de recherche). Alternativement, clonez ce projet.
- Vérifiez qu'on est sur la dernière version de Kotlin dans build.gradle.kts (l'assistant peut le configurer à une version antérieure).
- Ouvrez src/wasmMain/kotlin/sample.kt et cliquez sur le bouton lancer qui apparaît à côté de la fonction
main
.- Si la compilation échoue parce que l'IDE a utilisé la mauvaise tâche gradle, veuillez la changer en
wasmBrowserDevelopmentRun
et essayez de l'exécuter à nouveau.
- Le serveur de développement devrait démarrer et vous pouvez ouvrir votre application web sur http://localhost:8080/
- ⚠️ Il se peut que vous deviez activer certains drapeaux sur votre navigateur pour que l'application fonctionne. Si vous voyez une page blanche, veuillez lire les journaux du navigateur pour vérifier les instructions.
- Le fichier wasm généré est disponible dans build/js/packages/nom_du_projet/kotlin
- WASM étant un format binaire, nous devons d'abord le convertir au format texte.
- Nous pouvons soit installer [WABT (The WebAssembly Binary Toolkit ou wabbit)] (https://github.com/WebAssembly/wabt) et utiliser l'outil wasm2wattool
wasm2wat --enable-all -v .\\kotlin-wasm-demo-wasm.wasm -o wasm.wat
,- ou utiliser un convertisseur en ligne comme celui-ci
- ❗ Cependant, je n'ai pas réussi à le faire fonctionner
🧪 Application web KotlinJS
L'assistant Kotlin/JS crée une application très similaire à celle de Kotlin/WASM. Dans un prochain PW, nous créerons une application complète avec Ktor et Kotlin/JS.
Compose
Compose multiplatform est une famille de frameworks d'interface utilisateur déclaratifs pour Android (Jetpack Compose), le bureau (Compose Desktop) et le web (Compose Web). Il dispose d'un support expérimental pour iOS et Web Canvas.
Compose multiplatform vs Jetpack Compose
Bien que très similaire, Compose multiplatform est différent de Jetpack Compose car ce dernier n'est compatible qu'avec Android. Google fournit un JetPack compose tutorial pour le développement Android.
Compose Web vs Compose for Web Canvas
- La surface de l'API de Compose Web est différente des autres cibles de Compose car elle travaille directement avec le DOM.
- Compose for Web Canvas a la même surface d'API que celle du Desktop, Android et iOS car il dessine sur un Canvas et ne manipule pas le DOM.
Cela signifie que le premier a un meilleur support web et que le second a plus de code réutilisable.
🧪 Compose Web
- Créez un nouveau projet IntelliJ -> Compose Multiplaform.
- Choisissez "Single platform" -> "Web" et remplissez les autres champs.
- Choisissez Finish
- IntelliJ peut prendre un certain temps pour préparer le projet et peut demander d'installer des plugins supplémentaires.
- Lancez le serveur de développement de l'application web à l'aide de la commande
./gradlew jsBrowserRun --continuous
.- Modifiez
Main.kt
comme suit et lancez l'application.- Ouvrez cette adresse :
localhost:8080
.fun main() { + renderComposable(rootElementId = "root") { + Div({ style { padding(25.px) } }) { + var expanded by remember { mutableStateOf(false) } + Button( + attrs = { + onClick { expanded = !expanded } + } + ) { Text("Cliquez sur moi") } + Div({ style { display(if (expanded) DisplayStyle.Block else DisplayStyle.None) } }) { + Text("Cliquez sur moi !") + } + } + } +} +
🧪 Compose desktop + Android app
- Créez un nouveau projet sur IntelliJ -> Compose Multiplatform.
- Choisissez "multiple platforms" et remplissez les autres champs. Choisissez ensuite Finish.
- IntelliJ commence à préparer le projet et peut demander l'installation de plugins.
- Une fois le projet prêt, lancez l'application Android en utilisant le bouton vert run.
- Lancez l'application desktop en exécutant la fonction principale du projet desktop (qui devrait se trouver dans
Main.kt
).- Modifiez
App.kt
dans le projet principal comme suit et lancez l'application.@Composable +fun App() { + val platformName = getPlatformName() + Card { + var expanded by remember { mutableStateOf(false) } + Column(Modifier.clickable { expanded = !expanded }) { + Texte( + text="Cliquez sur moi !", + style = MaterialTheme.typography.h2 + ) + AnimatedVisibility(expanded){ + Texte( + text = "Bonjour, \${platformName} 🎊", + style = MaterialTheme.typography.h1 + ) + } + } + } +} +
Pour aller plus loin
',35),d=[c];function m(k,f){return a(),s("div",null,d)}const v=e(u,[["render",m],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/fr/front-development/","title":"📚 Développement frontend","lang":"fr-FR","frontmatter":{},"headers":[{"level":2,"title":"KMP","slug":"kmp","link":"#kmp","children":[]},{"level":2,"title":"Kotlin/JS et Kotlin/WASM","slug":"kotlin-js-et-kotlin-wasm","link":"#kotlin-js-et-kotlin-wasm","children":[{"level":3,"title":"🧪 Application web Kotlin/WASM","slug":"🧪-application-web-kotlin-wasm","link":"#🧪-application-web-kotlin-wasm","children":[]},{"level":3,"title":"🧪 Application web KotlinJS","slug":"🧪-application-web-kotlinjs","link":"#🧪-application-web-kotlinjs","children":[]}]},{"level":2,"title":"Compose","slug":"compose","link":"#compose","children":[{"level":3,"title":"🧪 Compose Web","slug":"🧪-compose-web","link":"#🧪-compose-web","children":[]},{"level":3,"title":"🧪 Compose desktop + Android app","slug":"🧪-compose-desktop-android-app","link":"#🧪-compose-desktop-android-app","children":[]}]},{"level":2,"title":"Pour aller plus loin","slug":"pour-aller-plus-loin","link":"#pour-aller-plus-loin","children":[]}],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"fr/front-development/README.md"}');export{v as comp,h as data}; diff --git a/assets/index.html-CDLKHirq.js b/assets/index.html-CDLKHirq.js new file mode 100644 index 00000000..6a1374e6 --- /dev/null +++ b/assets/index.html-CDLKHirq.js @@ -0,0 +1,13 @@ +import{_ as n,c as e,o as a,a as s}from"./app-DAFlGqDu.js";const t={},i=s(`📚 Advanced and other Kotlin features
Delegated properties
Kotlin allows to delegate the getter and setter of a property to another object, which is called a delegate. It is a class that defines the
getValue
andsetValue
methods.Kotlin provides standard delegates such lazy properties and observable properties.
▶️ this code illustrates delegate properties.
Concurrency and Coroutines
Kotlin provides a high level concurrency model called Coroutines. The developer can delegated the management of threads to the compiler and runtime and using higher level constructs than threads to express asynchronous operations.
Coroutines in Kotlin revolve around these concepts:
- A coroutine is an instance of suspendable computation.
- Kotlin has many methods for creating a coroutine such as
launch
.- A coroutine must exist within a coroutine scope.
- For example
runBlocking
creates a coroutine scope whithin which coroutines can be launched.- A coroutine can run suspend functions which can suspend the coroutine but do not block the thread.
- For example: the
delay
suspend the coroutine but does not block the thread on which it is running.- Suspend functions are operations that may take time such http requests and file system calls.
- The
suspend
qualifier defines a suspend function. It runs within a coroutine and can call other suspend functions.Flow
allows to generate a list of asynchronous values.Deferred
andChannel
transfer a single value and a stream of values, respectively, between coroutines.▶️ this code show how to create a coroutine and suspend function and how to use them.
▶️ this code illustrated flows.
▶️ this code illustrates channels and deferred.
Function literal with receiver and Type-safe builders
As seen previously, function extension add behavior to existing classes. Inside the definition of the function extension, we can reference the extension receiver (or this) implicitly.
fun String.countCharacters() = length // or this.length +println("hello".countCharacters()) // prints 5 +
We can define this extension with a function literal (or lambda) in instead of a named function (declared with
fun
).var extFn: String.() -> Int +extFn = { length } // extFn is a function literal +println("hello".extFn()) // prints 5 +println(extFn("hello")) // prints 5 +
extFn
is a function literal (lambda) that has access to the receiver (this). That's why it's called a function literal with receiver.
extFn("hello")
orextFn("hello")
call the extension as expected from extension functions.The type of a function literal with receiver is
funName: ReceiverType.(arg1Type, arg2Type, etc.) -> ReturnType
and is called withfunName(receiverValue, arg1Value, etc.)
orreceiverValue.funName(arg1Value, etc.)
. However, this is not the interesting aspect.The important part is
extFn = { length }
which can be put as a function argument in a higher order function. The developer that calls the higher order function must defineextFn
, which in turn has access to the receiver. This allows for a nice style of programming. ▶️ this code shows an example.Type-safe builders combine well-named builder functions and functions literals with receiver to create type-safe, statically typed builders in Kotlin.
Builder pattern
The builder pattern is a way to construct complex objects.
// StirngBuilder uses the builder pattern +val text = StringBuilder("Temp") + .append(1) + .append(true) + .append("friend") + .toString() +
This code shows a basic type-safe builder.
Kotlin docs provides an example of an advanced type-safe builder that builds an HTML page.
🧪 Exercises
Exercise 1
`,28),l=[i];function c(o,p){return a(),e("div",null,l)}const u=n(t,[["render",c],["__file","index.html.vue"]]),g=JSON.parse('{"path":"/en/kotlin-features-advanced/","title":"📚 Advanced and other Kotlin features","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Delegated properties","slug":"delegated-properties","link":"#delegated-properties","children":[]},{"level":2,"title":"Concurrency and Coroutines","slug":"concurrency-and-coroutines","link":"#concurrency-and-coroutines","children":[]},{"level":2,"title":"Function literal with receiver and Type-safe builders","slug":"function-literal-with-receiver-and-type-safe-builders","link":"#function-literal-with-receiver-and-type-safe-builders","children":[]},{"level":2,"title":"🧪 Exercises","slug":"🧪-exercises","link":"#🧪-exercises","children":[]}],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"en/kotlin-features-advanced/README.md"}');export{u as comp,g as data}; diff --git a/assets/index.html-CDbrj19k.js b/assets/index.html-CDbrj19k.js new file mode 100644 index 00000000..c618c4a8 --- /dev/null +++ b/assets/index.html-CDbrj19k.js @@ -0,0 +1 @@ +import{_ as e}from"./kmp_codelab-CiTPMWjt.js";import{_ as t,c as a,o,a as r}from"./app-DAFlGqDu.js";const i={},n=r('
- open the java-integration-exercise projects in the materials folder.
- Have a look at the Java class we provided you in the
src/main/java/com/worldline/learning/kotlin/java2kotlin
package. (yes, that's the Pokemon class)- Convert that Java class in Kotlin using IntelliJ's awesome copy-pasta tool! (just copy paste the java code in a kotlin file, one is provided at
src/main/kotlin/com/worldline/learning/kotlin/java2kotlin
)- Have a look at the generated Kotlin code, and note the major differences you spot!
🛠 Let's make a cross-plaform app !
By combining KMP and Compose, it is possible to fully develop cross-platform mobile, web and desktop apps using only Kotlin.
Prerequisites
- Basic knowledge of kotlin development (nullability,inline & lambda functions mainly)
- Android Studio IDE with latest stable version Giraffe version or above
- A good connectivity
TIP
For more information about your DEV environment and installs please have a look to jetbrain related docs
_____ 🚀 Start the practical work here 🚀_____
',7),l=[n];function s(p,c){return o(),a("div",null,l)}const h=t(i,[["render",s],["__file","index.html.vue"]]),f=JSON.parse(`{"path":"/en/other-technologies/","title":"🛠 Let's make a cross-plaform app !","lang":"en-US","frontmatter":{},"headers":[],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"en/other-technologies/README.md"}`);export{h as comp,f as data}; diff --git a/assets/index.html-C_RVdbGD.js b/assets/index.html-C_RVdbGD.js new file mode 100644 index 00000000..557a03ec --- /dev/null +++ b/assets/index.html-C_RVdbGD.js @@ -0,0 +1 @@ +import{_ as e}from"./kmp_codelab-CiTPMWjt.js";import{_ as t,c as n,o as r,a}from"./app-DAFlGqDu.js";const i={},o=a('🛠 Construisons une app multiplateforme !
En combinant KMP, KMM et Compose, il est possible de développer des applications mobiles et de bureau multiplateformes en utilisant uniquement Kotlin.
Prérequis
- Connaissance de base du développement en Kotlin (notamment la nullabilité, les fonctions en ligne et les fonctions lambda)
- IDE Android Studio avec la version stable la plus récente, version Giraffe ou supérieure
- Une bonne connectivité
TIP
Pour plus d'informations sur votre environnement de développement (DEV) et les installations, veuillez consulter la documentation liée à JetBrains ici.
____ 🚀 Demarrer la session pratique ici 🚀___
',7),s=[o];function l(p,c){return r(),n("div",null,s)}const d=t(i,[["render",l],["__file","index.html.vue"]]),f=JSON.parse('{"path":"/fr/other-technologies/","title":"🛠 Construisons une app multiplateforme !","lang":"fr-FR","frontmatter":{},"headers":[],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"fr/other-technologies/README.md"}');export{d as comp,f as data}; diff --git a/assets/index.html-CeJwufaG.js b/assets/index.html-CeJwufaG.js new file mode 100644 index 00000000..65acfd72 --- /dev/null +++ b/assets/index.html-CeJwufaG.js @@ -0,0 +1,7 @@ +import{_ as l,c as e,o as I,a}from"./app-DAFlGqDu.js";const g={},c=a(`📚 Fonctionnalités du langage Kotlin
Kotlin est un langage qui support les paradigmes orienté objet et fonctionnel. Ce chapitre couvre les caractéristiques basiques et intermédiaires. Le chapitre suivant couvrira les fonctionnalités avancées.
Caractéristiques de base
Constructions de base (variables, flux de contrôle)
- Kotlin est typé statiquement et prend en charge le typage implicite.
- Typage statique : les types ne peuvent pas changer à l'exécution (c'est l'inverse du typage dynamique).
- Typage implicite : le compilateur peut inférer le type tant que c'est possible.
var
crée des variables ré-assignables.val
crée des variables qu'on ne peut plus ré-assigner.- Les points-virgules sont facultatifs.
- Kotlin prend en charge la déclaration de haut niveau des variables et des fonctions (elles peuvent être déclarées en dehors des classes).
- L'interpolation de chaîne de caractères est disponible avec cette syntaxe
\${expression}
.if
etwhen
sont des expressions (elles peuvent renvoyer une valeur).
when
est équivalent à l'instructionswitch
des autres langages- L'opérateur ternaire n'est pas disponible. L'expression
if
le remplace.- for-each est le seul type de boucle for disponible.
- La programmation orientée objet est prise en charge comme en Java avec quelques fonctionnalités supplémentaires telles que les extensions.
- Le compilateur prend en charge la Null Safety. Il permet d'écrire du code sans erreur de pointeur nulle vérifié à la compilation.
- La programmation fonctionnelle est prise en charge (fonctions d'ordre supérieur et fonctions en tant qu'éléments de 1ère classe, etc.).
Utiliser val par défaut
Utiliser
var
uniquement si vous réassignez une variable ou argument.▶️ this code highlights the above features.
Les fonctions
Dans cette section, les termes 'argument' et 'paramètre' sont utilisés de manière interchangeable.
Les fonctions de Kotlin ont les caractéristiques suivantes :
- Déclaration :
fun functionName(arg1 : type1 = defaultvalue1, ...) : retunrType
.- Appeler une fonction en passant la valeur dans l'ordre de déclaration.
- Utilisez des étiquettes d'argument pour plus de clarté, cependant, cela permet également un classement arbitraire des arguments.
- Les arguments optionnels ont une valeur par défaut et peuvent être omis lors de l'appel.
- Les fonctions sont des éléments de première classe ou citoyens : elles peuvent être affectées à une variable, passées en tant que paramètre de fonction ou renvoyées par une fonction.
- 💡 Une fonction qui prend une fonction comme argument ou en renvoie une est une fonction d'ordre supérieur.
- Un type de fonction peut être exprimé comme suit :
(typeOfParam1, typeOfParam2, etc) -> returnType
(Le type de retour vide estUnit
).- Les fonctions anonymes utilisent la syntaxe suivante
{ argName1, argName2, etc. -> // code }
- Aussi appelées fonctions lambda ou fonctions littérales
- Le dernier argument de la fonction peut être mis après la fermeture après la parenthèse fermante
compute(9, 5) { x, y -> x * y }
▶️ Ce code illustre les fonctions en Kotlin.
La prochaine section abordera le null safety.
Null safety
null safety est une fonctionnalité du compilateur qui élimine la fameuse Null pointer exception ou npe. En effet, le compilateur signale des erreurs et des avertissements lorsque nous manipulons des types nullables (également appelées types optionnels) dès qu'il y a un risque de npe à l'exécution. Ainsi, afin de mettre Voici une liste des fonctionnalités de sécurité null fournies par Kotlin :
- Tous les types ne sont pas nullables par défaut ; nous ne pouvons pas affecter
null
à une variable ou à un argument.
- Par exemple, ce code échoue
var s: String = null
.- Un type peut être rendu nullable en le suffixant avec un ?. Par exemple :
var s : chaîne ? = nul
.- Kotlin interdit d'appeler une méthode ou une propriété de type non nullable, sauf si l'on fait l'une de ces possibilités :
- Utilisez le chaînage optionnel avec le suffixe ?.
- Fournissez une valeur par défaut avec l'opérateur elvis ?:.
- Smart-cast le nullable dans un non-nullable.
- Utilisez l'opérateur !! qui élimine les vérifications du compilateur. Cela ne devrait jamais être utilisé.
Ne jamais déballer avec !!
Car cela équivaut à désactiver la null safety. Utilisez les autres possibilités à la place.
▶️ ce code illustrate la null safety et les types optionnels.
La classe \`Optional\` de Java ne fournit aucun protection à la compilation
Ce code lance une npe en Java:
Optional<String> s = null; s.isPresent();
. Le compilateur Java (au moins à la version version 17) ne propose pas d'équivalent à ce que propose Kotlin comme le smart casting.Énumérations
Les énumérations permettent de travailler avec un groupe de valeurs de façon cadrée. Contrairement aux énumérations Java, les énumérations Kotlin sont des classes. Les
enum class
de Kotlin fournissent ces fonctionnalités :
- Les expressions when prennent en charge les énumérations.
- Une
enum class
peut définir des méthodes et implémenter des interfaces mais elle ne peut pas dériver d'une classe.- Il existe des méthodes pour lister les constantes d'une
enum class
.- Chaque constante d'une énumération a des propriétés pour obtenir son nom et sa position (en commençant par 0).
▶️ ce code illustres les enum en Kotlin de façon succincte. Veuillez consulter la documentation officielle pour aller plus loin.
Exercices
Exercice 1
Veuillez cliquer sur le lien pour consulter l'énoncé
Déplier pour consulter la solution
Exercise 2
Veuillez cliquer sur le lien pour consulter l'énoncé
Déplier pour consulter la solution
Fonctionnalités intermédiaires
Programmation orientée objet
Kotlin permet d'écrire du code Orienté Object concis grâce aux caractéristiques suivantes :
- Concepts disponibles : classes, héritage, interfaces et classes abstraites.
- Prise en charge possée des propriétés : les getters et les setters sont automatiquement implémentés.
- On peut les personnaliser les accesseurs en définissant les fonctions
get()
etset(value)
à côté de la déclaration de la propriété.- Les arguments du constructeur sont définis à côté du nom de la classe
class ClassName(arg1, atg2, )
- Préfixer les arguments d'un constructeur avec
val
ouvar
en fait une propriété (val
la rend non ré-assignable).- Le nom du constructeur est
init
et ne nécessite pas de paramètres.- Le compilateur vérifie que toutes les propriétés non nullables sont initialisées à la fin du constructeur.
- ⚠️ Le compilateur ne vérifie pas l'initialisation des propriétés
lateinit
. Ainsi, y accéder avant alors qu'elles ne sont pas initialisés provoque une exception.- Une classe doit être préfixée avec
open
pour permettre l'héritage.- Kotlin utilise le niveau d'accès
public
par défaut.- L'opérateur d'égalité
==
appelle implicitement la méthodeequals()
(contrairement à Java qui utilise l'égalité de référence).- Un objet compagnon contient des méthodes et des propriétés statiques.
- Les extensions ajoutent des fonctions et des propriétés aux classes existantes.
- 💡 Ils remplacent l'héritage dans de nombreuses situations.
- Par exemple, nous pouvons ajouter des fonctions à la classe String au lieu de créer une nouvelle classe
StringUtils
.- Les classes et interfaces scellées ne peuvent pas être étendues ou implémentées par des tiers.
Ne définir les accesseurs que si vous avez un comportement personnalisé
Kotlin prend en charge les propriétés de façon plus poussée que Java et permet d'ajouter des accesseurs ultérieurement sans refactoriser le code qui appelle ces propriétés. Ainsi, par défaut, il suffit de définir le nom des propriétés sans accesseurs et on peut les utiliser directement.
▶️ ce code illustre la POO en Kotlin.
Data class
Ce sont des classes qui implémentent des méthodes communes:
equals()
,hashCode()
,copy()
ettoString()
componentN()
qui est une syntaxe alternative pour récupérer les propriétés.Cependant, les data class ont des restrictions:
- Le constructeur principal doit avoir au moins un paramètre.
- Tous les paramètres du constructeur principal doivent être marqués comme
val
ouvar
.- Une Data class ne peut pas être abstraite, ouverte à l'héritage, scellée ou interne (💡 mais des extensions sont possibles).
▶️ ce code illustrate les data class.
Programmation fonctionnelle
Concepts généraux
La programmation fonctionnelle s'articule autour de ces concepts : fonctions pures, récursivité, transparence référentielle, variables immuables, fonctions en tant que citoyens de première classe et fonctions d'ordre supérieur.
Expliquons brièvement ces concepts :
- Les variables immuables signifient qu'on ne peut pas changer la valeur d'une variable ou ses propriétés une fois qu'elle a été créée. Si nous voulons le faire, nous devons créer une nouvelle instance avec la nouvelle valeur.
- Les fonctions pures sont des fonctions qui n'ont pas d'effets secondaires et renverront donc toujours la même sortie étant donné la même entrée.
- Les fonctions sont des citoyennes de première classe : elles peuvent être affectées à une variable ou utilisées dans des fonctions d'ordre supérieur (passées en tant qu'un argument de fonction ou retournées dans un fonction).
- Transparence référentielle : signifie qu'une expression peut être remplacée par son résultat sans modifier le comportement du programme.
💡 Les langages fonctionnels purs fournissent ces fonctionnalités de manière native et les appliquent (au moment de la construction).
Kotlin et programmation fonctionnelle
Kotlin n'est pas un langage fonctionnel pur mais il prend en charge certaines fonctionnalités. En effet, Kotlin ne sait pas dire si une fonction est pures ou non, mais il fournit des collections immuables via la bibliothèque kotlinx.collections.immutable pour nous aider à manipuler des données immuables.
\`listOf\` génère des listes en lecture seule, mais qui sont mutables
Une liste en lecture seule ne peut pas ajouter ou supprimer des éléments, mais elle peut modifier les données sous-jacentes.
@Test +fun givenReadOnlyList_whenCastToMutableList_checkNewElementsAdded(){ + val list: List<String> = listOf("This", "Is", "Totally", "Immutable") + (list as MutableList<String>)[2] = "Not" + assertEquals(listOf("This", "Is", "Not", "Immutable"), list) +} +
La librairie Arrow-kt permet d'aller encore plus loin en développement fonctionnel.
Programmation déclarative
La programmation déclarative est un style célèbre dans la programmation fonctionnelle. Il consiste à écrire du code sous la forme d'un enchaînement d'appels de fonction dans ce style
val result = f(x).g(y). ...
. Les fonctions d'ordre supérieur remplacent de nombreuses situations où nous utiliserions des boucles. Cela favorise le code lisible qui est facile à déboguer et à maintenir.▶️ ce code montre comment manipuler une liste avec la programmation déclarative.
Kotlin and Java interoperability
- Kotlin is designed with Java interoperability in mind.
- Kotlin code may require some annotations to be called from Java.
- It is possible to mix Java and Kotlin in the same project.
- JetBrain's IntelliJ and Android Studio can convert to Kotlin when pasting java code.
- Kotlin generates Java records by annotating a data class with
@JvmRecord
and targeting JVM 16, among other requirement listed here.- It is much more easier and natural to call Java from Kotlin.
- For example: Java accessors are converted to Kotlin properties.
▶️ this code shows how to convert a Kotlin
List
to a JavaArrayList
.The official documentation provides exhaustive documentation on Kotlin and JVM integration
Exercices
Exercice 3
Solution(s)
Exercice 4
Solutions(s)
Exercice 5
Solution(s)
Exercice 6
solution(s)
Exercice 7
Solution(s)
Plus d'exercices et de lecture
`,77),i=[c];function n(b,s){return I(),e("div",null,i)}const u=l(g,[["render",n],["__file","index.html.vue"]]),G=JSON.parse(`{"path":"/fr/kotlin-features/","title":"📚 Fonctionnalités du langage Kotlin","lang":"fr-FR","frontmatter":{},"headers":[{"level":2,"title":"Caractéristiques de base","slug":"caracteristiques-de-base","link":"#caracteristiques-de-base","children":[{"level":3,"title":"Constructions de base (variables, flux de contrôle)","slug":"constructions-de-base-variables-flux-de-controle","link":"#constructions-de-base-variables-flux-de-controle","children":[]},{"level":3,"title":"Les fonctions","slug":"les-fonctions","link":"#les-fonctions","children":[]},{"level":3,"title":"Null safety","slug":"null-safety","link":"#null-safety","children":[]},{"level":3,"title":"Énumérations","slug":"enumerations","link":"#enumerations","children":[]},{"level":3,"title":"Exercices","slug":"exercices","link":"#exercices","children":[]}]},{"level":2,"title":"Fonctionnalités intermédiaires","slug":"fonctionnalites-intermediaires","link":"#fonctionnalites-intermediaires","children":[{"level":3,"title":"Programmation orientée objet","slug":"programmation-orientee-objet","link":"#programmation-orientee-objet","children":[]},{"level":3,"title":"Data class","slug":"data-class","link":"#data-class","children":[]},{"level":3,"title":"Programmation fonctionnelle","slug":"programmation-fonctionnelle","link":"#programmation-fonctionnelle","children":[]},{"level":3,"title":"Kotlin and Java interoperability","slug":"kotlin-and-java-interoperability","link":"#kotlin-and-java-interoperability","children":[]},{"level":3,"title":"Exercices","slug":"exercices-1","link":"#exercices-1","children":[]}]},{"level":2,"title":"Plus d'exercices et de lecture","slug":"plus-d-exercices-et-de-lecture","link":"#plus-d-exercices-et-de-lecture","children":[]}],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"fr/kotlin-features/README.md"}`);export{u as comp,G as data}; diff --git a/assets/index.html-DOWQbO5d.js b/assets/index.html-DOWQbO5d.js new file mode 100644 index 00000000..cb83cc94 --- /dev/null +++ b/assets/index.html-DOWQbO5d.js @@ -0,0 +1 @@ +import{_ as a,a as r,b as d,c as s}from"./qrcode-mixtit24-D-YZ7Myr.js";import{_ as p,r as u,c,d as e,b as t,w as o,e as n,a as i,o as m}from"./app-DAFlGqDu.js";const h={},f=i('📅 Workshops
(2023) Android makers : Kotlin Beyond Android
Link
Workshop link: awl.li/am23-kt
Agenda
',7),k=e("li",null,[e("a",{href:"https://worldline.github.io/learning-kotlin-multiplatform/",target:"_blank",rel:"noopener noreferrer"},"Front-end development: Cross-platform Quiz App with Compose multiplatform")],-1),g=i('(2023) JNation : Let's discover the possibilities of Kotlin
Link
Workshop link: awl.li/jnation23-kt
Agenda
',5),v=e("li",null,[e("a",{href:"https://worldline.github.io/learning-kotlin-multiplatform/",target:"_blank",rel:"noopener noreferrer"},"Front-end development: Cross-platform Quiz App with Compose multiplatform")],-1),b=i('(2023) Mobile DevOps summit
- Title: From Android to Multiplatform: leveraging the full potential of Kotlin
- Link: awl.li/mds23-kt
Agenda
',4),_=i('(2024) MiXit
- Titre : Développement front et back en Kotlin. Une visite guidée de KMP
Agenda
',4),A=e("strong",null,"Développement backend",-1),K=e("li",null,[e("a",{href:"https://speakerdeck.com/yostane/kotlin-pour-le-developpement-backend",target:"_blank",rel:"noopener noreferrer"},"Présentation d'introduction à Kotlin pour le développement backend")],-1),w=e("strong",null,"Développement frontend",-1),x=e("strong",null,"Développement fullstack",-1),q=e("strong",null,"Autres fonctionnalités",-1);function y(W,j){const l=u("RouteLink");return m(),c("div",null,[f,e("ul",null,[e("li",null,[t(l,{to:"/en/backend-development/#ktor"},{default:o(()=>[n("Backend development: Rest API with Ktor")]),_:1})]),e("li",null,[n("Optional: "),t(l,{to:"/en/backend-development/#ktor"},{default:o(()=>[n("Backend development: Rest API with node.js and Kotlin/JS")]),_:1})]),e("li",null,[t(l,{to:"/en/front-development/#kotlin-js-and-kotlin-wasm"},{default:o(()=>[n("Front-end development: Kotlin/WASM and Kotlin/JS webapp")]),_:1})]),e("li",null,[t(l,{to:"/en/front-development/#compose"},{default:o(()=>[n("Front-end development: cross-platform Hello world App with Compose multiplatform")]),_:1})]),k,e("li",null,[t(l,{to:"/en/other-technologies/#pw-add-a-ktor-server-app"},{default:o(()=>[n("Full-stack development: Quiz App with Compose multiplatform and Ktor server")]),_:1})])]),g,e("ul",null,[e("li",null,[t(l,{to:"/en/presentation/#prerequisites"},{default:o(()=>[n("Prerequisutes")]),_:1})]),e("li",null,[n("Selection of Kotlin's languages features: "),t(l,{to:"/en/kotlin-features/#null-safety"},{default:o(()=>[n("null safety")]),_:1}),n(" and "),t(l,{to:"/en/kotlin-features/#functions"},{default:o(()=>[n("functions")]),_:1})]),e("li",null,[t(l,{to:"/en/backend-development/#ktor"},{default:o(()=>[n("Backend development: Rest API with Ktor")]),_:1})]),e("li",null,[n("Optional: "),t(l,{to:"/en/backend-development/#ktor"},{default:o(()=>[n("Backend development: Rest API with node.js and Kotlin/JS")]),_:1})]),e("li",null,[t(l,{to:"/en/front-development/#kotlin-js-and-kotlin-wasm"},{default:o(()=>[n("Front-end development: Kotlin/WASM and Kotlin/JS webapp")]),_:1})]),e("li",null,[t(l,{to:"/en/front-development/#compose"},{default:o(()=>[n("Front-end development: cross-platform Hello world App with Compose multiplatform")]),_:1})]),v,e("li",null,[t(l,{to:"/en/other-technologies/#pw-add-a-ktor-server-app"},{default:o(()=>[n("Full-stack development: Quiz App with Compose multiplatform and Ktor server")]),_:1})])]),b,e("ul",null,[e("li",null,[t(l,{to:"/en/other-technologies/#pw-add-a-ktor-server-app"},{default:o(()=>[n("Initiate a cross-platform app")]),_:1})]),e("li",null,[n("Live examples of other Kotlin possibilities: "),e("ul",null,[e("li",null,[t(l,{to:"/en/backend-development/#ktor"},{default:o(()=>[n("Backend development: Rest API with Ktor")]),_:1})]),e("li",null,[t(l,{to:"/en/backend-development/#ktor"},{default:o(()=>[n("Backend development: Rest API with node.js and Kotlin/JS")]),_:1})]),e("li",null,[t(l,{to:"/en/front-development/#kotlin-js-and-kotlin-wasm"},{default:o(()=>[n("Front-end development: Kotlin/WASM and Kotlin/JS webapp")]),_:1})])])])]),_,e("ul",null,[e("li",null,[t(l,{to:"/en/presentation/#prerequisites"},{default:o(()=>[n("Prérequis")]),_:1})]),e("li",null,[n("Fonctionnalités notables: "),t(l,{to:"/en/kotlin-features/#null-safety"},{default:o(()=>[n("null safety")]),_:1}),n(" et "),t(l,{to:"/en/kotlin-features/#functions"},{default:o(()=>[n("les fonctions")]),_:1})]),e("li",null,[A,e("ul",null,[K,e("li",null,[t(l,{to:"/en/backend-development/#spring-framework"},{default:o(()=>[n("API Rest avec Spring boot")]),_:1})]),e("li",null,[t(l,{to:"/en/backend-development/#ktor"},{default:o(()=>[n("API Rest avec Ktor")]),_:1})])])]),e("li",null,[w,e("ul",null,[e("li",null,[t(l,{to:"/en/front-development/#compose"},{default:o(()=>[n('Application "Hello World" avec Compose Multiplatform')]),_:1})])])]),e("li",null,[x,e("ul",null,[e("li",null,[t(l,{to:"/en/other-technologies/#pw-add-a-ktor-server-app"},{default:o(()=>[n("Application de quiz avec Ktor + Compose Multiplatform")]),_:1})])])]),e("li",null,[q,e("ul",null,[e("li",null,[t(l,{to:"/en/front-development/#kotlin-js-and-kotlin-wasm"},{default:o(()=>[n("Kotlin/WASM")]),_:1})]),e("li",null,[t(l,{to:"/en/backend-development/#node.js"},{default:o(()=>[n("Développement node.js en Kotlin")]),_:1})]),e("li",null,[t(l,{to:"/en/kotlin-features-advanced/#concurrency-and-coroutines"},{default:o(()=>[n("Coroutines")]),_:1})])])])])])}const C=p(h,[["render",y],["__file","index.html.vue"]]),M=JSON.parse(`{"path":"/en/workshops/","title":"📅 Workshops","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"(2023) Android makers : Kotlin Beyond Android","slug":"_2023-android-makers-kotlin-beyond-android","link":"#_2023-android-makers-kotlin-beyond-android","children":[{"level":3,"title":"Link","slug":"link","link":"#link","children":[]},{"level":3,"title":"Agenda","slug":"agenda","link":"#agenda","children":[]}]},{"level":2,"title":"(2023) JNation : Let's discover the possibilities of Kotlin","slug":"_2023-jnation-let-s-discover-the-possibilities-of-kotlin","link":"#_2023-jnation-let-s-discover-the-possibilities-of-kotlin","children":[{"level":3,"title":"Link","slug":"link-1","link":"#link-1","children":[]},{"level":3,"title":"Agenda","slug":"agenda-1","link":"#agenda-1","children":[]}]},{"level":2,"title":"(2023) Mobile DevOps summit","slug":"_2023-mobile-devops-summit","link":"#_2023-mobile-devops-summit","children":[{"level":3,"title":"Agenda","slug":"agenda-2","link":"#agenda-2","children":[]}]},{"level":2,"title":"(2024) MiXit","slug":"_2024-mixit","link":"#_2024-mixit","children":[{"level":3,"title":"Agenda","slug":"agenda-3","link":"#agenda-3","children":[]}]}],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"en/workshops/README.md"}`);export{C as comp,M as data}; diff --git a/assets/index.html-DPrxNVl-.js b/assets/index.html-DPrxNVl-.js new file mode 100644 index 00000000..a42d8bcc --- /dev/null +++ b/assets/index.html-DPrxNVl-.js @@ -0,0 +1,272 @@ +import{_ as i,r as p,c,b as a,w as e,a as o,o as r,d as n,e as s}from"./app-DAFlGqDu.js";const u={},d=o(`📚 Backend development
Many frameworks officially support Kotlin: Spring, Quarkus, Ktor, among others listed here.
In addition to that, Kotlin is theoretically compatible with any framework that targets the JVM or JS. For example, this tutorial shows how to use node.js with Kotlin. However, frameworks that do not officially support Kotlin may require some tweaking to use it.
Ktor
Ktor is a cross-platform Kotlin library for building both HTTP clients and servers. This makes Ktor a useful library to learn for both front-end developers for its HTTP client capabilities and backend-development for its HTTP server capabilities. In the following, we'll create a REST API with Ktor server.
🧪 develop an API with Ktor
- Create a project on start.ktor.io with the following plugins: Content Negotiation, kotlinx.serialization, and Routing.
- Click on "Generate project".
- Download the archive, unzip it, and open the project with IntelliJ.
- Create a
models
package and add to it aCustomer
data class with these immutable propertiesid: String, firstName: String, lastName: String, email: String
.- Annotate the class with
@Serializable
.- Create a new package named
routes
and add to it a fileCustomerRoutes.kt
that will contain the code for the/customer
endpoint.- The code below provides the implementation of some endpoints. Please implement the remaining ones.
- To enable the route call
customerRouting()
in the routing configuration file located inplugins/Routing.kt
.- For simplicity, use a global in-memory list of customers
val store = mutableListOf<Customer>()
.- Run the server by running the main method.
- Test the api on the IDE by using an http file or using any other client.
CustomerRoutes.kt
val store = mutableListOf<Customer>() + +fun Route.customerRouting() { + route("/customer") { + get { + call.respond(store) + } + get("{id?}") { + val id = call.parameters["id"] ?: return@get call.respondText( + "Missing id", + status = HttpStatusCode.BadRequest + ) + val customer = + store.find { it.id == id } ?: return@get call.respondText( + "No customer with id $id", + status = HttpStatusCode.NotFound + ) + call.respond(customer) + } + post { + val customer = call.receive<Customer>() + store.add(customer) + call.respondText("Customer stored correctly", status = HttpStatusCode.Created) + } + delete("{id?}") { + + } + } +} +
plugins/Routing.kt
fun Application.configureRouting() { + routing { + customerRouting() + } +} +
return@label
You can specify which level you want to return with an explicit label using
return@lambda
.lambdaA { + lambdaB { + lambdaC { + val randomInt = Random.nextInt(0, 100) + if (randomInt > 50) return@lambdaC else return@lambdaB + } + printf("In lambdaB") + } +} +
CustomerTest.http
POST http://127.0.0.1:8080/customer +Content-Type: application/json + +{ + "id": "100", + "firstName": "Jane", + "lastName": "Smith", + "email": "jane.smith@company.com" +} + + +### +POST http://127.0.0.1:8080/customer +Content-Type: application/json + +{ + "id": "200", + "firstName": "John", + "lastName": "Smith", + "email": "john.smith@company.com" +} + +### +POST http://127.0.0.1:8080/customer +Content-Type: application/json + +{ + "id": "300", + "firstName": "Mary", + "lastName": "Smith", + "email": "mary.smith@company.com" +} + + +### +GET http://127.0.0.1:8080/customer +Accept: application/json + +### +GET http://127.0.0.1:8080/customer/200 +Accept: application/json + +### +GET http://127.0.0.1:8080/customer/500 +Accept: application/json + +### +DELETE http://127.0.0.1:8080/customer/100 + +### +DELETE http://127.0.0.1:8080/customer/500 +
Spring framework
Spring is a famous framework for developing server-side applications: APIs, server generated web pages, microservices, etc. It relies on the the Java ecosystem to build and run, thus making it compatible with Kotlin. Even better, Spring officially supports Kotlin. It even allows in start a new project with Kotlin and Gradle-Kotlin. In the next section, we'll use this starter to recreate our above REST API with Spring.
🧪 Spring boot part 1 - develop the same API with Spring Boot
- Create a project on start.spring.io (also called Spring initializr) with the following dependencies: Spring Web and Spring Boot DevTools.
- Choose Kotlin as the language and Kotlin-Grade as the project manager.
- Add these dependencies: Spring Web, Spring Boot DevTools, h2 database and Spring Data JPA.
- Click on "Generate". Download the archive, unzip it, and open the project with IntelliJ (preferably) or VSCode.
- For VSCode, install a Kotlin extension and Spring Boot Extension Pack ( ⚠️ Spring extension do not seem to support kotlin).
- Create
Customer
data class in themodel
package without the@Serializable
annotation.- Create a
controller
package that contains aCustomerController
class which provides a CRUD using a global list. You can find a skeleton below.
- 💡 In Spring, Rest controllers serve the purpose of Ktor routes, where a controller defines a REST resource.
- Define the same endpoints as in the previous PW.
- Start the REST API server by running
.\\gradlew bootRun
or from your IDE.- Please test the endpoints with a REST client. You can find http files here in JetBrains format or VSCode's REST Client extension
CustomerController.kt
val store = mutableListOf<Customer>() + +@RestController +@RequestMapping("/customer") +class CustomerController { + @GetMapping + fun getAll() = store + + @GetMapping("{id}") + fun getById(@PathVariable id: String) { /* TODO: implement */ } + + @PostMapping + fun addOne(@RequestBody customer: Customer) { /* TODO: implement */ } + + @DeleteMapping("{id}") + fun deleteOne(@PathVariable id: String) { /* TODO: implement */ } +} +
Models or model package ? plural or not ?
Both are ok as long as you follow the same convention in the project.
🧪 Spring boot part 2 - adding a database
Let's go a little bit further by storing data in a database and writing some tests.
We'll use the H2 in-memory database for the sake of simplicity, since it does not require a server to run. Classes will mapped to database tables with JPA annotations. The database API we'll be using is called
JPARepository
. It is a lightweight API that provides common CRUD features by just defining an interface.On the testing side, we'll see two different syntaxes. The default one that is more familiar with Java style and the DSL one which is more readable and more familiar with Kotlin developers.
- Create a new Spring project using Spring initializr with Kotlin and the following dependencies: Spring Data JPA, H2 Database, Spring Boot DevTools, Spring Web
- Open the project and add this class in the
model
package@Entity class Product(@Id @GeneratedValue var id: Long? = null, var name: String, var price: Int)
. This single defines the class as well as the minimal JPA annotations (@Entity
,@Id
and@GeneratedValue
) to generate the corresponding table.- In the
repository
package, declare theProductRepository
interface as followsinterface ProductRepository: JpaRepository<Product, Long>
. This is enough for Spring to generate an implementation with common features as we'll see later.- Next, create a
ProductService
class which will contain the business logic. In terms of architecture, the controller calls a service which in turn rely on other services or repositories.ProductService.kt
@Service +class ProductService(@Autowired val productRepository: ProductRepository) { + fun getAll() = productRepository.findAll() + + // use findByIdOrNull instad of findById because the latter returns an optional<Product> instead of Product? + fun getById(id: Long) = productRepository.findByIdOrNull(id) +} +
- In the controller package, create a
ProductController
class that is mapped to/product
and injects the with@Autowired
. Reply to@Get
as follows.ProductController.kt
@RestController +@RequestMapping("/product") +class ProductController(@Autowired val productService: ProductService) { + @GetMapping fun getAll() = productService.getAll() + + @GetMapping("{id}") + fun getById(@PathVariable id: Long) = + productService.getById(id) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) +} +
Kotlin makes getById(@PathVariable id: Long) more concise
The Elvis operator
?:
allows to simplify the code. Here is a longer version as reference.@GetMapping("{id}") +fun getById(@PathVariable id: Long): Product { + val product = productService.getById(id) + if (product != null){ + return product + } + throw ResponseStatusException(HttpStatus.NOT_FOUND) +} +
In addition to that, Spring provides
@ControllerAdvice
to change the exception message. You can see an example here.
- Let's run the project. Before running the project, we need to add a plugin that allows Kotlin classes to generate a default constructor
id("org.jetbrains.kotlin.plugin.jpa") version "1.8.10"
. The plugins should look as follows:plugins { + id("org.jetbrains.kotlin.plugin.jpa") version "1.8.10" + id("org.springframework.boot") version "3.0.4" + id("io.spring.dependency-management") version "1.1.0" + kotlin("jvm") version "1.8.10" + kotlin("plugin.spring") version "1.8.10" +} +
- As an exercise, implement these endpoints: POST a single product, DELETE by id (
/product/{id}
) and GET by id (/product/{id}
).
- Hint:
ProductController
already provides the necessary methods.- Call the different endpoints with a REST client.
🧪 Spring boot part 2 - adding tests
Spring frameworks helps perform different types of tests by providing different classes out of the box:
- Unit testing of services, repositories and the REST API. This is done through mock utilities such as
MockMVC
.- Integration testing of the REST API using
TestRestTemplate
. In this situation, a full server is run and tested.Most, if not all classes provided by Spring provide an elegant syntax for Java developers. Some of them go further by taking advantage of Kotlin specific features. In the following, we're going to focus on parts that provide Kotlin DSLs, namely unit testing the REST API with
MockMVC
.
- Create a test class
ProductControllerUnitTests
with this initial content.MockMvc
allows to unit test the REST API. The@AutoConfigureMockMvc
annotation allows spring to configure it automatically@SpringBootTest +@AutoConfigureMockMvc +class ProductControllerTests( + @Autowired val mockMvc: MockMvc, + @Autowired val productRepository: ProductRepository) { + + @BeforeEach + fun reset(){ + productRepository.deleteAll() + } +} +
`,37),k=n("div",{class:"language-kotlin line-numbers-mode","data-highlighter":"prismjs","data-ext":"kt","data-title":"kt"},[n("pre",null,[n("code",null,[n("span",{class:"line"},[n("span",{class:"token annotation builtin"},"@Test")]),s(` +`),n("span",{class:"line"},[n("span",{class:"token keyword"},"fun"),s(),n("span",{class:"token function"},"testWithClassicApproach"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"{")]),s(` +`),n("span",{class:"line"},[s(" mockMvc"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"perform"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"get"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string-literal singleline"},[n("span",{class:"token string"},'"/product"')]),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")")]),s(` +`),n("span",{class:"line"},[s(" "),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"andExpect"),n("span",{class:"token punctuation"},"("),n("span",{class:"token function"},"status"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"."),s("isOk"),n("span",{class:"token punctuation"},")")]),s(` +`),n("span",{class:"line"},[s(" "),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"andExpect"),n("span",{class:"token punctuation"},"("),n("span",{class:"token function"},"content"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"string"),n("span",{class:"token punctuation"},"("),n("span",{class:"token function"},"containsString"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string-literal singleline"},[n("span",{class:"token string"},'"[]"')]),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")")]),s(` +`),n("span",{class:"line"},[n("span",{class:"token punctuation"},"}")]),s(` +`),n("span",{class:"line"})])]),n("div",{class:"line-numbers","aria-hidden":"true",style:{"counter-reset":"line-number 0"}},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),m=n("div",{class:"language-kotlin line-numbers-mode","data-highlighter":"prismjs","data-ext":"kt","data-title":"kt"},[n("pre",null,[n("code",null,[n("span",{class:"line"},[n("span",{class:"token annotation builtin"},"@Test")]),s(` +`),n("span",{class:"line"},[n("span",{class:"token keyword"},"fun"),s(),n("span",{class:"token function"},"`test GET a single product`"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{")]),s(` +`),n("span",{class:"line"},[s(" mockMvc"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"get"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string-literal singleline"},[n("span",{class:"token string"},'"/product/1"')]),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"andExpect"),s(),n("span",{class:"token punctuation"},"{")]),s(` +`),n("span",{class:"line"},[s(" status "),n("span",{class:"token punctuation"},"{"),s(),n("span",{class:"token function"},"isOk"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"}")]),s(` +`),n("span",{class:"line"},[s(" "),n("span",{class:"token function"},"jsonPath"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string-literal singleline"},[n("span",{class:"token string"},'"$.name"')]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(),n("span",{class:"token function"},"value"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string-literal singleline"},[n("span",{class:"token string"},'"A"')]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"}")]),s(` +`),n("span",{class:"line"},[s(" "),n("span",{class:"token function"},"jsonPath"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string-literal singleline"},[n("span",{class:"token string"},'"$.price"')]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(),n("span",{class:"token function"},"value"),n("span",{class:"token punctuation"},"("),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"}")]),s(` +`),n("span",{class:"line"},[s(" content "),n("span",{class:"token punctuation"},"{"),s(),n("span",{class:"token function"},"contentType"),n("span",{class:"token punctuation"},"("),s("MediaType"),n("span",{class:"token punctuation"},"."),s("APPLICATION_JSON"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"}")]),s(` +`),n("span",{class:"line"},[s(" "),n("span",{class:"token punctuation"},"}")]),s(` +`),n("span",{class:"line"},[n("span",{class:"token punctuation"},"}")]),s(` +`),n("span",{class:"line"})])]),n("div",{class:"line-numbers","aria-hidden":"true",style:{"counter-reset":"line-number 0"}},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),v=o(`
- Add these two tests. The first one uses a classic approach while the second take advantage of Kotlin DSL capabilities. In addition to that, we name using a more readable string literal
- As an exercise, unit tests for the other endpoints.
The request builder of JpaRepository
Spring repositories implement requests based on the name of their methods. For example, to get all products sorted by name, we can add this method to the interface.
interface ProductRepository: JpaRepository<Product, Long> { + fun findAllByOrderByNameAsc(): List<Product>; +} +
The official documentation provides more detailed explanations and examples.
node.js
Thanks to Kotlin/JS, we can write apps that target node.js using Kotlin. We can even import npm libraries as long as you declare the JS API surface that you'll be using in Kotlin. This is called external declaration (You can think of it as an equivalent of TypeScript's type definitions) that declares the symbols that we want to access in Kotlin thanks to @JsModule and @JsNonModule annotations.
Defining such external declarations can be a hassle and there seems to be no official automatic generator (dukat has been removed in kotlin 1.8.20). In that case, we have two options, either we write the external declaration ourselves or import it as a dependency if available. Fortunately for express developers, chrisnkrueger/kotlin-express provides declarations for the express library.
There are two gradle plugins that allow to create node.js projects: the
kotlin("js")
one and thekotlin("multiplatform")
one. The difference between the two plugins is that the former only supports JS or WASM while the latter supports more platforms but requires to configure source sets. Thus, the former may seem easier to setup but the latter is better in the long run because it allows us to get more familiar with Kotlin Multiplatform (KMP).🧪 Getting started with Kotlin/JS and Express
At the time of writing, I didn't find an official wizard or starter project. So we'll create one from scratch using
gradle init
.
- Create a new Gradle project using IntelliJ or by running
gradle init
in a empty folder (see below for the replies to thegradle init
command).gradle init
gradle init +Starting a Gradle Daemon, 1 incompatible and 1 stopped Daemons could not be reused, use --status for details + +Select type of project to generate: + 1: basic + 2: application + 3: library + 4: Gradle plugin +Enter selection (default: basic) [1..4] 1 + +Select build script DSL: + 1: Kotlin + 2: Groovy +Enter selection (default: Kotlin) [1..2] 1 + +Project name (default: starter): rest-api-kotlin-nodejs + +Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] yes + + +> Task :init +To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.3/samples + +BUILD SUCCESSFUL in 24s +2 actionable tasks: 2 executed +
- In build.gradle.kts, add and configure the
kotlin("multiplatform")
plugin. Also add theexpress
anddev.chriskrueger:kotlin-express
dependencies.build.gradle.kts
plugins { + kotlin("multiplatform") version "1.9.20-Beta" +} + +repositories { + mavenCentral() +} + +group = "tech.worldline.demo" +version = "1.0-SNAPSHOT" + +kotlin { + js { + nodejs { + } + binaries.executable() + useCommonJs() + } + + sourceSets { + val jsMain by getting { + dependencies { + implementation(npm("express", "> 4.0.0 < 5.0.0")) + implementation("dev.chriskrueger:kotlin-express:1.2.0") + } + } + } +} +
Some notes on the build file
- express dependency is retrieved from npm
useCommonJs()
is required to be able to use chrisnkrueger/kotlin-express in our code.
- create a main.kt file in src/jsMain/kotlin with the following content:
main.kt
data class Message(val id: Int, val message: String) + +val messages = mutableListOf(Message(0, "I love Kotlin/JS")) + +fun main() { + val app = express.Express() + + // REST API that provides a **GET /hello** endpoint + app.get("/hello") { _, res -> + res.send(messages) + } + + // Create a server that listens to port 3000 + app.listen(3000) { + console.log("server start at port 3000") + } +} +
- Run the task
jsRun
from IntelliJ of from the command line./gradlew --console=plain jsRun
. The server should start running.- Open the hello endpoint on http://localhost:3000/hello
Execution failed for task ':kotlinStoreYarnLock'
If you get this error:
Execution failed for task ':kotlinStoreYarnLock'. +> yarn.lock was changed. Run the \`kotlinUpgradeYarnLock\` task to actualize yarn.lock file +
Run
./gradlew kotlinUpgradeYarnLock
so that yarn.lock is updated🧪 Adding a post endpoint and an external Kotlin/JS definition
Let's add a post endpoint which reads the body as a json. In order to read the body as json, we must add this possibility to express by importing the npm library body-parser and by calling
app.use(bodyParser.json())
. Once this setup is complete,req.body
will contain the content of the body. However, there is no available external definition for bodyParser as of the time of writing. Thus, we must create or own external definition.
- First, add the body-parser dependncy in the build file
implementation(npm("body-parser", "> 1.0.0 < 2.0.0"))
- Next, we would write:
app.use(bodyparser.json())
to activate the library. Let's guess what a minimal definition ofbodyparser
can be.BodyParser.kt
// external means that this class is defined in JS +external class BodyParser { + // we tell Kotlin that we want to use the json() function. + fun json(): Any + // It is not required to define all the functions of the module +} + +// @JsModule is used to import the module from the NPM registry +@JsModule("body-parser") +external val bodyParser: BodyParser +
- Finally, we just need to add the BodyParser.kt file into the project and use it in our server.
main.kt
app.use(bodyParser.json()) +app.post("/hello") { req, res -> + // Kotlin does not keep the original field name when parsing JSON from JS (you can see it the in get response) + if (req.body as? Message == null) { + println("failed to get the body from Kotlin") + } + // Thus, we need to use js() to get the the field by its expected name + // js() calls JS from Kotlin + println("req.body from JS \${js("req.body.id")} - \${js("req.body.message")}") + val id = js("req.body.id") as? Int + val message = js("req.body.message") as? String + if (message != null && id != null) { + messages.add(Message(id, message)) + res.status(201).end() + } else { + res.status(400).send(js("{cause : 'error'}") as Any) + } +} +
🧪 Adding more endpoints
- Add PUT and DELETE endpoints
🎯 Solutions
- ktor Rest API
- Spring boot Rest API
- The starter and final node.js projects are available here
📖 Further readings
These official tutorials go even further:
- This tutorial from kotlinlang shows how to create a RESTful web service with a database using Spring Boot.
- This one from spring.io show how to build a web application with Spring Boot and Kotlin.
- Rest ÄPIs with Spring
- Quarkus and kotlin
References
`,32);function g(b,h){const t=p("CodeGroupItem"),l=p("CodeGroup");return r(),c("div",null,[d,a(l,null,{default:e(()=>[a(t,{title:"Without DSL (Test Get All)"},{default:e(()=>[k]),_:1}),a(t,{title:"With DSL (Test Get Single)"},{default:e(()=>[m]),_:1})]),_:1}),v])}const y=i(u,[["render",g],["__file","index.html.vue"]]),w=JSON.parse('{"path":"/en/backend-development/","title":"📚 Backend development","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Ktor","slug":"ktor","link":"#ktor","children":[{"level":3,"title":"🧪 develop an API with Ktor","slug":"🧪-develop-an-api-with-ktor","link":"#🧪-develop-an-api-with-ktor","children":[]}]},{"level":2,"title":"Spring framework","slug":"spring-framework","link":"#spring-framework","children":[{"level":3,"title":"🧪 Spring boot part 1 - develop the same API with Spring Boot","slug":"🧪-spring-boot-part-1-develop-the-same-api-with-spring-boot","link":"#🧪-spring-boot-part-1-develop-the-same-api-with-spring-boot","children":[]},{"level":3,"title":"🧪 Spring boot part 2 - adding a database","slug":"🧪-spring-boot-part-2-adding-a-database","link":"#🧪-spring-boot-part-2-adding-a-database","children":[]},{"level":3,"title":"🧪 Spring boot part 2 - adding tests","slug":"🧪-spring-boot-part-2-adding-tests","link":"#🧪-spring-boot-part-2-adding-tests","children":[]}]},{"level":2,"title":"node.js","slug":"node-js","link":"#node-js","children":[{"level":3,"title":"🧪 Getting started with Kotlin/JS and Express","slug":"🧪-getting-started-with-kotlin-js-and-express","link":"#🧪-getting-started-with-kotlin-js-and-express","children":[]},{"level":3,"title":"🧪 Adding a post endpoint and an external Kotlin/JS definition","slug":"🧪-adding-a-post-endpoint-and-an-external-kotlin-js-definition","link":"#🧪-adding-a-post-endpoint-and-an-external-kotlin-js-definition","children":[]},{"level":3,"title":"🧪 Adding more endpoints","slug":"🧪-adding-more-endpoints","link":"#🧪-adding-more-endpoints","children":[]}]},{"level":2,"title":"🎯 Solutions","slug":"🎯-solutions","link":"#🎯-solutions","children":[]},{"level":2,"title":"📖 Further readings","slug":"📖-further-readings","link":"#📖-further-readings","children":[]}],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"en/backend-development/README.md"}');export{y as comp,w as data}; diff --git a/assets/index.html-DfLOFdKV.js b/assets/index.html-DfLOFdKV.js new file mode 100644 index 00000000..b159aea6 --- /dev/null +++ b/assets/index.html-DfLOFdKV.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as i,a as n}from"./app-DAFlGqDu.js";const r={},a=n('
- JS in Kotlin/JS
- mockmvc kotlin dsl
- spring-boot-kotlin tutorial
- Working with Kotlin and JPA
- Spring Data JPA How to use Kotlin nulls instead of Optional
Prerequisites
- Connaissance de base en language de programmation orienté object comme Java
- Préparez votre environnement de développement et installez des éléments avant la session (voir la section Outillage)
Liens utiles
',5),l=[a];function s(o,c){return i(),t("div",null,l)}const d=e(r,[["render",s],["__file","index.html.vue"]]),p=JSON.parse(`{"path":"/fr/","title":"","lang":"fr-FR","frontmatter":{"home":true,"heroImage":"./kotlin_logo.png","tagline":"A beginner's guide to a modern programming language","actions":[{"text":"Débuter →","link":"/fr/presentation/","type":"primary"}],"features":[{"title":"Bien commencer avec Kotlin","details":"lorem"},{"title":"Syntaxe de base","details":"lorem"},{"title":"Exemples","details":"lorem"}],"footer":"Worldline, 2021"},"headers":[{"level":2,"title":"Prerequisites","slug":"prerequisites","link":"#prerequisites","children":[]},{"level":2,"title":"Liens utiles","slug":"liens-utiles","link":"#liens-utiles","children":[]}],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"fr/index.md"}`);export{d as comp,p as data}; diff --git a/assets/index.html-DkjVfAMI.js b/assets/index.html-DkjVfAMI.js new file mode 100644 index 00000000..83385e91 --- /dev/null +++ b/assets/index.html-DkjVfAMI.js @@ -0,0 +1,13 @@ +import{_ as e,c as n,o as s,a}from"./app-DAFlGqDu.js";const t={},c=a(`📚 Fonctionnalités avancées de Kotlin
Propriétés déléguées
Kotlin permet de déléguer le getter et le setter d'une propriété à un autre objet, appelé délégué. C'est une classe qui définit les méthodes
getValue
etsetValue
.Kotlin fournit des délégués standard tels que des propriétés paresseuses et des propriétés observables.
▶️ this code illustrates delegate properties.
Concurrence et coroutines
Kotlin fournit un modèle de concurrence de haut niveau appelé Coroutines. Le développeur peut déléguer la gestion des threads au compilateur et à l'exécution et utiliser des constructions de niveau supérieur aux threads pour exprimer des opérations asynchrones.
Les coroutines de Kotlin tournent autour de ces concepts :
- Une coroutine est une instance de calcul suspendable.
- Kotlin a de nombreuses méthodes pour créer une coroutine telle que
launch
.- Une coroutine doit exister dans une portée de coroutine.
- Par exemple,
runBlocking
crée une portée de coroutine dans laquelle les coroutines peuvent être lancées.- Une coroutine peut exécuter des fonctions de suspension qui peuvent suspendre la coroutine mais ne bloquent pas le thread.
- Par exemple : le
delay
suspend la coroutine mais ne bloque pas le thread sur lequel elle s'exécute.- Les fonctions de suspension sont des opérations qui peuvent prendre du temps telles que les requêtes http et les appels au système de fichiers.
- Le qualificateur
suspend
définit une fonction de suspension. Il s'exécute dans une coroutine et peut appeler d'autres fonctions de suspension.Flow
permet de générer une liste de valeurs asynchrones.Deferred
etChannel
transfèrent respectivement une valeur unique et un flux de valeurs entre coroutines.▶️ this code show how to create a coroutine and suspend function and how to use them.
▶️ this code illustrated flows.
▶️ this code illustrates channels and deferred.
Littéral de fonction avec récepteur et constructeurs de type sécurisé
Comme vu précédemment, les extensions de fonctions ajoute du comportement à des classes existantes sans utiliser l'héritage. À l'intérieur de la définition de l'extension de fonction, nous pouvons référencer implicitement le récepteur d'extension (
this
).fun String.countCharacters() = length // or this.length +println("hello".countCharacters()) // prints 5 +
Nous pouvons définir cette extension avec une fonction littérale (ou lambda) au lieu d'une fonction classique (déclarée avec
fun
).var extFn: String.() -> Int +extFn = { length } // extFn is a function literal +println("hello".extFn()) // prints 5 +println(extFn("hello")) // prints 5 +
extFn
est une fonction littérale (lambda) qui a accès au récepteur (this
). C'est pourquoi on l'appelle une fonction littérale avec récepteur.
extFn("hello")
ouextFn("hello")
appelle l'extension comme prévu par les fonctions d'extension.Le type d'une fonction littérale avec récepteur est
funName: ReceiverType.(arg1Type, arg2Type, etc.) -> ReturnType
et est appelé avecfunName(receiverValue, arg1Value, etc.)
oureceiverValue.funName(arg1Value, etc.) .)
. Cependant, ce n'est pas l'aspect le plus intéressant.La partie importante est
extFn = { length }
qui peut être placée comme argument de fonction dans une fonction d'ordre supérieur. Le développeur qui appelle la fonction d'ordre supérieur doit définirextFn
, qui à son tour a accès au récepteur. Cela permet un style de programmation assez intéressant.▶️ ce code montre un example.
Les Type-safe builders combinent les monteurs bien nommées et les fonctions littérales avec récepteur pour créer des monteur avec un typage statique et sécurisé. La syntaxe particulière possible avec technique permet de définir une sorte de sous-langage aussi appelé DSL (domain specific language).
Le pattern monteur (Builder)
Est une technique permettant de construire des objects avec une syntaxe élégante.
// StirngBuilder uses the builder pattern +val text = StringBuilder("Temp") + .append(1) + .append(true) + .append("friend") + .toString() +
Ce code montre un type-safe builder basique.
Kotlin docs fournit un exemple plus avancé d'un monteur de documents HTML.
Exercises
Exercise 1
`,29),l=[c];function i(o,p){return s(),n("div",null,l)}const r=e(t,[["render",i],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/fr/kotlin-features-advanced/","title":"📚 Fonctionnalités avancées de Kotlin","lang":"fr-FR","frontmatter":{},"headers":[{"level":2,"title":"Propriétés déléguées","slug":"proprietes-deleguees","link":"#proprietes-deleguees","children":[]},{"level":2,"title":"Concurrence et coroutines","slug":"concurrence-et-coroutines","link":"#concurrence-et-coroutines","children":[]},{"level":2,"title":"Littéral de fonction avec récepteur et constructeurs de type sécurisé","slug":"litteral-de-fonction-avec-recepteur-et-constructeurs-de-type-securise","link":"#litteral-de-fonction-avec-recepteur-et-constructeurs-de-type-securise","children":[]},{"level":2,"title":"Exercises","slug":"exercises","link":"#exercises","children":[{"level":3,"title":"Exercise 1","slug":"exercise-1","link":"#exercise-1","children":[]}]}],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"fr/kotlin-features-advanced/README.md"}');export{r as comp,d as data}; diff --git a/assets/index.html-NwXwu3YI.js b/assets/index.html-NwXwu3YI.js new file mode 100644 index 00000000..5fc46e51 --- /dev/null +++ b/assets/index.html-NwXwu3YI.js @@ -0,0 +1 @@ +import{_ as e}from"./logo_worldline-dinT9MYm.js";import{_ as t,c as i,o as n,a}from"./app-DAFlGqDu.js";const r={},l=a('
- open the java-integration-exercise projects in the materials folder.
- Have a look at the Java class we provided you in the
src/main/java/com/worldline/learning/kotlin/java2kotlin
package. (yes, that's the Pokemon class)- Convert that Java class in Kotlin using IntelliJ's awesome copy-pasta tool! (just copy paste the java code in a kotlin file, one is provided at
src/main/kotlin/com/worldline/learning/kotlin/java2kotlin
)- Have a look at the generated Kotlin code, and note the major differences you spot!
Welcome
Who we are
We design payments technology that powers the growth of millions of businesses around the world. Engineering the next frontiers in payments technology
- Leader in payment and secured transactions
- Over 50 billion transactions/year
- 7000+ engineers in over 40 countries
- A huge & diverse tech-stack
Prerequisites
- Basic knowledge of object-oriented language like Java
- Prepare your development environment and install stuff before the session (see Tooling section)
Useful links
',10),o=[l];function s(d,c){return n(),i("div",null,o)}const u=t(r,[["render",s],["__file","index.html.vue"]]),m=JSON.parse(`{"path":"/en/","title":"Welcome","lang":"en-US","frontmatter":{"home":true,"heroImage":"./kotlin_logo.png","tagline":"A beginner's guide to a modern programming language","actions":[{"text":"Get started →","link":"/en/presentation/","type":"primary"}],"features":[{"title":"Language features","details":"null safety, extensions, lambdas, Java interoperability and more"},{"title":"Backend development","details":"With Ktor, spring and node.js"},{"title":"Frontend development","details":"Compose multiplatform, Kotlin/JS, Kotlin/WASM and JVM frameworks"},{"title":"Cross-platform development","details":"With KMP and Compose multiplatform"},{"title":"Advanced Kotlin","details":"Coroutines, delegates, Function literal with receiver, DSLs and more"},{"title":"Practical exercises and solutions","details":"All chapters have a set of exercises"}],"footer":"Worldline, 2023"},"headers":[{"level":2,"title":"Who we are","slug":"who-we-are","link":"#who-we-are","children":[]},{"level":2,"title":"Prerequisites","slug":"prerequisites","link":"#prerequisites","children":[]},{"level":2,"title":"Useful links","slug":"useful-links","link":"#useful-links","children":[]}],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"en/index.md"}`);export{u as comp,m as data}; diff --git a/assets/index.html-xZK9TAru.js b/assets/index.html-xZK9TAru.js new file mode 100644 index 00000000..61e67cc3 --- /dev/null +++ b/assets/index.html-xZK9TAru.js @@ -0,0 +1,195 @@ +import{_ as p,r as i,c,b as a,w as e,a as l,o as r,d as n,e as s}from"./app-DAFlGqDu.js";const u={},d=l(`📚 Développement du backend
De nombreux frameworks supportent officiellement Kotlin comme Spring, Quarkus et Ktor, parmi d'autres listés ici.
En outre, Kotlin est théoriquement compatible avec tout framework qui cible la JVM ou JS. Cependant, les frameworks qui ne supportent pas officiellement Kotlin peuvent nécessiter quelques ajustements pour l'utiliser.
Ktor
Ktor est une bibliothèque Kotlin multiplateforme permettant de développer des clients et des serveurs HTTP. Cela fait de Ktor une bibliothèque utile à la fois aux développeurs frontend, pour la partie client HTTP, ainsi qu'aux développeurs backend, pour la partie serveur HTTP. Dans ce qui suit, nous allons créer une API REST avec le serveur Ktor.
TP : développer une API avec Ktor
- Créez un projet sur start.ktor.io avec les plugins suivants : Content Negotiation, kotlinx.serialization, et Routing.
- Cliquez sur "Generate project".
- Téléchargez l'archive, décompressez-la et ouvrez le projet avec votre IDE préféré.
- Créez un package
models
et ajoutez-y une classe de donnéesCustomer
avec ces propriétés immuablesid : String, firstName : String, lastName : Chaîne, email : Chaîne
.- Annotez la classe avec
@Serializable
.- Créez un nouveau package nommé
routes
et ajoutez-y un fichierCustomerRoutes.kt
qui contiendra le code pour l'endpoint/customer
.- Le code ci-dessous fournit l'implémentation de certains endpoints. Veuillez implémenter les autres.
- Pour activer la route, appelez
customerRouting()
dans le fichier de configuration du routage situé dansplugins/Routing.kt
.- Pour plus de simplicité, utilisez une liste globale de clients en mémoire
val store = mutableListOf<Customer>()
.- Lancer le serveur en exécutant la méthode main.
- Tester l'API sur l'IDE en utilisant un fichier http ou en utilisant n'importe quel autre client.
CustomerRoutes.kt
val store = mutableListOf<Customer>() + +fun Route.customerRouting() { + route("/customer") { + get { + call.respond(store) + } + get("{id?}") { + val id = call.parameters["id"] ? : return@get call.respondText( + "Missing id", + status = HttpStatusCode.BadRequest + ) + val customer = + store.find { it.id == id } ? : return@get call.respondText( + "Pas de client avec l'id $id", + status = HttpStatusCode.NotFound + ) + call.respond(customer) + } + post { + val customer = call.receive<Customer>() + store.add(customer) + call.respondText("Customer stored correctly", status = HttpStatusCode.Created) + } + delete("{id?}") { + // TODO + } + } +} + +
plugins/Routing.kt
fun Application.configureRouting() { + routing { + customerRouting() + } +} +
return@label
Vous pouvez spécifier le niveau que vous voulez retourner avec un label explicite en utilisant
return@lambda
.lambdaA { + lambdaB { + lambdaC { + val randomInt = Random.nextInt(0, 100) + if (randomInt > 50) return@lambdaC else return@lambdaB + } + printf("In lambdaB") + } +} +
CustomerTest.http
POST http://127.0.0.1:8080/customer +Content-Type : application/json + +{ + "id" : "100", + "firstName" : "Jane", + "lastName" : "Smith", + "email" : "jane.smith@company.com" +} + + +### +POST http://127.0.0.1:8080/customer +Content-Type : application/json + +{ + "id" : "200", + "firstName" : "John", + "lastName" : "Smith", + "email" : "john.smith@company.com" +} + +### +POST http://127.0.0.1:8080/customer +Content-Type : application/json + +{ + "id" : "300", + "firstName" : "Mary", + "lastName" : "Smith", + "email" : "mary.smith@company.com" +} + + +### +GET http://127.0.0.1:8080/customer +Accept : application/json + +### +GET http://127.0.0.1:8080/customer/200 +Accepte : application/json + +### +GET http://127.0.0.1:8080/customer/500 +Accepte : application/json + +### +DELETE http://127.0.0.1:8080/customer/100 + +### +DELETE http://127.0.0.1:8080/customer/500 +
Cette page contient des étapes détaillées
node.js
Grâce à Kotlin/JS, nous pouvons écrire des applications qui ciblent node.js en utilisant Kotlin.
On peut même importer des librairies npm à condition de déclarer les API JS que l'on va utiliser en Kotlin. C'est ce qu'on appelle une déclaration externe (vous pouvez la considérer comme un équivalent des définitions de type de TypeScript) qui déclare les symboles auxquels nous voulons accéder en Kotlin grâce aux annotations @JsModule et @JsNonModule. Définir de telles déclarations externes peut s'avérer fastidieux et il ne semble pas y avoir de générateur automatique officiel et stable (dukat a été supprimé dans kotlin 1.8.20). Dans ce cas, nous avons deux options, soit écrire la déclaration externe nous-même, soit l'importer en tant que dépendance si elle est disponible.
Heureusement pour nous, le prochain TP utilise la librairie Express pour laquelle nous pouvons trouver une déclaration de type externe.
TP : API Rest avec Kotlin/JS et Express
- Dans IntelliJ, créer un nouveau projet node.js
- Une fois le projet chargé, éditer build.gradle.ts comme suit :
- Mettre la dernière version de Kotlin dans la ligne contenant
kotlin("js")
.- Ajoutez ces deux dépendances:
- La première est la bibliothèque Express,
- et la seconde est la définition externe fournie par chrisnkrueger/kotlin-express.
- ajouter une ligne
useCommonJs()
à l'intérieur du blocjs
. Ceci est nécessaire pour pouvoir utiliser chrisnkrueger/ kotlin-express dans notre code.implementation(npm("express", "> 4.0.0 < 5.0.0")); +implementation("dev.chriskrueger:kotlin-express:1.2.0"); +
- Modifiez main.kt comme suit. Cela crée un serveur API REST qui écoute le port 3000 et fournit une route GET /hello.
data class Message(val id : Int, val message : String) + +fun main() { + val messages = listOf(Message(0, "I love Kotlin/JS")) + val app = express.Express() + app.get("/hello") { req, res -> + res.send(messages) + } + + app.listen(3000) { + console.log("server start at port 3000") + } +} +
- Exécutez la tâche
nodeRun
depuis votre IDE ou depuis la ligne de commande (si vous avez installé Gradle).
- Si vous rencontrez une erreur avec Yarn lock, exécutez la tâche
kotlinUpgradeYarnLock
puis réessayez.- Ajouter des routes en POST, PUT et DELETE
- En ce qui concerne le corps du POST, Express positionne
req.body
àundefined
à moins que nous ne spécifions un body parser.
- Pour un corps en JSON, nous devons appeler
app.use(bodyParser.json())
.- bodyParser est une bibliothèque npm et malheureusement, chrisnkrueger/kotlin-express ne fournit pas de définition externe pour bodyParser au moment de l'écriture de ces lignes (chrisnkrueger/kotlin-express en version 1.2.0).
- Pouvez-vous essayer de la définir vous-même en lisant le code de la bibliothèque ?
- Vous pouvez trouver une solution ici
Spring framework
Spring est un framework célèbre pour le développement d'applications côté serveur : API REST, pages web générées par le serveur, microservices, etc. Il s'appuie sur l'écosystème Java pour la compilation et l'exécution, ce qui le rend compatible avec Kotlin. Mieux encore, Spring supporte officiellement Kotlin. On peut même démarrer un nouveau projet avec Kotlin et Gradle-Kotlin. Dans la prochaine section, nous utiliserons ce projet pour recréer notre API REST plus haut avec Spring.
TP : Spring boot part 1 - développer la même API avec Spring Boot
- Créez un projet sur start.spring.io (aussi appelé Spring initializr) avec les dépendances suivantes : Spring Web et Spring Boot DevTools.
- Choisissez Kotlin comme langage et Kotlin-Grade comme gestionnaire de projet.
- Ajoutez les dépendances suivantes : Spring Web et Spring Boot DevTools.
- Cliquez sur Generate. Téléchargez l'archive, décompressez-la et ouvrez le projet avec IntelliJ (de préférence) ou VSCode.
- Pour VSCode, installez une extension Kotlin et Spring Boot Extension Pack ( ⚠️ l'extension Spring ne semble pas supporter kotlin).
- Vérifiez que la partie plugins
build.gradle.kts
utilise la dernière version de Kotlin. Voici à quoi cela devrait ressembler avec Kotlin 1.8.10 :plugins { + id("org.springframework.boot") version "3.0.4" + id("io.spring.dependency-management") version "1.1.0" + kotlin("jvm") version "1.8.10" + kotlin("plugin.spring") version "1.8.10" +} +
- Créez la
data class Customer
dans le packagemodel
(sans l'annotation@Serializable
).- Créez un paquetage
controller
qui contient une classeCustomerController
qui fournit un CRUD en utilisant une liste globale.
- Vous pouvez trouver un squelette ci-dessous.
- 💡 Dans Spring, les contrôleurs Rest servent de routes Ktor, où un contrôleur définit une ressource REST.
- Définissez les mêmes routes que dans le TP précédent.
- Démarrez le serveur de l'API REST en exécutant :
- Sur Powershell :
.\\gradlew.bat bootRun
- Tout shell Unix :
.\\gradlew bootRun
- Ou bien, vérifiez si votre IDE fournit déjà des configurations d'exécution pour les projets Spring Boot.
- Veuillez tester les routes avec un client REST. Vous pouvez trouver des fichiers http ici au format JetBrains ou au format de l'extension REST Client de VSCode
CustomerController.kt
val store = mutableListOf<Customer>() + +@RestController +@RequestMapping("/customer") +class CustomerController { + @GetMapping + fun getAll() = store + + @GetMapping("{id}") + fun getById(@PathVariable id : String) { /* TODO : implement */ } + + @PostMapping + fun addOne(@RequestBody customer : Customer) { /* TODO : implement */ } + + @DeleteMapping("{id}") + fun deleteOne(@PathVariable id : String) { /* TODO : implement */ } +} +
TP : Spring boot partie 2 - ajouter une base de données
Allons un peu plus loin en stockant des données dans une base de données et en écrivant quelques tests.
Nous utiliserons la base de données en mémoire H2 pour des raisons de simplicité, puisqu'elle ne nécessite pas de serveur pour fonctionner. Les classes seront mappées aux tables de la base de données avec des annotations JPA. L'API de base de données que nous utiliserons s'appelle
JPARepository
. C'est une API légère qui fournit des fonctionnalités CRUD communes à partir d'une simple une interface.
- Créez un nouveau projet Spring en utilisant Spring initializr avec Kotlin et les dépendances suivantes : Spring Data JPA, H2 Database, Spring Boot DevTools, Spring Web.
- Ouvrez le projet et ajoutez cette classe dans le package
model
@Entity class Product(@Id @GeneratedValue var id : Long ? = null, var name : String, var price : Int)
. Ceci définit la classe ainsi que les annotations JPA minimales (@Entity
,@Id
et@GeneratedValue
) pour générer la table correspondante.- Dans le package
repository
, déclarez l'interfaceProductRepository
comme suitinterface ProductRepository : JpaRepository<Produit, Long>
. C'est suffisant pour que Spring génère une implémentation avec des caractéristiques communes comme nous le verrons plus tard.- Ensuite, créez une classe
ProductService
qui contiendra la logique métier. En termes d'architecture, le contrôleur appelle un service qui, à son tour, s'appuie sur d'autres services ou référentiels.ProductService.kt
@Service +class ProductService(@Autowired val productRepository: ProductRepository) { + fun getAll() = productRepository.findAll() + + // use findByIdOrNull instad of findById because the latter returns an optional<Product> instead of Product? + fun getById(id: Long) = productRepository.findByIdOrNull(id) +} +
- Dans le package controller, créez une classe
ProductController
qui est mappée à/product
et injectée avec@Autowired
. Répondez à@Get
comme suit.ProductController.kt
@RestController +@RequestMapping("/product") +class ProductController(@Autowired val productService : ProductService) { + @GetMapping fun getAll() = productService.getAll() + + @GetMapping("{id}") + fun getById(@PathVariable id : Long) = + productService.getById(id) ? : throw ResponseStatusException(HttpStatus.NOT_FOUND) +} +
Kotlin rend getById(@PathVariable id : Long) plus concis
L'opérateur Elvis
?:
permet de simplifier le code. Voici une version plus longue en guise de référence.@GetMapping("{id}") +fun getById(@PathVariable id : Long) : Produit { + val product = productService.getById(id) + if (product != null){ + return product + } + throw ResponseStatusException(HttpStatus.NOT_FOUND) +} +
En outre, Spring fournit
@ControllerAdvice
pour modifier le message d'exception. Vous pouvez voir un [exemple ici] (https://spring.io/guides/tutorials/rest/).
- Exécutons le projet. Avant de lancer le projet, nous devons ajouter un plugin qui permet aux classes Kotlin de générer un constructeur par défaut
id("org.jetbrains.kotlin.plugin.jpa") version "1.8.10"
. Les plugins devraient ressembler à ce qui suit :plugins { + id("org.jetbrains.kotlin.plugin.jpa") version "1.8.10" + id("org.springframework.boot") version "3.0.4" + id("io.spring.dependency-management") version "1.1.0" + kotlin("jvm") version "1.8.10" + kotlin("plugin.spring") version "1.8.10" +} + +
- En guise d'exercice, implémentez ces routes : POST d'un seul produit, DELETE par id (
/produit/{id}
) et GET par id (/produit/{id}
).
- Indice :
ProductController
fournit déjà les méthodes nécessaires.- Appelez les différents points de terminaison avec un client REST.
- Tester votre API Rest avec un client HTTP
TP : Spring boot partie 3 - ajouter des tests
Les frameworks Spring permettent d'effectuer différents types de tests en fournissant différentes classes dès le départ :
- Tests unitaires/de composants des services et de l'API REST. Cela se fait par le biais d'utilitaires de bouchonnage tels que
MockMVC
.- Tests d'intégration de l'API REST en utilisant
TestRestTemplate
. Dans ce cas, un serveur complet est exécuté et testé.La plupart des classes fournies par Spring, si ce n'est toutes, offrent une syntaxe élégante pour les développeurs Java. Certaines d'entre elles vont plus loin en tirant parti des caractéristiques spécifiques de Kotlin. Dans ce qui suit, nous allons nous concentrer sur les parties qui fournissent des DSLs Kotlin, à savoir le test unitaire de l'API REST avec
MockMVC
.
- Créer une classe de test
ProductControllerUnitTests
avec le contenu initial ci-dessous.MockMvc
permet de tester unitairement l'API REST. L'annotation@AutoConfigureMockMvc
permet à Spring de la configurer automatiquement.@SpringBootTest +@AutoConfigureMockMvc +classe ProductControllerTests( + @Autowired val mockMvc : MockMvc, + @Autowired val productRepository : ProductRepository) { + + @BeforeEach + fun reset(){ + productRepository.deleteAll() + } +} +
`,47),k=n("div",{class:"language-kotlin line-numbers-mode","data-highlighter":"prismjs","data-ext":"kt","data-title":"kt"},[n("pre",null,[n("code",null,[n("span",{class:"line"},[n("span",{class:"token annotation builtin"},"@Test")]),s(` +`),n("span",{class:"line"},[n("span",{class:"token keyword"},"fun"),s(),n("span",{class:"token function"},"testWithClassicApproach"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"{")]),s(` +`),n("span",{class:"line"},[s(" mockMvc"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"perform"),n("span",{class:"token punctuation"},"("),n("span",{class:"token keyword"},"get"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string-literal singleline"},[n("span",{class:"token string"},'"/product"')]),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")")]),s(` +`),n("span",{class:"line"},[s(" "),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"andExpect"),n("span",{class:"token punctuation"},"("),n("span",{class:"token function"},"status"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"."),s("isOk"),n("span",{class:"token punctuation"},")")]),s(` +`),n("span",{class:"line"},[s(" "),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"andExpect"),n("span",{class:"token punctuation"},"("),n("span",{class:"token function"},"content"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"string"),n("span",{class:"token punctuation"},"("),n("span",{class:"token function"},"containsString"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string-literal singleline"},[n("span",{class:"token string"},'"[]"')]),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},")")]),s(` +`),n("span",{class:"line"},[n("span",{class:"token punctuation"},"}")]),s(` +`),n("span",{class:"line"})])]),n("div",{class:"line-numbers","aria-hidden":"true",style:{"counter-reset":"line-number 0"}},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),m=n("div",{class:"language-kotlin line-numbers-mode","data-highlighter":"prismjs","data-ext":"kt","data-title":"kt"},[n("pre",null,[n("code",null,[n("span",{class:"line"},[n("span",{class:"token annotation builtin"},"@Test")]),s(` +`),n("span",{class:"line"},[n("span",{class:"token keyword"},"fun"),s(),n("span",{class:"token function"},"`test GET a single product`"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{")]),s(` +`),n("span",{class:"line"},[s(" mockMvc"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"get"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string-literal singleline"},[n("span",{class:"token string"},'"/product/1"')]),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"andExpect"),s(),n("span",{class:"token punctuation"},"{")]),s(` +`),n("span",{class:"line"},[s(" status "),n("span",{class:"token punctuation"},"{"),s(),n("span",{class:"token function"},"isOk"),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"}")]),s(` +`),n("span",{class:"line"},[s(" "),n("span",{class:"token function"},"jsonPath"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string-literal singleline"},[n("span",{class:"token string"},'"$.name"')]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(),n("span",{class:"token function"},"value"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string-literal singleline"},[n("span",{class:"token string"},'"A"')]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"}")]),s(` +`),n("span",{class:"line"},[s(" "),n("span",{class:"token function"},"jsonPath"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string-literal singleline"},[n("span",{class:"token string"},'"$.price"')]),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"{"),s(),n("span",{class:"token function"},"value"),n("span",{class:"token punctuation"},"("),n("span",{class:"token number"},"1"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"}")]),s(` +`),n("span",{class:"line"},[s(" content "),n("span",{class:"token punctuation"},"{"),s(),n("span",{class:"token function"},"contentType"),n("span",{class:"token punctuation"},"("),s("MediaType"),n("span",{class:"token punctuation"},"."),s("APPLICATION_JSON"),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token punctuation"},"}")]),s(` +`),n("span",{class:"line"},[s(" "),n("span",{class:"token punctuation"},"}")]),s(` +`),n("span",{class:"line"},[n("span",{class:"token punctuation"},"}")]),s(` +`),n("span",{class:"line"})])]),n("div",{class:"line-numbers","aria-hidden":"true",style:{"counter-reset":"line-number 0"}},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),v=l(`
- Ajoutez les deux tests ci-dessous. Le premier utilise une approche classique tandis que le second tire parti des capacités du DSL de Kotlin. De plus, nous utilisons une chaîne littérale plus lisible.
- En guise d'exercice, écrire des tests pour les autres points d'accès.
Le constructeur de requêtes de JpaRepository
Les repository Spring implémentent des requêtes basées sur le nom de leurs méthodes. Par exemple, pour obtenir tous les produits triés par nom, nous pouvons ajouter cette méthode à l'interface.
interface ProductRepository : JpaRepository<Produit, Long> { + fun findAllByOrderByNameAsc() : List<Produit> ; +} +
La [documentation officielle] (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation) fournit des explications et des exemples plus détaillés.
Projets terminés
Aller plus loin
Ces tutoriels officiels vont encore plus loin :
- Ce tutoriel de kotlinlang montre comment créer un service web RESTful avec une base de données en utilisant Spring Boot.
- Ce tutoriel de spring.io montre comment construire une application web avec Spring Boot et Kotlin.
- Rest APIs with Spring
- Quarkus et Kotlin
Lien et références
`,9);function g(b,h){const t=i("CodeGroupItem"),o=i("CodeGroup");return r(),c("div",null,[d,a(o,null,{default:e(()=>[a(t,{title:"Sans DSL (Test Get All)"},{default:e(()=>[k]),_:1}),a(t,{title:"Avec DSL (Test Get Single)"},{default:e(()=>[m]),_:1})]),_:1}),v])}const q=p(u,[["render",g],["__file","index.html.vue"]]),y=JSON.parse('{"path":"/fr/backend-development/","title":"📚 Développement du backend","lang":"fr-FR","frontmatter":{},"headers":[{"level":2,"title":"Ktor","slug":"ktor","link":"#ktor","children":[{"level":3,"title":"TP : développer une API avec Ktor","slug":"tp-developper-une-api-avec-ktor","link":"#tp-developper-une-api-avec-ktor","children":[]}]},{"level":2,"title":"node.js","slug":"node-js","link":"#node-js","children":[{"level":3,"title":"TP : API Rest avec Kotlin/JS et Express","slug":"tp-api-rest-avec-kotlin-js-et-express","link":"#tp-api-rest-avec-kotlin-js-et-express","children":[]}]},{"level":2,"title":"Spring framework","slug":"spring-framework","link":"#spring-framework","children":[{"level":3,"title":"TP : Spring boot part 1 - développer la même API avec Spring Boot","slug":"tp-spring-boot-part-1-developper-la-meme-api-avec-spring-boot","link":"#tp-spring-boot-part-1-developper-la-meme-api-avec-spring-boot","children":[]},{"level":3,"title":"TP : Spring boot partie 2 - ajouter une base de données","slug":"tp-spring-boot-partie-2-ajouter-une-base-de-donnees","link":"#tp-spring-boot-partie-2-ajouter-une-base-de-donnees","children":[]},{"level":3,"title":"TP : Spring boot partie 3 - ajouter des tests","slug":"tp-spring-boot-partie-3-ajouter-des-tests","link":"#tp-spring-boot-partie-3-ajouter-des-tests","children":[]},{"level":3,"title":"Projets terminés","slug":"projets-termines","link":"#projets-termines","children":[]}]},{"level":2,"title":"Aller plus loin","slug":"aller-plus-loin","link":"#aller-plus-loin","children":[]},{"level":2,"title":"Lien et références","slug":"lien-et-references","link":"#lien-et-references","children":[]}],"git":{"updatedTime":1724774876000,"contributors":[{"name":"yostane","email":"1958676+yostane@users.noreply.github.com","commits":1}]},"filePathRelative":"fr/backend-development/README.md"}');export{q as comp,y as data}; diff --git a/assets/kmp-compose-desktop-CzocCLNL.png b/assets/kmp-compose-desktop-CzocCLNL.png new file mode 100644 index 00000000..acef82a2 Binary files /dev/null and b/assets/kmp-compose-desktop-CzocCLNL.png differ diff --git a/assets/kmp_codelab-CiTPMWjt.js b/assets/kmp_codelab-CiTPMWjt.js new file mode 100644 index 00000000..69a29a44 --- /dev/null +++ b/assets/kmp_codelab-CiTPMWjt.js @@ -0,0 +1 @@ +const s="/learning-kotlin/assets/kmp_codelab-J5AI0da_.png";export{s as _}; diff --git a/assets/kmp_codelab-J5AI0da_.png b/assets/kmp_codelab-J5AI0da_.png new file mode 100644 index 00000000..9ebc2b8b Binary files /dev/null and b/assets/kmp_codelab-J5AI0da_.png differ diff --git a/assets/kotlin-decision-tree-4i7nEr1Z.svg b/assets/kotlin-decision-tree-4i7nEr1Z.svg new file mode 100644 index 00000000..d3375a77 --- /dev/null +++ b/assets/kotlin-decision-tree-4i7nEr1Z.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/kotlin-used-for-Bdlavnqs.js b/assets/kotlin-used-for-Bdlavnqs.js new file mode 100644 index 00000000..c91b22fd --- /dev/null +++ b/assets/kotlin-used-for-Bdlavnqs.js @@ -0,0 +1 @@ +const s="/learning-kotlin/assets/kotlin-used-for-FU2DHhYT.png";export{s as _}; diff --git a/assets/kotlin-used-for-FU2DHhYT.png b/assets/kotlin-used-for-FU2DHhYT.png new file mode 100644 index 00000000..d0232536 Binary files /dev/null and b/assets/kotlin-used-for-FU2DHhYT.png differ diff --git a/assets/kotlin-wasm-flag-BKaaN9Pq.png b/assets/kotlin-wasm-flag-BKaaN9Pq.png new file mode 100644 index 00000000..c51b253b Binary files /dev/null and b/assets/kotlin-wasm-flag-BKaaN9Pq.png differ diff --git a/assets/kotlin-wasm-webapp-R4_9ho9v.js b/assets/kotlin-wasm-webapp-R4_9ho9v.js new file mode 100644 index 00000000..d4ed5a84 --- /dev/null +++ b/assets/kotlin-wasm-webapp-R4_9ho9v.js @@ -0,0 +1 @@ +const s="/learning-kotlin/assets/kotlin-wasm-webapp-ViYyGRus.png";export{s as _}; diff --git a/assets/kotlin-wasm-webapp-ViYyGRus.png b/assets/kotlin-wasm-webapp-ViYyGRus.png new file mode 100644 index 00000000..0910b9d7 Binary files /dev/null and b/assets/kotlin-wasm-webapp-ViYyGRus.png differ diff --git a/assets/launch-android-app-BS4WBFFP.png b/assets/launch-android-app-BS4WBFFP.png new file mode 100644 index 00000000..0deef90e Binary files /dev/null and b/assets/launch-android-app-BS4WBFFP.png differ diff --git a/assets/logo_worldline-dinT9MYm.js b/assets/logo_worldline-dinT9MYm.js new file mode 100644 index 00000000..bba4f9b6 --- /dev/null +++ b/assets/logo_worldline-dinT9MYm.js @@ -0,0 +1 @@ +const o="/learning-kotlin/assets/logo_worldline-t5KadDQv.png";export{o as _}; diff --git a/assets/logo_worldline-t5KadDQv.png b/assets/logo_worldline-t5KadDQv.png new file mode 100644 index 00000000..6981642d Binary files /dev/null and b/assets/logo_worldline-t5KadDQv.png differ diff --git a/assets/qrcode-devoxxma23-Beeff8NO.gif b/assets/qrcode-devoxxma23-Beeff8NO.gif new file mode 100644 index 00000000..dc77dac3 Binary files /dev/null and b/assets/qrcode-devoxxma23-Beeff8NO.gif differ diff --git a/assets/qrcode-mds2023-tbApIRH6.gif b/assets/qrcode-mds2023-tbApIRH6.gif new file mode 100644 index 00000000..accc55d1 Binary files /dev/null and b/assets/qrcode-mds2023-tbApIRH6.gif differ diff --git a/assets/qrcode-mixtit24-3_5F4s9g.svg b/assets/qrcode-mixtit24-3_5F4s9g.svg new file mode 100644 index 00000000..20e2aa8a --- /dev/null +++ b/assets/qrcode-mixtit24-3_5F4s9g.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/qrcode-mixtit24-D-YZ7Myr.js b/assets/qrcode-mixtit24-D-YZ7Myr.js new file mode 100644 index 00000000..044db536 --- /dev/null +++ b/assets/qrcode-mixtit24-D-YZ7Myr.js @@ -0,0 +1 @@ +const s="/learning-kotlin/assets/Kotlin-Beyond-Android-B6J4ER4N.png",n="/learning-kotlin/assets/Androidmakers2023Kotlinshortlink-7XGfMd7N.svg",t="/learning-kotlin/assets/qrcode-mds2023-tbApIRH6.gif",o="/learning-kotlin/assets/qrcode-mixtit24-3_5F4s9g.svg";export{s as _,n as a,t as b,o as c}; diff --git a/assets/style-CTaFAUa6.css b/assets/style-CTaFAUa6.css new file mode 100644 index 00000000..303cc833 --- /dev/null +++ b/assets/style-CTaFAUa6.css @@ -0,0 +1 @@ +@charset "UTF-8";.vp-back-to-top-button{position:fixed!important;inset-inline-end:1rem;bottom:4rem;z-index:100;width:48px;height:48px;padding:12px;border-width:0;border-radius:50%;background:var(--back-to-top-bg-color);color:var(--back-to-top-color);box-shadow:2px 2px 10px 4px var(--back-to-top-shadow);cursor:pointer}@media (max-width: 959px){.vp-back-to-top-button{transform:scale(.8);transform-origin:100% 100%}}@media print{.vp-back-to-top-button{display:none}}.vp-back-to-top-button:hover{color:var(--back-to-top-color-hover)}.vp-back-to-top-button .back-to-top-icon{overflow:hidden;width:24px;height:24px;margin:0 auto;background:var(--back-to-top-icon-color);-webkit-mask-image:var(--back-to-top-icon);mask-image:var(--back-to-top-icon);-webkit-mask-position:50%;mask-position:50%;-webkit-mask-size:cover;mask-size:cover}.vp-scroll-progress{position:absolute;right:-2px;bottom:-2px;width:52px;height:52px}.vp-scroll-progress svg{width:100%;height:100%}.vp-scroll-progress circle{opacity:.9;transform:rotate(-90deg);transform-origin:50% 50%}.back-to-top-enter-active,.back-to-top-leave-active{transition:opacity .3s}.back-to-top-enter-from,.back-to-top-leave-to{opacity:0}:root{--back-to-top-z-index: 5;--back-to-top-icon: url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2048%2048'%3e%3cpath%20fill='none'%20stroke='currentColor'%20stroke-linecap='round'%20stroke-linejoin='round'%20stroke-width='4'%20d='M24.008%2014.1V42M12%2026l12-12l12%2012M12%206h24'%20/%3e%3c/svg%3e");--back-to-top-bg-color: #fff;--back-to-top-color: #3eaf7c;--back-to-top-icon-color: currentcolor;--back-to-top-color-hover: #71cda3;--back-to-top-shadow: rgb(0 0 0 / 20%)}:root{--code-copy-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2'%3e%3cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2' /%3e%3c/svg%3e");--code-copied-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2'%3e%3cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9 2 2 4-4' /%3e%3c/svg%3e");--copy-code-color: var(--code-c-line-number, #9e9e9e);--copy-code-hover: var(--code-highlight-bg-color, rgb(0 0 0 / 50%))}.vp-copy-code-button{position:absolute;top:.5em;right:.5em;z-index:5;width:2.5rem;height:2.5rem;padding:0;border-width:0;border-radius:.5rem;background:transparent;outline:none;opacity:0;cursor:pointer;transition:opacity .4s}@media print{.vp-copy-code-button{display:none}}.vp-copy-code-button:before{content:"";display:inline-block;width:1.25rem;height:1.25rem;padding:.625rem;background:currentcolor;color:var(--copy-code-color);font-size:1.25rem;-webkit-mask-image:var(--code-copy-icon);mask-image:var(--code-copy-icon);-webkit-mask-position:50%;mask-position:50%;-webkit-mask-size:1em;mask-size:1em;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.vp-copy-code-button:focus,.vp-copy-code-button.copied{opacity:1}.vp-copy-code-button:hover,.vp-copy-code-button.copied{background:var(--copy-code-hover)}.vp-copy-code-button.copied:before{-webkit-mask-image:var(--code-copied-icon);mask-image:var(--code-copied-icon)}.vp-copy-code-button.copied:after{content:attr(data-copied);position:absolute;top:0;right:calc(100% + .25rem);display:block;height:1.25rem;padding:.625rem;border-radius:.5rem;background:var(--copy-code-hover);color:var(--copy-code-color);font-weight:500;line-height:1.25rem;white-space:nowrap}.copy-code-disabled .vp-copy-code-button{display:none}body:not(.copy-code-disabled) div[class*=language-]:hover:before{display:none}body:not(.copy-code-disabled) div[class*=language-]:hover .vp-copy-code-button{opacity:1}:root{--nprogress-color: #29d;--nprogress-z-index: 1031}#nprogress{pointer-events:none}#nprogress .bar{position:fixed;top:0;left:0;z-index:var(--nprogress-z-index);width:100%;height:2px;background:var(--nprogress-color)}:root{--code-padding-x: 1.25rem;--code-padding-y: 1rem;--code-border-radius: 6px;--code-line-height: 1.6;--code-font-family: consolas, monaco, "Andale Mono", "Ubuntu Mono", monospace}div[class*=language-]{position:relative;border-radius:var(--code-border-radius);background-color:var(--code-c-bg)}div[class*=language-]:before{content:attr(data-title);position:absolute;top:.8em;right:1em;z-index:3;color:var(--code-c-text);font-size:.75rem}div[class*=language-] pre{position:relative;z-index:1;overflow-x:auto;margin:.75rem 0;border-radius:var(--code-border-radius);font-size:14px;font-family:var(--code-font-family);line-height:var(--code-line-height)}div[class*=language-] pre code{display:block;box-sizing:border-box;width:-moz-fit-content;width:fit-content;min-width:100%;padding:var(--code-padding-y) var(--code-padding-x);background-color:transparent!important;overflow-wrap:unset;-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}:root{--code-c-text: #f8f8f2;--code-c-bg: #2e3440;--code-c-highlight-bg: #343d4e;--code-c-line-number: rgba(248, 248, 242, .67)}.token.comment,.token.prolog,.token.doctype,.token.cdata{color:#636f88}.token.punctuation{color:#81a1c1}.namespace{opacity:.7}.token.property,.token.tag,.token.constant,.token.symbol,.token.deleted{color:#81a1c1}.token.number{color:#b48ead}.token.boolean{color:#81a1c1}.token.selector,.token.attr-name,.token.string,.token.char,.token.builtin,.token.inserted{color:#a3be8c}.token.operator,.token.entity,.token.url,.language-css .token.string,.style .token.string,.token.variable{color:#81a1c1}.token.atrule,.token.attr-value,.token.function,.token.class-name{color:#88c0d0}.token.keyword{color:#81a1c1}.token.regex,.token.important{color:#ebcb8b}.token.important,.token.bold{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}:root{--code-line-number-width: 3.5rem}div[class*=language-]:not(.line-numbers-mode) .line-numbers{display:none}div[class*=language-].line-numbers-mode:after{content:"";position:absolute;top:0;left:0;width:var(--code-line-number-width);height:100%;border-right:1px solid var(--code-highlight-bg-color);border-radius:var(--code-border-radius) 0 0 var(--code-border-radius)}div[class*=language-].line-numbers-mode pre{vertical-align:middle;margin-left:var(--code-line-number-width)}div[class*=language-].line-numbers-mode code{padding-left:1rem}div[class*=language-].line-numbers-mode .line-numbers{counter-reset:line-number;position:absolute;top:0;width:var(--code-line-number-width);padding-top:var(--code-padding-y);color:var(--code-c-line-number, --code-c-text);font-size:.875em;line-height:var(--code-line-height);text-align:center}div[class*=language-].line-numbers-mode .line-number{position:relative;z-index:3;font-family:var(--code-font-family);-webkit-user-select:none;-moz-user-select:none;user-select:none}div[class*=language-].line-numbers-mode .line-number:before{content:counter(line-number);counter-increment:line-number}.vp-badge{display:inline-block;vertical-align:top;height:18px;padding:0 6px;border-radius:3px;color:var(--c-bg);font-weight:600;font-size:14px;line-height:18px;transition:color var(--t-color),background-color var(--t-color)}.vp-badge.tip{background-color:var(--c-badge-tip)}.vp-badge.warning{background-color:var(--c-badge-warning);color:var(--c-badge-warning-text)}.vp-badge.danger{background-color:var(--c-badge-danger);color:var(--c-badge-danger-text)}.vp-badge+.vp-badge{margin-left:5px}.code-group-nav{margin-top:.85rem;margin-bottom:calc(-1.7rem - 6px);padding-top:10px;padding-bottom:calc(1.7rem - 6px);padding-left:10px;border-top-left-radius:6px;border-top-right-radius:6px;background-color:var(--c-code-group-tab-bg)}@media (max-width: 419px){.code-group-nav{margin-right:-1.5rem;margin-left:-1.5rem;border-radius:0}}.code-group-nav-tab{padding:5px;border:0;background-color:transparent;color:var(--c-code-group-tab-title);font-weight:600;font-size:.85em;line-height:1.4;cursor:pointer}.code-group-nav-tab:focus{outline:none}.code-group-nav-tab:focus-visible{outline:1px solid var(--c-code-group-tab-outline)}.code-group-nav-tab.active{border-bottom:var(--c-code-group-tab-active-border) 1px solid}.code-group-item{display:none}.code-group-item.active{display:block}.code-group-item>pre{background-color:orange}.vp-features{display:flex;flex-wrap:wrap;place-content:stretch space-between;align-items:flex-start;margin-top:2.5rem;padding:1.2rem 0;border-top:1px solid var(--c-border);transition:border-color var(--t-color)}@media (max-width: 719px){.vp-features{flex-direction:column}}.vp-feature{flex-grow:1;flex-basis:30%;max-width:30%}@media (max-width: 719px){.vp-feature{max-width:100%;padding:0 2.5rem}}.vp-feature h2{padding-bottom:0;border-bottom:none;color:var(--c-text-light);font-weight:500;font-size:1.4rem}@media (max-width: 419px){.vp-feature h2{font-size:1.25rem}}.vp-feature p{color:var(--c-text-lighter)}.vp-footer{padding:2.5rem;border-top:1px solid var(--c-border);color:var(--c-text-lighter);text-align:center;transition:border-color var(--t-color)}.vp-hero{text-align:center}.vp-hero-image{display:block;max-width:100%;max-height:280px;margin:3rem auto 1.5rem}@media (max-width: 419px){.vp-hero-image{max-height:210px;margin:2rem auto 1.2rem}}#main-title{font-size:3rem}@media (max-width: 419px){#main-title{font-size:2rem}}#main-title,.vp-hero-description,.vp-hero-actions{margin:1.8rem auto}@media (max-width: 419px){#main-title,.vp-hero-description,.vp-hero-actions{margin:1.2rem auto}}.vp-hero-actions{display:flex;flex-wrap:wrap;gap:1rem;justify-content:center}.vp-hero-description{max-width:35rem;color:var(--c-text-lightest);font-size:1.6rem;line-height:1.3}@media (max-width: 419px){.vp-hero-description{font-size:1.2rem}}.vp-hero-action-button{display:inline-block;box-sizing:border-box;padding:.8rem 1.6rem;border-width:2px;border-style:solid;border-radius:4px;font-size:1.2rem;transition:background-color var(--t-color)}@media (max-width: 419px){.vp-hero-action-button{padding:.6rem 1.2rem;font-size:1rem}}.vp-hero-action-button.primary{border-color:var(--c-brand);background-color:var(--c-brand);color:var(--c-bg)}.vp-hero-action-button.primary:hover{background-color:var(--c-brand-light)}.vp-hero-action-button.secondary{border-color:var(--c-brand);background-color:var(--c-bg);color:var(--c-brand)}.vp-hero-action-button.secondary:hover{background-color:var(--c-brand-light);color:var(--c-bg)}.vp-home{display:block;max-width:var(--homepage-width);margin:0 auto;padding:var(--navbar-height) 2rem 0}@media (max-width: 419px){.vp-home{padding-right:1.5rem;padding-left:1.5rem}}.vp-home .theme-default-content{margin:0;padding:0}.vp-site-logo{vertical-align:top;height:var(--navbar-line-height);margin-right:var(--navbar-padding-v)}.vp-site-name{position:relative;color:var(--c-text);font-weight:600;font-size:1.3rem}@media screen and (max-width: 719px){.vp-site-name{display:block;overflow:hidden;width:calc(100vw - 11rem);text-overflow:ellipsis;white-space:nowrap}}.vp-dropdown-enter-from,.vp-dropdown-leave-to{height:0!important}.vp-navbar-dropdown-wrapper{cursor:pointer}.vp-navbar-dropdown-wrapper:not(.mobile){height:1.8rem}.vp-navbar-dropdown-wrapper:not(.mobile):hover .vp-navbar-dropdown,.vp-navbar-dropdown-wrapper:not(.mobile).open .vp-navbar-dropdown{display:block!important}.vp-navbar-dropdown-wrapper.mobile .vp-navbar-dropdown{overflow:hidden;transition:height .1s ease-out;padding-top:.5rem}.vp-navbar-dropdown-wrapper:not(.mobile) .vp-navbar-dropdown{position:absolute;top:100%;right:0;display:none;overflow-y:auto;box-sizing:border-box;height:auto!important;max-height:calc(100vh - 2.7rem);margin:0;padding:.6rem 0;border:1px solid var(--c-border);border-bottom-color:var(--c-border-dark);border-radius:.25rem;background-color:var(--c-bg-navbar);text-align:left;white-space:nowrap}.vp-navbar-dropdown-title{display:block;padding:inherit;border:none;background:transparent;color:var(--c-text);font-weight:500;font-size:.9rem;font-family:inherit;line-height:1.4rem;cursor:inherit}.vp-navbar-dropdown-wrapper.mobile .vp-navbar-dropdown-title{display:none}.vp-navbar-dropdown-title:hover{border-color:transparent}.vp-navbar-dropdown-title-mobile{display:none;padding:inherit;border:none;background:transparent;color:var(--c-text);font-weight:600;font-size:inherit;font-family:inherit;line-height:1.4rem;cursor:inherit}.vp-navbar-dropdown-wrapper.mobile .vp-navbar-dropdown-title-mobile{display:block}.vp-navbar-dropdown-title-mobile:hover{color:var(--c-text-accent)}.vp-navbar-dropdown-item{color:inherit;line-height:1.7rem}.vp-navbar-dropdown-item a{position:relative;display:block;margin-bottom:0;padding:0 1.5rem 0 1.25rem;border-bottom:none;font-weight:400;line-height:1.7rem}.vp-navbar-dropdown-item a:hover,.vp-navbar-dropdown-item a.route-link-active{color:var(--c-text-accent)}.vp-navbar-dropdown-item a.route-link-active:after{content:"";position:absolute;top:calc(50% - 2px);left:9px;width:0;height:0;border-top:3px solid transparent;border-bottom:3px solid transparent;border-left:5px solid var(--c-text-accent)}.vp-navbar-dropdown-wrapper.mobile .vp-navbar-dropdown-item>a{font-size:15px;line-height:2rem}.vp-navbar-dropdown-subtitle{margin:.45rem 0 0;padding:1rem 0 .45rem;border-top:1px solid var(--c-border);font-size:.9rem}.vp-navbar-dropdown-wrapper.mobile .vp-navbar-dropdown-subtitle{margin-top:0;padding-top:0;padding-bottom:0;border-top:0;font-size:15px;line-height:2rem}.vp-navbar-dropdown-item:first-child .vp-navbar-dropdown-subtitle{margin-top:0;padding-top:0;border-top:0}.vp-navbar-dropdown-subtitle>span{padding:0 1.5rem 0 1.25rem}.vp-navbar-dropdown-subtitle>a{font-weight:inherit}.vp-navbar-dropdown-subtitle>a.route-link-active:after{display:none}.vp-navbar-dropdown-subitem-wrapper{padding:0;list-style:none}.vp-navbar-dropdown-subitem{font-size:.9em}.vp-navbar-dropdown-wrapper.mobile .vp-navbar-dropdown-subitem{padding-left:1rem;font-size:14px}.vp-navbar-items{display:inline-block}@media print{.vp-navbar-items{display:none}}.vp-navbar-items a{display:inline-block;color:inherit;line-height:1.4rem}.vp-navbar-items a:hover,.vp-navbar-items a.route-link-active{color:var(--c-text)}.vp-navbar-item{position:relative;display:inline-block;margin-left:1.5rem;line-height:var(--navbar-line-height)}@media (max-width: 719px){.vp-navbar-item{margin-left:0}}.vp-navbar-item:first-child{margin-left:0}.vp-navbar-item a:hover,.vp-navbar-item a.route-link-active{color:var(--c-text-accent)}.vp-navbar-item>a:hover,.vp-navbar-item>a.route-link-active{margin-bottom:-2px;border-bottom:2px solid var(--c-text-accent)}@media (max-width: 719px){.vp-navbar-item>a:hover,.vp-navbar-item>a.route-link-active{margin-bottom:0;border-bottom:none}}.vp-toggle-color-mode-button{display:flex;margin:auto;margin-left:1rem;border:0;background:none;color:var(--c-text);opacity:.8;cursor:pointer}@media print{.vp-toggle-color-mode-button{display:none}}.vp-toggle-color-mode-button:hover{opacity:1}.vp-toggle-color-mode-button .light-icon,.vp-toggle-color-mode-button .dark-icon{width:1.25rem;height:1.25rem}.vp-toggle-sidebar-button{position:absolute;top:.6rem;left:1rem;display:none;padding:.6rem;cursor:pointer}@media screen and (max-width: 719px){.vp-toggle-sidebar-button{display:block}}.vp-toggle-sidebar-button .icon{display:flex;flex-direction:column;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;cursor:inherit}.vp-toggle-sidebar-button .icon span{display:inline-block;width:100%;height:2px;border-radius:2px;background-color:var(--c-text);transition:transform var(--t-transform)}.vp-toggle-sidebar-button .icon span:nth-child(2){margin:6px 0}.vp-theme-container.sidebar-open .vp-toggle-sidebar-button .icon span:nth-child(1){transform:rotate(45deg) translate3d(5.5px,5.5px,0)}.vp-theme-container.sidebar-open .vp-toggle-sidebar-button .icon span:nth-child(2){transform:scale3d(0,1,1)}.vp-theme-container.sidebar-open .vp-toggle-sidebar-button .icon span:nth-child(3){transform:rotate(-45deg) translate3d(6px,-6px,0)}.vp-theme-container.sidebar-open .vp-toggle-sidebar-button .icon span:nth-child(1),.vp-theme-container.sidebar-open .vp-toggle-sidebar-button .icon span:nth-child(3){transform-origin:center}.vp-navbar{--navbar-line-height: calc( var(--navbar-height) - 2 * var(--navbar-padding-v) );position:fixed;top:0;right:0;left:0;z-index:20;box-sizing:border-box;height:var(--navbar-height);padding:var(--navbar-padding-v) var(--navbar-padding-h);border-bottom:1px solid var(--c-border);background-color:var(--c-bg-navbar);line-height:var(--navbar-line-height);transition:background-color var(--t-color),border-color var(--t-color)}@media screen and (max-width: 719px){.vp-navbar{padding-left:4rem}}.vp-navbar-items-wrapper{position:absolute;top:var(--navbar-padding-v);right:var(--navbar-padding-h);display:flex;box-sizing:border-box;height:var(--navbar-line-height);padding-left:var(--navbar-padding-h);font-size:.9rem;white-space:nowrap}.vp-page-meta{max-width:var(--content-width);margin:0 auto;padding:.75rem 2.5rem;display:flex;flex-wrap:wrap;justify-content:space-between;overflow:auto}@media (max-width: 959px){.vp-page-meta{padding:2rem}}@media (max-width: 419px){.vp-page-meta{padding:1.5rem}}@media print{.vp-page-meta{margin:0!important;padding-right:0!important;padding-left:0!important}}@media (max-width: 719px){.vp-page-meta{display:block}}.vp-page-meta .vp-meta-item{flex-grow:1}.vp-page-meta .vp-meta-item .vp-meta-label{font-weight:500}.vp-page-meta .vp-meta-item .vp-meta-label:not(a){color:var(--c-text-lighter)}.vp-page-meta .vp-meta-item .vp-meta-info{color:var(--c-text-quote);font-weight:400}.vp-page-meta .git-info{text-align:end}.vp-page-meta .edit-link{margin-top:.25rem;margin-right:.5rem;margin-bottom:.25rem;font-size:14px}@media print{.vp-page-meta .edit-link{display:none}}.vp-page-meta .edit-link .edit-icon{position:relative;bottom:-.125em;width:1em;height:1em;margin-right:.25em}.vp-page-meta .last-updated,.vp-page-meta .contributors{margin-top:.25rem;margin-bottom:.25rem;font-size:14px}@media (max-width: 719px){.vp-page-meta .last-updated,.vp-page-meta .contributors{font-size:13px;text-align:start}}.vp-page-nav{display:flex;flex-wrap:wrap;max-width:var(--content-width, 740px);min-height:2rem;margin-top:0;margin-right:auto;margin-left:auto;padding:1rem 2rem 0;border-top:1px solid var(--c-border);transition:border-top var(--t-color)}@media (max-width: 959px){.vp-page-nav{padding-right:1rem;padding-left:1rem}}@media print{.vp-page-nav{display:none}}.vp-page-nav .route-link{display:inline-block;flex-grow:1;margin:.25rem;padding:.25rem .5rem;border:1px solid var(--c-border);border-radius:.25rem}.vp-page-nav .route-link:hover{background:var(--c-bg-light)}.vp-page-nav .route-link .hint{color:var(--c-text-quote);font-size:.875rem;line-height:2}.vp-page-nav .prev{text-align:start}.vp-page-nav .next{text-align:end}.vp-page{display:block;padding-top:var(--navbar-height);padding-bottom:2rem;padding-left:var(--sidebar-width)}@media (max-width: 959px){.vp-page{padding-left:var(--sidebar-width-mobile)}}@media (max-width: 719px){.vp-page{padding-left:0}}.vp-page .theme-default-content{max-width:var(--content-width);margin:0 auto;padding:2rem 2.5rem;padding-top:0}@media (max-width: 959px){.vp-page .theme-default-content{padding:2rem}}@media (max-width: 419px){.vp-page .theme-default-content{padding:1.5rem}}.vp-sidebar-item{border-left:.25rem solid transparent;color:var(--c-text);cursor:default}.vp-sidebar-item:focus-visible{outline-width:1px;outline-offset:-1px}.vp-sidebar-item.vp-sidebar-heading{box-sizing:border-box;width:100%;margin:0;padding:.35rem 1.5rem .35rem 1.25rem;font-weight:700;font-size:1.1em;transition:color .15s ease}.vp-sidebar-item.vp-sidebar-heading+.vp-sidebar-children{overflow:hidden;transition:height .1s ease-out;margin-bottom:.75rem}.vp-sidebar-item.collapsible{cursor:pointer}.vp-sidebar-item:not(.vp-sidebar-heading){display:inline-block;box-sizing:border-box;width:100%;margin:0;padding:.35rem 1rem .35rem 2rem;font-weight:400;font-size:1em;line-height:1.4}.vp-sidebar-item:not(.vp-sidebar-heading)+.vp-sidebar-children{padding-left:1rem;font-size:.95em}.vp-sidebar-children .vp-sidebar-children .vp-sidebar-item:not(.vp-sidebar-heading){padding:.25rem 1rem .25rem 1.75rem}.vp-sidebar-children .vp-sidebar-children .vp-sidebar-item:not(.vp-sidebar-heading).active{border-left-color:transparent;font-weight:500}a.vp-sidebar-heading+.vp-sidebar-children .vp-sidebar-item:not(.vp-sidebar-heading).active{border-left-color:transparent}.vp-sidebar-item.active:not(p.vp-sidebar-heading){border-left-color:var(--c-text-accent);color:var(--c-text-accent);font-weight:600}a.vp-sidebar-item{cursor:pointer}a.vp-sidebar-item:hover{color:var(--c-text-accent)}.vp-sidebar-items{margin:0;padding:1.5rem 0;list-style-type:none}@media (max-width: 719px){.vp-sidebar-items{padding:1rem 0}}.vp-sidebar-items ul{margin:0;padding:0;list-style-type:none}.vp-sidebar-items a{display:inline-block}.vp-sidebar{position:fixed;top:var(--navbar-height);bottom:0;left:0;z-index:10;overflow-y:auto;box-sizing:border-box;width:var(--sidebar-width);margin:0;border-right:1px solid var(--c-border);background-color:var(--c-bg-sidebar);font-size:16px;transition:transform var(--t-transform),background-color var(--t-color),border-color var(--t-color);scrollbar-color:var(--c-brand) var(--c-border);scrollbar-width:thin}@media (max-width: 959px){.vp-sidebar{width:var(--sidebar-width-mobile);font-size:15px}}@media (max-width: 719px){.vp-sidebar{top:0;padding-top:var(--navbar-height);transform:translate(-100%)}}.vp-sidebar::-webkit-scrollbar{width:7px}.vp-sidebar::-webkit-scrollbar-track{background-color:var(--c-border)}.vp-sidebar::-webkit-scrollbar-thumb{background-color:var(--c-brand)}.vp-sidebar .vp-navbar-items{display:none;padding:.5rem 0 .75rem;border-bottom:1px solid var(--c-border);transition:border-color var(--t-color)}@media (max-width: 719px){.vp-sidebar .vp-navbar-items{display:block}.vp-sidebar .vp-navbar-items .vp-navbar-dropdown-item a.route-link-active:after{top:calc(1rem - 2px)}}.vp-sidebar .vp-navbar-items ul{margin:0;padding:0;list-style-type:none}.vp-sidebar .vp-navbar-items a{font-weight:600}.vp-sidebar .vp-navbar-item{display:block;padding:.5rem 0 .5rem 1.5rem;font-size:1.1em;line-height:1.25rem}.vp-sidebar-mask{position:fixed;top:0;left:0;z-index:9;display:none;width:100vw;height:100vh}.vp-theme-container.no-navbar .vp-sidebar{top:0}@media (max-width: 719px){.vp-theme-container.no-navbar .vp-sidebar{padding-top:0}}.vp-theme-container.no-navbar .vp-page{padding-top:0}.vp-theme-container.no-navbar .theme-default-content h1,.vp-theme-container.no-navbar .theme-default-content h2,.vp-theme-container.no-navbar .theme-default-content h3,.vp-theme-container.no-navbar .theme-default-content h4,.vp-theme-container.no-navbar .theme-default-content h5,.vp-theme-container.no-navbar .theme-default-content h6{margin-top:1.5rem;padding-top:0}.vp-theme-container.no-sidebar .vp-sidebar{display:none}@media (max-width: 719px){.vp-theme-container.no-sidebar .vp-sidebar{display:block}}.vp-theme-container.no-sidebar .vp-page{padding-left:0}@media (max-width: 719px){.vp-theme-container.sidebar-open .vp-sidebar{transform:translate(0)}.vp-theme-container.sidebar-open .vp-sidebar-mask{display:block}}.fade-slide-y-enter-active{transition:all .2s ease}.fade-slide-y-leave-active{transition:all .2s cubic-bezier(1,.5,.8,1)}.fade-slide-y-enter-from,.fade-slide-y-leave-to{opacity:0;transform:translateY(10px)}.vp-theme-container[data-v-303656ba]{max-width:740px;margin:0 auto;padding:2rem 2.5rem}@media (max-width: 959px){.vp-theme-container[data-v-303656ba]{padding:2rem}}html,body{background:#fff}html{font-size:16px;font-display:optional;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-tap-highlight-color:transparent}@media print{html{font-size:12pt}}body{min-height:100vh;margin:0;padding:0;color:#2c3e50;font-synthesis:style}a{color:#3eaf7c;font-weight:500;text-decoration:none;overflow-wrap:break-word}kbd{display:inline-block;min-width:1em;margin-inline:.125rem;padding:.25em;border:1px solid #eee;border-radius:.25em;box-shadow:1px 1px 4px #00000026;line-height:1;letter-spacing:-.1em;text-align:center}:not(pre)>code{margin:0;padding:3px 6px;border-radius:4px;background:#7f7f7f1f;font-size:.875em;overflow-wrap:break-word}table code{padding:.1rem .4rem}p a code{color:#3eaf7c;font-weight:400}strong{font-weight:600}h1,h2,h3,h4,h5,h6{font-weight:600;line-height:1.25;overflow-wrap:break-word}h1:focus-visible,h2:focus-visible,h3:focus-visible,h4:focus-visible,h5:focus-visible,h6:focus-visible{outline:none}h1{font-size:2rem}h2{padding-bottom:.3rem;border-bottom:1px solid #eaecef;font-size:1.65rem}h3{font-size:1.35rem}h4{font-size:1.15rem}h5{font-size:1.05rem}h6{font-size:1rem}a.header-anchor{position:relative;color:inherit;text-decoration:none}a.header-anchor:hover:before{content:"¶";position:absolute;top:.4167em;left:-.75em;color:var(--c-brand);font-size:.75em}a.header-anchor:focus-visible{outline:none}a.header-anchor:focus-visible:before{content:"¶";position:absolute;left:-.75em;color:var(--c-brand);outline:auto}p,ul,ol{line-height:1.6;overflow-wrap:break-word}@media print{p,ul,ol{line-height:1.5}}ul,ol{padding-inline-start:1.2em}blockquote{margin:1rem 0;padding:.25rem 0 .25rem 1rem;border-inline-start:.2rem solid #ddd;color:#666;font-size:1rem;overflow-wrap:break-word}blockquote>p{margin:0}hr{border:0;border-top:1px solid #eaecef}table{display:block;overflow-x:auto;margin:1rem 0;border-collapse:collapse}tr:nth-child(odd){background:#f6f8fa}th,td{padding:.6em 1em;border:1px solid #dfe2e5}pre{text-align:left;direction:ltr;white-space:pre;word-spacing:normal;word-wrap:normal;word-break:normal;overflow-wrap:unset;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;hyphens:none}@media print{pre{white-space:pre-wrap}}pre code{padding:0;border-radius:0}@page{margin:2cm;font-size:12pt;size:a4}@media print{*,:after,:before{box-shadow:none!important;text-shadow:none!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}a{color:inherit;font-weight:inherit!important;font-size:inherit!important;text-decoration:underline}a.header-anchor{text-decoration:none}abbr[title]:after{content:" (" attr(title) ")"}pre{border:1px solid #eee;white-space:pre-wrap!important}pre>code{white-space:pre-wrap!important}blockquote{border-inline-start:.2rem solid #ddd;color:inherit}blockquote,pre{orphans:5;widows:5}img,tr,canvas{page-break-inside:avoid}}@media (prefers-reduced-motion: reduce){*,:before,:after{background-attachment:initial!important;scroll-behavior:auto!important;transition-delay:0s!important;transition-duration:0s!important;animation-duration:1ms!important;animation-delay:-1ms!important;animation-iteration-count:1!important}}:root{--c-brand: #3eaf7c;--c-brand-light: #4abf8a;--c-bg: #fff;--c-bg-light: #f3f4f5;--c-bg-lighter: #eee;--c-bg-dark: #ebebec;--c-bg-darker: #e6e6e6;--c-bg-navbar: var(--c-bg);--c-bg-sidebar: var(--c-bg);--c-bg-arrow: #ccc;--c-text: #2c3e50;--c-text-accent: var(--c-brand);--c-text-light: #3a5169;--c-text-lighter: #4e6e8e;--c-text-lightest: #6a8bad;--c-text-quote: #999;--c-border: #eaecef;--c-border-dark: #dfe2e5;--c-tip: #42b983;--c-tip-bg: var(--c-bg-light);--c-tip-title: var(--c-text);--c-tip-text: var(--c-text);--c-tip-text-accent: var(--c-text-accent);--c-warning: #ffc310;--c-warning-bg: #fffae3;--c-warning-bg-light: #fff3ba;--c-warning-bg-lighter: #fff0b0;--c-warning-border-dark: #f7dc91;--c-warning-details-bg: #fff5ca;--c-warning-title: #f1b300;--c-warning-text: #746000;--c-warning-text-accent: #edb100;--c-warning-text-light: #c1971c;--c-warning-text-quote: #ccab49;--c-danger: #f11e37;--c-danger-bg: #ffe0e0;--c-danger-bg-light: #ffcfde;--c-danger-bg-lighter: #ffc9c9;--c-danger-border-dark: #f1abab;--c-danger-details-bg: #ffd4d4;--c-danger-title: #ed1e2c;--c-danger-text: #600;--c-danger-text-accent: #bd1a1a;--c-danger-text-light: #b5474d;--c-danger-text-quote: #c15b5b;--c-details-bg: #eee;--c-badge-tip: var(--c-tip);--c-badge-warning: #ecc808;--c-badge-warning-text: var(--c-bg);--c-badge-danger: #dc2626;--c-badge-danger-text: var(--c-bg);--c-code-group-tab-title: rgb(255 255 255 / 90%);--c-code-group-tab-bg: var(--code-bg-color);--c-code-group-tab-outline: var(var(--c-code-group-tab-title));--c-code-group-tab-active-border: var(--c-brand);--t-color: .3s ease;--t-transform: .3s ease;--font-family: -apple-system, "BlinkMacSystemFont", "Segoe UI", roboto, oxygen, ubuntu, cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;--navbar-height: 3.6rem;--navbar-padding-v: .7rem;--navbar-padding-h: 1.5rem;--sidebar-width: 20rem;--sidebar-width-mobile: calc(var(--sidebar-width) * .82);--content-width: 740px;--homepage-width: 960px;--external-link-icon: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M0 0h24v24H0V0z' fill='none' /%3E%3Cpath d='M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z' /%3E%3C/svg%3E");--external-link-icon-color: var(--c-text-quote)}.vp-back-to-top-button{--back-to-top-color: var(--c-brand);--back-to-top-color-hover: var(--c-brand-light);--back-to-top-bg-color: var(--c-bg)}.vp-catalog-wrapper{--catalog-bg-color: var(--c-bg);--catalog-bg-secondary-color: var(--c-bg-dark);--catalog-border-color: var(--c-border);--catalog-active-color: var(--c-brand);--catalog-hover-color: var(--c-brand-light)}.waline-wrapper{--waline-bg-color: var(--c-bg);--waline-bg-color-light: var(--c-bg-light);--waline-text-color: var(--c-color);--waline-border: 1px solid var(--c-border);--waline-border-color: var(--c-border);--waline-theme-color: var(--c-brand);--waline-active-color: var(--c-brand-light)}.DocSearch{--docsearch-primary-color: var(--c-brand);--docsearch-text-color: var(--c-text);--docsearch-highlight-color: var(--c-brand);--docsearch-muted-color: var(--c-text-quote);--docsearch-container-background: rgb(9 10 17 / 80%);--docsearch-modal-background: var(--c-bg-light);--docsearch-searchbox-background: var(--c-bg-lighter);--docsearch-searchbox-focus-background: var(--c-bg);--docsearch-searchbox-shadow: inset 0 0 0 2px var(--c-brand);--docsearch-hit-color: var(--c-text-light);--docsearch-hit-active-color: var(--c-bg);--docsearch-hit-background: var(--c-bg);--docsearch-hit-shadow: 0 1px 3px 0 var(--c-border-dark);--docsearch-footer-background: var(--c-bg)}.medium-zoom-overlay{--medium-zoom-bg-color: var(--c-bg)}.vp-notice-wrapper{--notice-color: var(--c-text);--notice-bg-color: var(--c-bg);--notice-primary-color: var(--c-brand);--notice-primary-text-color: var(--c-bg);--notice-primary-hover-color: var(--c-brand-light);--notice-button-color: var(--c-bg-light);--notice-button-hover-color: var(--c-bg-lighter)}#nprogress{--nprogress-color: var(--c-brand)}body{--photo-swipe-bullet: var(--c-bg);--photo-swipe-bullet-active: var(--c-brand);--pwa-text-color: var(--c-text);--pwa-bg-color: var(--c-bg);--pwa-border-color: var(--c-brand);--pwa-btn-text-color: var(--c-bg);--pwa-btn-bg-color: var(--c-brand);--pwa-btn-hover-bg-color: var(--c-brand-light)}.redirect-modal-mask{--redirect-bg-color: var(--c-bg);--redirect-bg-color-light: var(--c-bg-light);--redirect-bg-color-lighter: var(--c-bg-lighter);--redirect-text-color: var(--c-text);--redirect-primary-color: var(--c-brand);--redirect-primary-hover-color: var(--c-brand-light);--redirect-primary-text-color: var(--c-bg)}.search-box{--search-bg-color: var(--c-bg);--search-accent-color: var(--c-brand);--search-text-color: var(--c-text);--search-border-color: var(--c-border);--search-item-text-color: var(--c-text-lighter);--search-item-focus-bg-color: var(--c-bg-light)}html.dark{--c-brand: #3aa675;--c-brand-light: #349469;--c-bg: #22272e;--c-bg-light: #2b313a;--c-bg-lighter: #262c34;--c-bg-dark: #343b44;--c-bg-darker: #37404c;--c-text: #adbac7;--c-text-light: #96a7b7;--c-text-lighter: #8b9eb0;--c-text-lightest: #8094a8;--c-border: #3e4c5a;--c-border-dark: #34404c;--c-tip: #318a62;--c-warning: #e0ad15;--c-warning-bg: #2d2f2d;--c-warning-bg-light: #423e2a;--c-warning-bg-lighter: #44442f;--c-warning-border-dark: #957c35;--c-warning-details-bg: #39392d;--c-warning-title: #fdca31;--c-warning-text: #d8d96d;--c-warning-text-accent: #ffbf00;--c-warning-text-light: #ddb84b;--c-warning-text-quote: #ccab49;--c-danger: #fc1e38;--c-danger-bg: #39232c;--c-danger-bg-light: #4b2b35;--c-danger-bg-lighter: #553040;--c-danger-border-dark: #a25151;--c-danger-details-bg: #482936;--c-danger-title: #fc2d3b;--c-danger-text: #ea9ca0;--c-danger-text-accent: #fd3636;--c-danger-text-light: #d9777c;--c-danger-text-quote: #d56b6b;--c-details-bg: #323843;--c-badge-warning: var(--c-warning);--c-badge-warning-text: #3c2e05;--c-badge-danger: var(--c-danger);--c-badge-danger-text: #401416}html.dark .DocSearch{--docsearch-logo-color: var(--c-text);--docsearch-modal-shadow: inset 1px 1px 0 0 #2c2e40, 0 3px 8px 0 #000309;--docsearch-key-shadow: inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d, 0 2px 2px 0 rgb(3 4 9 / 30%);--docsearch-key-gradient: linear-gradient(-225deg, #444950, #1c1e21);--docsearch-footer-shadow: inset 0 1px 0 0 rgb(73 76 106 / 50%), 0 -4px 8px 0 rgb(0 0 0 / 20%)}html.dark body{--pwa-shadow-color: rgb(0 0 0 / 30%);--pwa-content-color: #ccc;--pwa-content-light-color: #999}html,body{background-color:var(--c-bg);transition:background-color var(--t-color)}html.dark{color-scheme:dark}body{color:var(--c-text);font-size:1rem;font-family:var(--font-family)}h2{border-color:var(--c-border);transition:border-color var(--t-color)}a{color:var(--c-text-accent)}code{background-color:var(--c-bg-light);color:var(--c-text-lighter);font-family:var(--code-font-family);transition:background-color var(--t-color)}p a code{color:var(--c-text-accent)}kbd{border-color:var(--c-border-dark);transition:border-color var(--t-color)}blockquote{border-color:var(--c-border-dark);color:var(--c-text-quote);transition:border-color var(--t-color)}hr{border-color:var(--c-border);transition:border-color var(--t-color)}table{transition:border-color var(--t-color)}tr:nth-child(odd){background-color:var(--c-bg-light);transition:background-color var(--t-color)}tr:nth-child(odd) code{background-color:var(--c-bg-dark)}th,td{border-color:var(--c-border-dark);transition:border-color var(--t-color)}.theme-default-content h1,.theme-default-content h2,.theme-default-content h3,.theme-default-content h4,.theme-default-content h5,.theme-default-content h6{margin-top:calc(.5rem - var(--navbar-height));margin-bottom:0;padding-top:calc(1rem + var(--navbar-height))}.theme-default-content h1:first-child,.theme-default-content h2:first-child,.theme-default-content h3:first-child,.theme-default-content h4:first-child,.theme-default-content h5:first-child,.theme-default-content h6:first-child{margin-bottom:1rem}.theme-default-content h1:first-child+p,.theme-default-content h1:first-child+pre,.theme-default-content h1:first-child+.custom-container,.theme-default-content h2:first-child+p,.theme-default-content h2:first-child+pre,.theme-default-content h2:first-child+.custom-container,.theme-default-content h3:first-child+p,.theme-default-content h3:first-child+pre,.theme-default-content h3:first-child+.custom-container,.theme-default-content h4:first-child+p,.theme-default-content h4:first-child+pre,.theme-default-content h4:first-child+.custom-container,.theme-default-content h5:first-child+p,.theme-default-content h5:first-child+pre,.theme-default-content h5:first-child+.custom-container,.theme-default-content h6:first-child+p,.theme-default-content h6:first-child+pre,.theme-default-content h6:first-child+.custom-container{margin-top:2rem}@media (max-width: 419px){.theme-default-content h1{font-size:1.9rem}}.theme-default-content a:not(.header-anchor){text-decoration:underline}.theme-default-content img{max-width:100%}div[class*=language-]{margin:.75rem 0;transition:background-color var(--t-color),color var(--t-color)}@media (max-width: 419px){div[class*=language-]{--code-border-radius: 0;margin:.75rem -1.5rem}}div[class*=language-] .line.diff,div[class*=language-] .line.highlighted{transition:background-color var(--t-color)}.custom-container{transition:color var(--t-color),border-color var(--t-color),background-color var(--t-color)}.custom-container .custom-container-title{font-weight:600}.custom-container .custom-container-title:not(:only-child){margin-bottom:-.4rem}.custom-container.tip,.custom-container.warning,.custom-container.danger{margin:1rem 0;padding:.1rem 1.5rem;border-left-width:.5rem;border-left-style:solid}.custom-container.tip{border-color:var(--c-tip);background-color:var(--c-tip-bg);color:var(--c-tip-text)}.custom-container.tip .custom-container-title{color:var(--c-tip-title)}.custom-container.tip a{color:var(--c-tip-text-accent)}.custom-container.tip code{background-color:var(--c-bg-dark)}.custom-container.warning{--external-link-icon-color: var(--c-warning-text-quote);border-color:var(--c-warning);background-color:var(--c-warning-bg);color:var(--c-warning-text)}.custom-container.warning .custom-container-title{color:var(--c-warning-title)}.custom-container.warning a{color:var(--c-warning-text-accent)}.custom-container.warning blockquote{border-left-color:var(--c-warning-border-dark);color:var(--c-warning-text-quote)}.custom-container.warning code{background-color:var(--c-warning-bg-light);color:var(--c-warning-text-light)}.custom-container.warning details{background-color:var(--c-warning-details-bg)}.custom-container.warning details code{background-color:var(--c-warning-bg-lighter)}.custom-container.danger{--external-link-icon-color: var(--c-danger-text-quote);border-color:var(--c-danger);background-color:var(--c-danger-bg);color:var(--c-danger-text)}.custom-container.danger .custom-container-title{color:var(--c-danger-title)}.custom-container.danger a{color:var(--c-danger-text-accent)}.custom-container.danger blockquote{border-left-color:var(--c-danger-border-dark);color:var(--c-danger-text-quote)}.custom-container.danger code{background-color:var(--c-danger-bg-light);color:var(--c-danger-text-light)}.custom-container.danger details{background-color:var(--c-danger-details-bg)}.custom-container.danger details code{background-color:var(--c-danger-bg-lighter)}.custom-container.details{position:relative;display:block;margin:1.6em 0;padding:1.6em;border-radius:2px;background-color:var(--c-details-bg)}.custom-container.details code{background-color:var(--c-bg-darker)}.custom-container.details h4{margin-top:0}.custom-container.details figure:last-child,.custom-container.details p:last-child{margin-bottom:0;padding-bottom:0}.custom-container.details summary{outline:none;cursor:pointer}.table-of-contents .vp-badge{vertical-align:middle}.arrow{display:inline-block;vertical-align:middle;width:1em;height:1em;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");background-position:center;background-repeat:no-repeat;line-height:normal;transition:all .3s}html.dark .arrow{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E")}.arrow.down{transform:rotate(180deg)}.arrow.right{transform:rotate(90deg)}.arrow.left{transform:rotate(-90deg)}.vp-external-link-icon:after{content:"";display:inline-block;flex-shrink:0;width:11px;height:11px;margin-top:-1px;margin-left:4px;background:var(--external-link-icon-color);-webkit-mask-image:var(--external-link-icon);mask-image:var(--external-link-icon)}.external-link-icon .external-link:after{content:"";display:inline-block;flex-shrink:0;width:11px;height:11px;margin-top:-1px;margin-left:4px;background:var(--external-link-icon-color);-webkit-mask-image:var(--external-link-icon);mask-image:var(--external-link-icon)}.external-link-icon .theme-default-content a[href*="://"]:not(.no-external-link-icon):after,.external-link-icon .theme-default-content a[target=_blank]:not(.no-external-link-icon):after{content:"";display:inline-block;flex-shrink:0;width:11px;height:11px;margin-top:-1px;margin-left:4px;background:var(--external-link-icon-color);-webkit-mask-image:var(--external-link-icon);mask-image:var(--external-link-icon)}@media screen and (max-width: 719px){.vp-hide-mobile{display:none}}#vp-comment{max-width:var(--content-width);margin:0 auto;padding:2rem 2.5rem}@media (max-width: 959px){#vp-comment{padding:2rem}}@media (max-width: 419px){#vp-comment{padding:1.5rem}}.vp-navbar .DocSearch{transition:background-color var(--t-color)}.vp-navbar .search-box{vertical-align:top;flex:0 0 auto}:root{--c-brand: #920097;--c-brand-light: #faaffd}html.dark{--c-brand: #920097;--c-brand-light: #faaffd}iframe{width:100%}:root{--search-bg-color: #fff;--search-accent-color: #3eaf7c;--search-text-color: #2c3e50;--search-border-color: #eaecef;--search-item-text-color: #5d81a5;--search-item-focus-bg-color: #f3f4f5;--search-input-width: 8rem;--search-result-width: 20rem}.search-box{position:relative;display:inline-block;margin-left:1rem}@media print{.search-box{display:none}}.search-box input{display:inline-block;width:var(--search-input-width);height:2rem;padding:0 .5rem 0 2rem;border:1px solid var(--search-border-color);border-radius:2rem;background:var(--search-bg-color) url("data:image/svg+xml,%3c?xml%20version='1.0'%20encoding='UTF-8'?%3e%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='12'%20height='13'%3e%3cg%20stroke-width='2'%20stroke='%23aaa'%20fill='none'%3e%3cpath%20d='M11.29%2011.71l-4-4'/%3e%3ccircle%20cx='5'%20cy='5'%20r='4'/%3e%3c/g%3e%3c/svg%3e") .6rem .5rem no-repeat;background-size:1rem;color:var(--search-text-color);outline:none;font-size:.9rem;line-height:2rem;cursor:text;-webkit-appearance:none;-moz-appearance:none;appearance:none;transition:all ease .3s}@media (max-width: 719px){.search-box input{position:relative;width:0;border-color:transparent;cursor:pointer}}.search-box input:focus{border-color:var(--search-accent-color);cursor:auto}@media (max-width: 719px){.search-box input:focus{left:0;width:10rem;cursor:text}}@media (max-width: 419px){.search-box input:focus{width:8rem}}.search-box .suggestions{position:absolute;top:2rem;right:0;width:var(--search-result-width);padding:.4rem;border:1px solid var(--search-border-color);border-radius:6px;background:var(--search-bg-color);list-style-type:none}@media (max-width: 419px){.search-box .suggestions{right:-.5rem;width:calc(100vw - 4rem)}}.search-box .suggestion{padding:.4rem .6rem;border-radius:4px;line-height:1.4;cursor:pointer}.search-box .suggestion a{color:var(--search-item-text-color);white-space:normal}.search-box .suggestion.focus{background-color:var(--search-item-focus-bg-color)}.search-box .suggestion.focus a{color:var(--search-accent-color)}.search-box .suggestion .page-title{font-weight:600}.search-box .suggestion .page-header{margin-left:.25em;font-size:.9em}@keyframes rotate{0%{transform:rotate(0)}50%{transform:rotate(360deg)}to{transform:rotate(360deg)}}.popup-enter-active,.popup-leave-active{transition:opacity .3s,transform .3s}.popup-enter-from,.popup-leave-to{opacity:0;transform:translateY(50%) scale(.5)}.sw-hint-popup,.sw-update-popup{position:fixed;inset-inline-end:1rem;bottom:1rem;z-index:var(--pwa-z-index);padding:.5rem .75rem;border-width:0;border-radius:.5rem;background:var(--pwa-bg-color);color:var(--pwa-color);box-shadow:0 2px 12px 0 var(--pwa-shadow-color);font-size:1rem;line-height:1.5;cursor:pointer}@media print{.sw-hint-popup,.sw-update-popup{display:none}}.sw-hint-popup .icon-wrapper,.sw-update-popup .icon-wrapper{display:inline-block;vertical-align:middle;width:1.5rem;height:1.5rem;margin-inline-start:.4rem;border-radius:.75rem;background:var(--pwa-btn-bg-color)}.sw-hint-popup .icon-wrapper:hover,.sw-update-popup .icon-wrapper:hover{background:var(--pwa-btn-hover-bg-color)}.sw-hint-popup .icon-wrapper svg,.sw-update-popup .icon-wrapper svg{width:1.2rem;height:1.2rem;margin:.15rem;color:var(--pwa-btn-text-color);animation:rotate 3s ease infinite}:root{--pwa-z-index: 10;--pwa-color: #2c3e50;--pwa-bg-color: #fff;--pwa-border-color: #3eaf7c;--pwa-shadow-color: rgb(0 0 0 / 15%);--pwa-btn-text-color: #fff;--pwa-btn-bg-color: #3eaf7c;--pwa-btn-hover-bg-color: #4abf8a;--pwa-content-color: #333;--pwa-content-light-color: #666}:root{--medium-zoom-z-index: 100;--medium-zoom-bg-color: #fff;--medium-zoom-opacity: 1}.medium-zoom-overlay{z-index:var(--medium-zoom-z-index);background-color:var(--medium-zoom-bg-color)!important}.medium-zoom-overlay~img{z-index:calc(var(--medium-zoom-z-index) + 1)}.medium-zoom--opened .medium-zoom-overlay{opacity:var(--medium-zoom-opacity)}html.dark{--box-shadow: #0f0e0d;--card-shadow: rgba(0, 0, 0, .3);--black: #fff;--grey-dark: #999;--grey-light: #666;--white: #000;--grey-darker: #bbb;--grey-lighter: #333;--grey14: #111}:root{--vp-bg: var(--c-bg, #fff);--vp-bgl: var(--c-bg-light, #f3f4f5);--vp-bglt: var(--c-bg-lighter, #eeeeee);--vp-c: var(--c-text, #2c3e50);--vp-cl: var(--c-text-light, #3a5169);--vp-clt: var(--c-text-lighter, #4e6e8e);--vp-brc: var(--c-border, #eaecef);--vp-brcd: var(--c-border-dark, #dfe2e5);--vp-tc: var(--c-brand, #3eaf7c);--vp-tcl: var(--c-brand-light, #4abf8a);--vp-ct: var(--t-color, .3s ease);--vp-tt: var(--t-transform, .3s ease);--box-shadow: #f0f1f2;--card-shadow: rgba(0, 0, 0, .15);--black: #000;--grey-dark: #666;--grey-light: #999;--white: #fff;--grey-darker: #333;--grey-lighter: #bbb;--grey14: #eee}.theme-default-content figure{position:relative;display:flex;flex-flow:column;width:auto;margin:1rem auto;text-align:center;transition:transform var(--vp-tt)}.theme-default-content figure>a[href*="://"]:after,.theme-default-content figure>a[target=_blank]:after{display:none!important}.theme-default-content figure img{overflow:hidden;margin:0 auto;border-radius:8px}.theme-default-content figure img[tabindex]:hover,.theme-default-content figure img[tabindex]:focus{box-shadow:2px 2px 10px 0 var(--card-shadow)}.theme-default-content figure figcaption{display:inline-block;margin:6px auto;font-size:.8rem}html:not(.dark) figure:has(img[data-mode=darkmode-only]),html:not(.dark) img[data-mode=darkmode-only]{display:none!important}html.dark figure:has(img[data-mode=lightmode-only]),html.dark img[data-mode=lightmode-only]{display:none!important} diff --git a/assets/wasm-build-conf-edit-CmamvRv7.png b/assets/wasm-build-conf-edit-CmamvRv7.png new file mode 100644 index 00000000..56d8bffd Binary files /dev/null and b/assets/wasm-build-conf-edit-CmamvRv7.png differ diff --git a/assets/wasm-run-configuration-x_w9-EC1.png b/assets/wasm-run-configuration-x_w9-EC1.png new file mode 100644 index 00000000..6ca168c9 Binary files /dev/null and b/assets/wasm-run-configuration-x_w9-EC1.png differ diff --git a/en/backend-development/index.html b/en/backend-development/index.html new file mode 100644 index 00000000..a9824bd5 --- /dev/null +++ b/en/backend-development/index.html @@ -0,0 +1,314 @@ + + + + + + + + +
- JS et Kotlin/JS
- mockmvc kotlin dsl
- spring-boot-kotlin tutorial
- Kotlin et JPA
- Spring Data JPA Comment utiliser les nulls Kotlin au lieu de Optional
📚 Backend development | Learning Kotlin + + + + + ++ + + diff --git a/en/front-development/index.html b/en/front-development/index.html new file mode 100644 index 00000000..a602d931 --- /dev/null +++ b/en/front-development/index.html @@ -0,0 +1,66 @@ + + + + + + + + +📚 Frontend development | Learning Kotlin + + + + + ++ + + diff --git a/en/index.html b/en/index.html new file mode 100644 index 00000000..4bdab18d --- /dev/null +++ b/en/index.html @@ -0,0 +1,43 @@ + + + + + + + + +Welcome | Learning Kotlin + + + + + ++ + + diff --git a/en/kotlin-features-advanced/index.html b/en/kotlin-features-advanced/index.html new file mode 100644 index 00000000..eab33afe --- /dev/null +++ b/en/kotlin-features-advanced/index.html @@ -0,0 +1,55 @@ + + + + + + + + +📚 Advanced and other Kotlin features | Learning Kotlin + + + + + ++ + + diff --git a/en/kotlin-features/index.html b/en/kotlin-features/index.html new file mode 100644 index 00000000..0687eee6 --- /dev/null +++ b/en/kotlin-features/index.html @@ -0,0 +1,49 @@ + + + + + + + + +📚 Kotlin language features | Learning Kotlin + + + + + ++ + + diff --git a/en/other-technologies/index.html b/en/other-technologies/index.html new file mode 100644 index 00000000..dac03b7c --- /dev/null +++ b/en/other-technologies/index.html @@ -0,0 +1,43 @@ + + + + + + + + +🛠 Let's make a cross-plaform app ! | Learning Kotlin + + + + + ++ + + diff --git a/en/presentation/index.html b/en/presentation/index.html new file mode 100644 index 00000000..bc5a58a8 --- /dev/null +++ b/en/presentation/index.html @@ -0,0 +1,43 @@ + + + + + + + + +🚀 Presentation of Kotlin | Learning Kotlin + + + + + ++ + + diff --git a/en/workshops/index.html b/en/workshops/index.html new file mode 100644 index 00000000..c06a0ef8 --- /dev/null +++ b/en/workshops/index.html @@ -0,0 +1,43 @@ + + + + + + + + +📅 Workshops | Learning Kotlin + + + + + ++ + + diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 00000000..0e4322cb Binary files /dev/null and b/favicon.ico differ diff --git a/fr/backend-development/index.html b/fr/backend-development/index.html new file mode 100644 index 00000000..b368f326 --- /dev/null +++ b/fr/backend-development/index.html @@ -0,0 +1,237 @@ + + + + + + + + +📚 Développement du backend | Apprendre Kotlin + + + + + ++ + + diff --git a/fr/front-development/index.html b/fr/front-development/index.html new file mode 100644 index 00000000..3a7a86dd --- /dev/null +++ b/fr/front-development/index.html @@ -0,0 +1,77 @@ + + + + + + + + +📚 Développement frontend | Apprendre Kotlin + + + + + ++ + + diff --git a/fr/index.html b/fr/index.html new file mode 100644 index 00000000..a9a6631f --- /dev/null +++ b/fr/index.html @@ -0,0 +1,43 @@ + + + + + + + + +Apprendre Kotlin + + + + + ++ + + diff --git a/fr/kotlin-features-advanced/index.html b/fr/kotlin-features-advanced/index.html new file mode 100644 index 00000000..c1f76c7c --- /dev/null +++ b/fr/kotlin-features-advanced/index.html @@ -0,0 +1,55 @@ + + + + + + + + +📚 Fonctionnalités avancées de Kotlin | Apprendre Kotlin + + + + + ++ + + diff --git a/fr/kotlin-features/index.html b/fr/kotlin-features/index.html new file mode 100644 index 00000000..6db13aef --- /dev/null +++ b/fr/kotlin-features/index.html @@ -0,0 +1,49 @@ + + + + + + + + +📚 Fonctionnalités du langage Kotlin | Apprendre Kotlin + + + + + ++ + + diff --git a/fr/other-technologies/index.html b/fr/other-technologies/index.html new file mode 100644 index 00000000..206757f3 --- /dev/null +++ b/fr/other-technologies/index.html @@ -0,0 +1,43 @@ + + + + + + + + +🛠 Construisons une app multiplateforme ! | Apprendre Kotlin + + + + + ++ + + diff --git a/fr/presentation/index.html b/fr/presentation/index.html new file mode 100644 index 00000000..c229116c --- /dev/null +++ b/fr/presentation/index.html @@ -0,0 +1,43 @@ + + + + + + + + +🚀 Présentation de Kotlin | Apprendre Kotlin + + + + + ++ + + diff --git a/fr/workshops/index.html b/fr/workshops/index.html new file mode 100644 index 00000000..4b29fc71 --- /dev/null +++ b/fr/workshops/index.html @@ -0,0 +1,43 @@ + + + + + + + + +📅 Workshops | Apprendre Kotlin + + + + + ++ + + diff --git a/icon-192x192.png b/icon-192x192.png new file mode 100644 index 00000000..45057eb6 Binary files /dev/null and b/icon-192x192.png differ diff --git a/icon-256x256.png b/icon-256x256.png new file mode 100644 index 00000000..2053f3a2 Binary files /dev/null and b/icon-256x256.png differ diff --git a/icon-384x384.png b/icon-384x384.png new file mode 100644 index 00000000..1447d286 Binary files /dev/null and b/icon-384x384.png differ diff --git a/icon-512x512.png b/icon-512x512.png new file mode 100644 index 00000000..6474ce29 Binary files /dev/null and b/icon-512x512.png differ diff --git a/index.html b/index.html new file mode 100644 index 00000000..2084a092 --- /dev/null +++ b/index.html @@ -0,0 +1,43 @@ + + + + + + + + +Welcome + + + + + ++ + + diff --git a/kotlin_logo.png b/kotlin_logo.png new file mode 100644 index 00000000..22d7a18d Binary files /dev/null and b/kotlin_logo.png differ diff --git a/logo_worldline.png b/logo_worldline.png new file mode 100644 index 00000000..6981642d Binary files /dev/null and b/logo_worldline.png differ diff --git a/manifest.webmanifest b/manifest.webmanifest new file mode 100644 index 00000000..95213a27 --- /dev/null +++ b/manifest.webmanifest @@ -0,0 +1 @@ +{"name":"Kotlin training","short_name":"Kotlin training","description":"A site built with vuepress","lang":"en-US","start_url":"/learning-kotlin/index.html","scope":"/learning-kotlin/","display":"standalone","theme_color":"#f635a4","background_color":"#e8d4f2","orientation":"portrait-primary","prefer_related_applications":false,"icons":[{"src":"icon-192x192.png","sizes":"192x192","type":"image/png"},{"src":"icon-256x256.png","sizes":"256x256","type":"image/png"},{"src":"icon-384x384.png","sizes":"384x384","type":"image/png"},{"src":"icon-512x512.png","sizes":"512x512","type":"image/png"}]} diff --git a/service-worker.js b/service-worker.js new file mode 100644 index 00000000..ce7c7a62 --- /dev/null +++ b/service-worker.js @@ -0,0 +1 @@ +if(!self.define){let e,s={};const i=(i,d)=>(i=new URL(i+".js",d).href,s[i]||new Promise((s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()})).then((()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e})));self.define=(d,a)=>{const r=e||("document"in self?document.currentScript.src:"")||location.href;if(s[r])return;let n={};const f=e=>i(e,r),t={module:{uri:r},exports:n,require:f};s[r]=Promise.all(d.map((e=>t[e]||f(e)))).then((e=>(a(...e),n)))}}define(["./workbox-1ab968a5"],(function(e){"use strict";self.addEventListener("message",(e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()})),e.clientsClaim(),e.precacheAndRoute([{url:"assets/404.html-CsHE7vwU.js",revision:"db6e1497086c03e3904329ee10d711ff"},{url:"assets/Androidmakers2023Kotlinshortlink-7XGfMd7N.svg",revision:"d6f2cc50c4311d68a92f93e0d12b284b"},{url:"assets/app-DAFlGqDu.js",revision:"e3b956ad4123d49b1e67fd77f91d3b14"},{url:"assets/index-DTEEl-sV.js",revision:"46a193641571106d3b7b43f9bc2a2735"},{url:"assets/index.html-09oChKdI.js",revision:"d8f21afbac58eed035646d2ded5b5c2e"},{url:"assets/index.html-7_H8SzH5.js",revision:"e24341f314ce7b4b6bf12e05b6a95140"},{url:"assets/index.html-BaJOs0HP.js",revision:"eae7b8b0d95643ba77c9dcb68dcc6085"},{url:"assets/index.html-Bht4bXi_.js",revision:"e3bc4511a1d9b7b80a1f95a25785679e"},{url:"assets/index.html-BnOna0wn.js",revision:"46ac770687bc337a834b91b979d85447"},{url:"assets/index.html-BRh3fjnU.js",revision:"e3cf5100fc426c15a32745254df4703c"},{url:"assets/index.html-Bxjju2_F.js",revision:"7a68870dbe60f7183cb7335506a38e17"},{url:"assets/index.html-C_RVdbGD.js",revision:"02845414fc82d5ff3daee3cbc1f2d67d"},{url:"assets/index.html-CDbrj19k.js",revision:"d64ad0087e67e04a33ef5813fa8e79de"},{url:"assets/index.html-CDLKHirq.js",revision:"39319cb3eac140887031fe86196e83fc"},{url:"assets/index.html-CeJwufaG.js",revision:"7015f0207e9ba63adbd7a199ff61a4cc"},{url:"assets/index.html-DfLOFdKV.js",revision:"649af9123e1efb227335ef7800e5ec5c"},{url:"assets/index.html-DkjVfAMI.js",revision:"cd5fd4556dd9da4ec3025b37c363c886"},{url:"assets/index.html-DOWQbO5d.js",revision:"38ec8983fedcaddec36d2d8ac9db2a74"},{url:"assets/index.html-DPrxNVl-.js",revision:"fcebca89a33d721bc6d620ff87dc2b0e"},{url:"assets/index.html-NwXwu3YI.js",revision:"25f4b0f5c5b5289111fa946e2e92c3a8"},{url:"assets/index.html-xZK9TAru.js",revision:"b56615fba454fbcc618a65f42df4204a"},{url:"assets/kmp_codelab-CiTPMWjt.js",revision:"6105fc434ff6ae7dfe6a0c1067618361"},{url:"assets/kotlin-decision-tree-4i7nEr1Z.svg",revision:"f257bed572584c782ad1e37c483ba4b6"},{url:"assets/kotlin-used-for-Bdlavnqs.js",revision:"92007e36f0c0d3b7b6265af81a141307"},{url:"assets/kotlin-wasm-webapp-R4_9ho9v.js",revision:"4be4df631b1ce26e600722ef2854991e"},{url:"assets/logo_worldline-dinT9MYm.js",revision:"aa655bfd05e03a82f745b7aa0a99c90f"},{url:"assets/qrcode-mixtit24-3_5F4s9g.svg",revision:"48a6a05fd99c023dee97a03e9dc7a2d1"},{url:"assets/qrcode-mixtit24-D-YZ7Myr.js",revision:"64a3c4be354788275f499ba1f9ba7fd3"},{url:"assets/style-CTaFAUa6.css",revision:"58eaf8e9cea8e79f14fcdaf6f10caa23"},{url:"404.html",revision:"a94b815676167966ba064938ec18a9ac"},{url:"en/backend-development/index.html",revision:"1138abf3a485a5b9b154ee9307195206"},{url:"en/front-development/index.html",revision:"b549f39d42a9ee4f9e17227b4730cd36"},{url:"en/index.html",revision:"f8c327f200677e7fb8ce264e85622b35"},{url:"en/kotlin-features-advanced/index.html",revision:"9330ec30a76ccfc1efd538f51b6ef330"},{url:"en/kotlin-features/index.html",revision:"2fc4d2d2f44efa7698bfa7872995bbb9"},{url:"en/other-technologies/index.html",revision:"4db2fe5fd2d01b5629cc84384b9f7144"},{url:"en/presentation/index.html",revision:"bfd0cebfcc975f337cccc4d8634ff54d"},{url:"en/workshops/index.html",revision:"0aca2938400a6360b72b5c14f1364d4a"},{url:"fr/backend-development/index.html",revision:"ee0e6ec4c1b398726ae83843b3766537"},{url:"fr/front-development/index.html",revision:"e0f6e6bc483dd4f8a5264d3b7c6adc8f"},{url:"fr/index.html",revision:"57ba2b849f38f78d2a8245ae7ce9e5b4"},{url:"fr/kotlin-features-advanced/index.html",revision:"37f7e3674db685be6a633821bf6fa576"},{url:"fr/kotlin-features/index.html",revision:"3b47eb82eb0da2b7adc5db4532babbf0"},{url:"fr/other-technologies/index.html",revision:"317958a3769a1696d3472a2f097d3292"},{url:"fr/presentation/index.html",revision:"fb5eb05d4c204c205d9737e90dab9897"},{url:"fr/workshops/index.html",revision:"348499cfbb02d7774c30ce3a84f4c512"},{url:"index.html",revision:"3c3fb7ccb3f1c6d470775182523cbfe7"}],{}),e.cleanupOutdatedCaches()})); diff --git a/workbox-1ab968a5.js b/workbox-1ab968a5.js new file mode 100644 index 00000000..b46d3f76 --- /dev/null +++ b/workbox-1ab968a5.js @@ -0,0 +1 @@ +define(["exports"],(function(t){"use strict";try{self["workbox:core:7.0.0"]&&_()}catch(t){}const e=(t,...e)=>{let s=t;return e.length>0&&(s+=` :: ${JSON.stringify(e)}`),s};class s extends Error{constructor(t,s){super(e(t,s)),this.name=t,this.details=s}}try{self["workbox:routing:7.0.0"]&&_()}catch(t){}const n=t=>t&&"object"==typeof t?t:{handle:t};class i{constructor(t,e,s="GET"){this.handler=n(e),this.match=t,this.method=s}setCatchHandler(t){this.catchHandler=n(t)}}class r extends i{constructor(t,e,s){super((({url:e})=>{const s=t.exec(e.href);if(s&&(e.origin===location.origin||0===s.index))return s.slice(1)}),e,s)}}class o{constructor(){this.t=new Map,this.i=new Map}get routes(){return this.t}addFetchListener(){self.addEventListener("fetch",(t=>{const{request:e}=t,s=this.handleRequest({request:e,event:t});s&&t.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(t=>{if(t.data&&"CACHE_URLS"===t.data.type){const{payload:e}=t.data,s=Promise.all(e.urlsToCache.map((e=>{"string"==typeof e&&(e=[e]);const s=new Request(...e);return this.handleRequest({request:s,event:t})})));t.waitUntil(s),t.ports&&t.ports[0]&&s.then((()=>t.ports[0].postMessage(!0)))}}))}handleRequest({request:t,event:e}){const s=new URL(t.url,location.href);if(!s.protocol.startsWith("http"))return;const n=s.origin===location.origin,{params:i,route:r}=this.findMatchingRoute({event:e,request:t,sameOrigin:n,url:s});let o=r&&r.handler;const c=t.method;if(!o&&this.i.has(c)&&(o=this.i.get(c)),!o)return;let a;try{a=o.handle({url:s,request:t,event:e,params:i})}catch(t){a=Promise.reject(t)}const h=r&&r.catchHandler;return a instanceof Promise&&(this.o||h)&&(a=a.catch((async n=>{if(h)try{return await h.handle({url:s,request:t,event:e,params:i})}catch(t){t instanceof Error&&(n=t)}if(this.o)return this.o.handle({url:s,request:t,event:e});throw n}))),a}findMatchingRoute({url:t,sameOrigin:e,request:s,event:n}){const i=this.t.get(s.method)||[];for(const r of i){let i;const o=r.match({url:t,sameOrigin:e,request:s,event:n});if(o)return i=o,(Array.isArray(i)&&0===i.length||o.constructor===Object&&0===Object.keys(o).length||"boolean"==typeof o)&&(i=void 0),{route:r,params:i}}return{}}setDefaultHandler(t,e="GET"){this.i.set(e,n(t))}setCatchHandler(t){this.o=n(t)}registerRoute(t){this.t.has(t.method)||this.t.set(t.method,[]),this.t.get(t.method).push(t)}unregisterRoute(t){if(!this.t.has(t.method))throw new s("unregister-route-but-not-found-with-method",{method:t.method});const e=this.t.get(t.method).indexOf(t);if(!(e>-1))throw new s("unregister-route-route-not-registered");this.t.get(t.method).splice(e,1)}}let c;const a=()=>(c||(c=new o,c.addFetchListener(),c.addCacheListener()),c);const h={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:"undefined"!=typeof registration?registration.scope:""},u=t=>[h.prefix,t,h.suffix].filter((t=>t&&t.length>0)).join("-"),l=t=>t||u(h.precache),f=t=>t||u(h.runtime);function w(t,e){const s=e();return t.waitUntil(s),s}try{self["workbox:precaching:7.0.0"]&&_()}catch(t){}function d(t){if(!t)throw new s("add-to-cache-list-unexpected-type",{entry:t});if("string"==typeof t){const e=new URL(t,location.href);return{cacheKey:e.href,url:e.href}}const{revision:e,url:n}=t;if(!n)throw new s("add-to-cache-list-unexpected-type",{entry:t});if(!e){const t=new URL(n,location.href);return{cacheKey:t.href,url:t.href}}const i=new URL(n,location.href),r=new URL(n,location.href);return i.searchParams.set("__WB_REVISION__",e),{cacheKey:i.href,url:r.href}}class p{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:t,state:e})=>{e&&(e.originalRequest=t)},this.cachedResponseWillBeUsed=async({event:t,state:e,cachedResponse:s})=>{if("install"===t.type&&e&&e.originalRequest&&e.originalRequest instanceof Request){const t=e.originalRequest.url;s?this.notUpdatedURLs.push(t):this.updatedURLs.push(t)}return s}}}class y{constructor({precacheController:t}){this.cacheKeyWillBeUsed=async({request:t,params:e})=>{const s=(null==e?void 0:e.cacheKey)||this.h.getCacheKeyForURL(t.url);return s?new Request(s,{headers:t.headers}):t},this.h=t}}let g;async function R(t,e){let n=null;if(t.url){n=new URL(t.url).origin}if(n!==self.location.origin)throw new s("cross-origin-copy-response",{origin:n});const i=t.clone(),r={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=e?e(r):r,c=function(){if(void 0===g){const t=new Response("");if("body"in t)try{new Response(t.body),g=!0}catch(t){g=!1}g=!1}return g}()?i.body:await i.blob();return new Response(c,o)}function m(t,e){const s=new URL(t);for(const t of e)s.searchParams.delete(t);return s.href}class v{constructor(){this.promise=new Promise(((t,e)=>{this.resolve=t,this.reject=e}))}}const q=new Set;try{self["workbox:strategies:7.0.0"]&&_()}catch(t){}function U(t){return"string"==typeof t?new Request(t):t}class L{constructor(t,e){this.u={},Object.assign(this,e),this.event=e.event,this.l=t,this.p=new v,this.R=[],this.m=[...t.plugins],this.v=new Map;for(const t of this.m)this.v.set(t,{});this.event.waitUntil(this.p.promise)}async fetch(t){const{event:e}=this;let n=U(t);if("navigate"===n.mode&&e instanceof FetchEvent&&e.preloadResponse){const t=await e.preloadResponse;if(t)return t}const i=this.hasCallback("fetchDidFail")?n.clone():null;try{for(const t of this.iterateCallbacks("requestWillFetch"))n=await t({request:n.clone(),event:e})}catch(t){if(t instanceof Error)throw new s("plugin-error-request-will-fetch",{thrownErrorMessage:t.message})}const r=n.clone();try{let t;t=await fetch(n,"navigate"===n.mode?void 0:this.l.fetchOptions);for(const s of this.iterateCallbacks("fetchDidSucceed"))t=await s({event:e,request:r,response:t});return t}catch(t){throw i&&await this.runCallbacks("fetchDidFail",{error:t,event:e,originalRequest:i.clone(),request:r.clone()}),t}}async fetchAndCachePut(t){const e=await this.fetch(t),s=e.clone();return this.waitUntil(this.cachePut(t,s)),e}async cacheMatch(t){const e=U(t);let s;const{cacheName:n,matchOptions:i}=this.l,r=await this.getCacheKey(e,"read"),o=Object.assign(Object.assign({},i),{cacheName:n});s=await caches.match(r,o);for(const t of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await t({cacheName:n,matchOptions:i,cachedResponse:s,request:r,event:this.event})||void 0;return s}async cachePut(t,e){const n=U(t);var i;await(i=0,new Promise((t=>setTimeout(t,i))));const r=await this.getCacheKey(n,"write");if(!e)throw new s("cache-put-with-no-response",{url:(o=r.url,new URL(String(o),location.href).href.replace(new RegExp(`^${location.origin}`),""))});var o;const c=await this.q(e);if(!c)return!1;const{cacheName:a,matchOptions:h}=this.l,u=await self.caches.open(a),l=this.hasCallback("cacheDidUpdate"),f=l?await async function(t,e,s,n){const i=m(e.url,s);if(e.url===i)return t.match(e,n);const r=Object.assign(Object.assign({},n),{ignoreSearch:!0}),o=await t.keys(e,r);for(const e of o)if(i===m(e.url,s))return t.match(e,n)}(u,r.clone(),["__WB_REVISION__"],h):null;try{await u.put(r,l?c.clone():c)}catch(t){if(t instanceof Error)throw"QuotaExceededError"===t.name&&await async function(){for(const t of q)await t()}(),t}for(const t of this.iterateCallbacks("cacheDidUpdate"))await t({cacheName:a,oldResponse:f,newResponse:c.clone(),request:r,event:this.event});return!0}async getCacheKey(t,e){const s=`${t.url} | ${e}`;if(!this.u[s]){let n=t;for(const t of this.iterateCallbacks("cacheKeyWillBeUsed"))n=U(await t({mode:e,request:n,event:this.event,params:this.params}));this.u[s]=n}return this.u[s]}hasCallback(t){for(const e of this.l.plugins)if(t in e)return!0;return!1}async runCallbacks(t,e){for(const s of this.iterateCallbacks(t))await s(e)}*iterateCallbacks(t){for(const e of this.l.plugins)if("function"==typeof e[t]){const s=this.v.get(e),n=n=>{const i=Object.assign(Object.assign({},n),{state:s});return e[t](i)};yield n}}waitUntil(t){return this.R.push(t),t}async doneWaiting(){let t;for(;t=this.R.shift();)await t}destroy(){this.p.resolve(null)}async q(t){let e=t,s=!1;for(const t of this.iterateCallbacks("cacheWillUpdate"))if(e=await t({request:this.request,response:e,event:this.event})||void 0,s=!0,!e)break;return s||e&&200!==e.status&&(e=void 0),e}}class b{constructor(t={}){this.cacheName=f(t.cacheName),this.plugins=t.plugins||[],this.fetchOptions=t.fetchOptions,this.matchOptions=t.matchOptions}handle(t){const[e]=this.handleAll(t);return e}handleAll(t){t instanceof FetchEvent&&(t={event:t,request:t.request});const e=t.event,s="string"==typeof t.request?new Request(t.request):t.request,n="params"in t?t.params:void 0,i=new L(this,{event:e,request:s,params:n}),r=this.U(i,s,e);return[r,this.L(r,i,s,e)]}async U(t,e,n){let i;await t.runCallbacks("handlerWillStart",{event:n,request:e});try{if(i=await this._(e,t),!i||"error"===i.type)throw new s("no-response",{url:e.url})}catch(s){if(s instanceof Error)for(const r of t.iterateCallbacks("handlerDidError"))if(i=await r({error:s,event:n,request:e}),i)break;if(!i)throw s}for(const s of t.iterateCallbacks("handlerWillRespond"))i=await s({event:n,request:e,response:i});return i}async L(t,e,s,n){let i,r;try{i=await t}catch(r){}try{await e.runCallbacks("handlerDidRespond",{event:n,request:s,response:i}),await e.doneWaiting()}catch(t){t instanceof Error&&(r=t)}if(await e.runCallbacks("handlerDidComplete",{event:n,request:s,response:i,error:r}),e.destroy(),r)throw r}}class C extends b{constructor(t={}){t.cacheName=l(t.cacheName),super(t),this.C=!1!==t.fallbackToNetwork,this.plugins.push(C.copyRedirectedCacheableResponsesPlugin)}async _(t,e){const s=await e.cacheMatch(t);return s||(e.event&&"install"===e.event.type?await this.O(t,e):await this.N(t,e))}async N(t,e){let n;const i=e.params||{};if(!this.C)throw new s("missing-precache-entry",{cacheName:this.cacheName,url:t.url});{const s=i.integrity,r=t.integrity,o=!r||r===s;n=await e.fetch(new Request(t,{integrity:"no-cors"!==t.mode?r||s:void 0})),s&&o&&"no-cors"!==t.mode&&(this.k(),await e.cachePut(t,n.clone()))}return n}async O(t,e){this.k();const n=await e.fetch(t);if(!await e.cachePut(t,n.clone()))throw new s("bad-precaching-response",{url:t.url,status:n.status});return n}k(){let t=null,e=0;for(const[s,n]of this.plugins.entries())n!==C.copyRedirectedCacheableResponsesPlugin&&(n===C.defaultPrecacheCacheabilityPlugin&&(t=s),n.cacheWillUpdate&&e++);0===e?this.plugins.push(C.defaultPrecacheCacheabilityPlugin):e>1&&null!==t&&this.plugins.splice(t,1)}}C.defaultPrecacheCacheabilityPlugin={cacheWillUpdate:async({response:t})=>!t||t.status>=400?null:t},C.copyRedirectedCacheableResponsesPlugin={cacheWillUpdate:async({response:t})=>t.redirected?await R(t):t};class E{constructor({cacheName:t,plugins:e=[],fallbackToNetwork:s=!0}={}){this.K=new Map,this.P=new Map,this.T=new Map,this.l=new C({cacheName:l(t),plugins:[...e,new y({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this.l}precache(t){this.addToCacheList(t),this.W||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this.W=!0)}addToCacheList(t){const e=[];for(const n of t){"string"==typeof n?e.push(n):n&&void 0===n.revision&&e.push(n.url);const{cacheKey:t,url:i}=d(n),r="string"!=typeof n&&n.revision?"reload":"default";if(this.K.has(i)&&this.K.get(i)!==t)throw new s("add-to-cache-list-conflicting-entries",{firstEntry:this.K.get(i),secondEntry:t});if("string"!=typeof n&&n.integrity){if(this.T.has(t)&&this.T.get(t)!==n.integrity)throw new s("add-to-cache-list-conflicting-integrities",{url:i});this.T.set(t,n.integrity)}if(this.K.set(i,t),this.P.set(i,r),e.length>0){const t=`Workbox is precaching URLs without revision info: ${e.join(", ")}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(t)}}}install(t){return w(t,(async()=>{const e=new p;this.strategy.plugins.push(e);for(const[e,s]of this.K){const n=this.T.get(s),i=this.P.get(e),r=new Request(e,{integrity:n,cache:i,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:s},request:r,event:t}))}const{updatedURLs:s,notUpdatedURLs:n}=e;return{updatedURLs:s,notUpdatedURLs:n}}))}activate(t){return w(t,(async()=>{const t=await self.caches.open(this.strategy.cacheName),e=await t.keys(),s=new Set(this.K.values()),n=[];for(const i of e)s.has(i.url)||(await t.delete(i),n.push(i.url));return{deletedURLs:n}}))}getURLsToCacheKeys(){return this.K}getCachedURLs(){return[...this.K.keys()]}getCacheKeyForURL(t){const e=new URL(t,location.href);return this.K.get(e.href)}getIntegrityForCacheKey(t){return this.T.get(t)}async matchPrecache(t){const e=t instanceof Request?t.url:t,s=this.getCacheKeyForURL(e);if(s){return(await self.caches.open(this.strategy.cacheName)).match(s)}}createHandlerBoundToURL(t){const e=this.getCacheKeyForURL(t);if(!e)throw new s("non-precached-url",{url:t});return s=>(s.request=new Request(t),s.params=Object.assign({cacheKey:e},s.params),this.strategy.handle(s))}}let O;const x=()=>(O||(O=new E),O);class N extends i{constructor(t,e){super((({request:s})=>{const n=t.getURLsToCacheKeys();for(const i of function*(t,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:s="index.html",cleanURLs:n=!0,urlManipulation:i}={}){const r=new URL(t,location.href);r.hash="",yield r.href;const o=function(t,e=[]){for(const s of[...t.searchParams.keys()])e.some((t=>t.test(s)))&&t.searchParams.delete(s);return t}(r,e);if(yield o.href,s&&o.pathname.endsWith("/")){const t=new URL(o.href);t.pathname+=s,yield t.href}if(n){const t=new URL(o.href);t.pathname+=".html",yield t.href}if(i){const t=i({url:r});for(const e of t)yield e.href}}(s.url,e)){const e=n.get(i);if(e){return{cacheKey:e,integrity:t.getIntegrityForCacheKey(e)}}}}),t.strategy)}}function k(t){const e=x();!function(t,e,n){let o;if("string"==typeof t){const s=new URL(t,location.href);o=new i((({url:t})=>t.href===s.href),e,n)}else if(t instanceof RegExp)o=new r(t,e,n);else if("function"==typeof t)o=new i(t,e,n);else{if(!(t instanceof i))throw new s("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});o=t}a().registerRoute(o)}(new N(e,t))}t.cleanupOutdatedCaches=function(){self.addEventListener("activate",(t=>{const e=l();t.waitUntil((async(t,e="-precache-")=>{const s=(await self.caches.keys()).filter((s=>s.includes(e)&&s.includes(self.registration.scope)&&s!==t));return await Promise.all(s.map((t=>self.caches.delete(t)))),s})(e).then((t=>{})))}))},t.clientsClaim=function(){self.addEventListener("activate",(()=>self.clients.claim()))},t.precacheAndRoute=function(t,e){!function(t){x().precache(t)}(t),k(e)}}));