From 5a88c8c85225b5ce523bea247fbfac9e51b84f0c Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Mon, 6 Apr 2020 18:58:41 +0100 Subject: [PATCH] [ACA-1743] extension settings (#1399) * upgrade tslib * initial settings skeleton * migrate language picker setting * support string parameters * remove process extensions workaround * update extensions schema * update docs * unit tests * fix unit test --- docs/extending/README.md | 1 + docs/extending/settings.md | 45 +++++++ docs/images/aca-settings-custom-group.png | Bin 0 -> 66901 bytes extension.schema.json | 52 ++++++++ package-lock.json | 6 +- package.json | 4 +- projects/aca-shared/rules/src/app.rules.ts | 3 +- .../store/src/actions/app.actions.ts | 23 ++-- .../store/src/selectors/app.selectors.ts | 5 - .../aca-shared/store/src/states/app.state.ts | 1 - src/app.config.json | 2 - src/app/app.component.spec.ts | 9 +- src/app/app.component.ts | 9 +- .../current-user.component.spec.ts | 6 +- .../settings/settings.component.html | 59 ++++---- .../settings/settings.component.spec.ts | 126 +++++++++++++++++- .../settings/settings.component.theme.scss | 5 + .../components/settings/settings.component.ts | 60 +++++---- src/app/extensions/extension.service.ts | 16 ++- src/app/store/initial-state.ts | 3 +- src/app/store/reducers/app.reducer.ts | 40 +++--- src/app/types.ts | 38 ++++++ src/assets/app.extensions.json | 39 ++++++ 23 files changed, 425 insertions(+), 127 deletions(-) create mode 100644 docs/extending/settings.md create mode 100644 docs/images/aca-settings-custom-group.png create mode 100644 src/app/types.ts diff --git a/docs/extending/README.md b/docs/extending/README.md index 1a4c68a45..abe4ea059 100644 --- a/docs/extending/README.md +++ b/docs/extending/README.md @@ -16,6 +16,7 @@ Learn how to extend the features of the Alfresco Content Application. - [Actions](/extending/actions) - [Application actions](/extending/application-actions) - [Rules](/extending/rules) +- [Settings](/extending/settings) - [Application features](/extending/application-features) - [Custom icons](/extending/icons) - [Registration](/extending/registration) diff --git a/docs/extending/settings.md b/docs/extending/settings.md new file mode 100644 index 000000000..581714336 --- /dev/null +++ b/docs/extending/settings.md @@ -0,0 +1,45 @@ +--- +Title: Settings +--- + +# Settings + +The application settings can be accessed via the `/settings` route. + +You can project custom configuration groups via the `settings` section: + +```json +{ + "settings": [ + { + "id": "extensions.ps.settings", + "name": "Extensions: Process Services", + "parameters": [ + { + "name": "Enable Process Services Extensions", + "key": "processServices", + "type": "boolean", + "value": false + } + ] + } + ] +} +``` + +At runtime, you are going to get an extra group called "Extensions: Process Services" +with a custom boolean setting "Enable Process Services Extensions". + +![Custom settings group](../images/aca-settings-custom-group.png) + +## Parameters + +Each setting parameter object supports the following properties: + +| Property | Description | +| -------- | ----------------------------------------------- | +| id | (optional) Unique identifier | +| name | Public name, can be translation key | +| key | The key to use when saving to the storage | +| type | The type of the value (boolean / string) | +| value | (optional) Default value to use for the setting | diff --git a/docs/images/aca-settings-custom-group.png b/docs/images/aca-settings-custom-group.png new file mode 100644 index 0000000000000000000000000000000000000000..02360ac10b08dbeb522b71b055b23a3a4d898fef GIT binary patch literal 66901 zcma%j1zeL|-!P4UgoH?gfCvZ(NVkY|NH;1aIl9L{R0QdiMkJ)WOGLW68A^_hQG>xZ zpYpoz_wjwd{eIidt~lrXJFkUksL2!JQ{$tdp%E%7Jkdl$!`?tc!|=I*gF2!$lx2j5 zcGKKOR#rn%R+dr21!QGoZ;6Jc5E7q=tFP5_J77IQo79jIL-R2xOM#G)_04S==>kR$ zCOnq7JEX5e)7+o6RcGD}r7)p*=H`xV!S#OHguKzt;K7%oq7T?K9H7S-4ZEDsl)V%& z$Y837>tIiRGe6pf+rDLmu{LFZxJ{rqxA;c^Cf0a4AFPKDX)^7~e_PA6H={g?2;{ zt_;g`S8Au`tgUhkY8=&|r@ay8UU`bv{YcU*9Xr5-Gp-UJ+h922gt@y!^V1XjfC%&z zw7`!%Z&J~`pO`LcQw#-dqa9mDc|NwX_Os;Q0-f-`+deuskS&U^%Z!p`!8hQBbY3SV zUz;4d#0R-fw<(eJG5^#)Xs2E5PomC$NxkA|^VBVjV=4-&_-3;SGeEPW_x+POB@lc z!PQrtlLxDqM+I=Bc##m=aAeBMTcF{qt-H>T`7Ail?o?~UkP{lvR0Q5-lhiB@GN1^l z9|;n5u}tlS&~)2JKN8n>U{LOh3+}LiTY87Kd6kovzo9Jt(Tm~y_>HL4G&efrD}=fG z_%1t}^KgU$FO%)V+n?^UXw!*J-ix_s`?F)W2mkf*BXeJi`f_S;>VsZFy`Eyml>7M0 z0_F$2*iOy`JIl{1UQc6HXL$JtLhOrX8Jb9*pmX0qe~|e`08<5fMtXE|VVDq-@B0mn zps%KB5kf zKH%cVvwbS8LYl!&FitZvOWjN56`MGtF|!+qqiQsHU+aM-?MA>!`=eXdessK!xDGx) z+v=pMh`5$j=CSIQ=$+_^=xJhTW7@rPs;NVVxMb4xNUbf~t!Z0o{xIB{T}3<_!^M9xMxad-CZ;1O%iTjtSth*C z$@uah`*k$4zGI1F3EGzy_RXnr&b6<#7%$Q3-6jO1b1yo~7C0X+|gUe=|r zKBxYOy%t2Ofs+t4@tkfNtLAmubK+@Y>aQ=HXj3uP+gP0l=ihDRk%?nUED>(VlDv5{ zd<*Agm_B38d;XvA%A(O&$U`3gj3Q^D>v^30o;2yLUvM1LYPYBbfo9O<8`{Sa%+}E! zE4N)3V2@4jGHBe)`GoU@$~HLS@yvbP@F!%$imz@bN8bKKFr4#>)i8Yb>DI{dhBusg zHc(R5mp8!Xg-SoEYuof4$`5Ut18qv>_(jx(?a510VC=204dti8x2%^Q%ww1Q zxFLRva_Rhx)(@|R2pTfATzp1xDY+N~xi^fLi)Hz&D)+_GQKBFe*^V@7PGAC(C$QCZN$QEl_S;@{vM7lJ=j z;Y)SYxAgj678Js}^HJQ2+m*^y>PYmc{bA*c)E8MVJ_Jt?Hi~Z-k2rm1WR8B8KVtgX z?kPi>r8Pj7rQuuY)?Z?T`Mr2)?!oO%_e+Od5Q& zVC&OXHPt`=U|z+lW^((DL9NJ`c(?eRU;O#ABW{9sash9@zl`tT{UD|P?g0^JqE3&k z<*|OU;ZuDMu3G&XgA;=pBUVESqn+voJJo6{BWuIb3di!@^71k&YyQ66$u%LPP_8*$ z`^L_bsvRRS-$skZw;qHZ>O0}v-Y0S=)hC{`8$^Lb=R^}kdUPQX_Fc4Hgb|6U8f@xp zO)rqG93%a}x9Cr?(^R@0|O^4SX4Y z#`!dj{&;3$Okm1AWou^y@wqF4n||RDA}1v_8PJ!5hL_fI1_wxk-u{CjY*VD+9 z^qWJ@=>!r+;!@t;zCF#GO$|*BO=HbdzR>41W#%)=)7E}BB-p^jj)tMV8GEfAt;_)wQS@aZnasm{Q4IQYXN=vV3U{g`Ht@Ql zV+Upiyy>^89}r94xlGvVOB-+?YJ2=mR!MgD@v$tK?9|&uf=c{8{6fMlywjT*RExAS z@85^%-*OJJ?j+l{^^!1e?i!?@qgMP77HLd*&e+SYL_hI}h)&33zSr3^%i(j*2q7=y zJIYGFHOC^bz5h1Pa>%lImtHu8R!G!QcOK^T^fKHG`(6zTj3tM4 zf~8rNNReB8_v1s`Z`*k5S}K|>?BV&@YWV$mnGLnpU2= zL!wBZk_Fv)%X#6E?@ILH4*nBf#S#7IXvBMzaYVg)@uvis_=WgTRpn%l=M9T)FgO8! zs?~$^gY5f78(m3fDsBA<1__mGLk&_X{Jwp(7HXZT3H6h%l>IxYl#-xzzs<|k^VZvq zw-YH_uei?}bFr-m*0{XRz-On`th20#F*F3o+m42Bcw!#d8f~|D?fe|d`8MimSz<|H z$&h#>A@$+~oH$+z&NE;JQ9y@AtU>o)B^iI*l1}@(u-@cPTa4#5g8Fw{yjEuf8A}S*uN7`Q;A%en!a*h>{ zVTg1bXIXi3CG(3<<-vx@m&rs|e20m2!Cb_8biCpf+xL$*UVlb*g+?3;_9Zt)TGK(M z`EpiILh#s1Y9c@mVpQIT{rP2DY-+A*3}VZWwS5Cw<9tq#!J z*5ieA&tRuKc**WUsBi9R0mSnX3<}okioRev}mak}`l^rKQfRGvYMQao3x#0zEE z*YfAa+D%@VBwl?ylV9hbschMekvhIIzMlVaJ_Nx=x_`2MtiDcg{^2T7*_5hv_F#6| zT^VG**DT}>LM$e<@cY*uNn9Xd#61k@aMcS1g#LNYf!5f1_mC&z2ytQQCjwczAJ7(R zWreZtf{O+x_wuO2D#at4@N5Zu{bes76YGksaPKW<2SEUx3>wQICL@0OGr@1R1W7nX z6VD`=Ze;H6%Iav5C+#}g8a?t_W@_5ngIMCA<>LjYA6{Lx5F(KoOLP>C*wU^oh?0Dc zCtgC+_BmN*#gwl>9}x5ioiJ#P^lBjPSq+lX)-Cwe>#D6#RMZh_sjq0Is*1*e8s9*} zLZ?QZ%K5T|BRoa-$%pz$8iiav|t-FtbbjjhWh^XibB1A-Sf|H%$Rp*xTs&G zsJB-p#=oz|-pIuK_ZY(mbq-BNOIA@4^{r*@Vrl8<`U>Rs0~CIMI)LY-VBm^|cANFr z8(mS8Wgj*Fl#RB&o4%@ws5!`i`-KJQr6sqQgVV2h(8Rq&Q9}nyw-=0F4)%_&qFxer z|8a#VYW(Xk&t1lUT;gUYaaUhegHaabV#z4L{fPVFT}gaKMn-WL3oB90Cr|%%JL;Fj z-B)gIPNFErKTnlR%GjZ*B|0zrXS~Z`WY;N@ zJXpBb}cjQ*epqWTG|hW2VS0^;s*(X{Q>)2CK>EjPgw4j}F{5w^ZeE1mzB zCRV@(W4Oxdv(!v4S`-$~K50l^lxy2=`7z~1C8gh8_8g37$2?p)4LZ!xQZwxKF@ExU z+t1}m7^Pd8{d$|^0ke^^WCotwNu4R0A{{?`ycH4s@;z;(JY~Jjbqm%ebJb_j|7`i| z3DYkU&0c%DnVK%197K3g0x=s!9!Mi)$Qjvh!sfoHAbS)z)~BEx{h{+T%I;)`&MSx? zuxO1d`seKI?{J^y#{vFg*V*2_fSJB4K3|xm7TJ88bGV$_D}1aEMTIjCs~zU=6sA0= zoOcoj0VsM^j6%LmamLM~$G;)Xq`>!U$kkT!v#HnCPx*Q;tE$>%bV%6aeHd&;^47u_ z+kap}!zfnzdY_wH5G={SxnVTX_00k}IT(XKk)n6z+9W+LWI)8>&Gy0vMzCfX71n>) zGqVFr!FlW@{ASp1&i-1%tnq9;7I|GQ?LSKktr85!Td~OPWn16o2D|Xn6n=9DqQV

dw3LTDx)kK9EYjDpi{~gc&p{roiLgM0TJ7&{g#Y4IPo7 zVXXp~)G+XCLa3aqub1DbKe4U{OQHJf;*hR#KR~Bi6yY zM(`gTpas4mxRaBUliRh-gbV(bPX&Zzl4*Z52E%)^G5l~fGG{=o8}8LHZOn-D@r+X@ zzQf42JHeSbkOL}?v2Izss~SBndGWSsNDc^d3QER|=GeaZ0Vt>tAe}Z82N{^Vf9hXjS`+uKb#vhXJH_Xp zwM|EKE_2=C@SCn>fKI=e^z(Cubw8xQmPHr*1Kq&wLV?sJDP9aK5D4< z4Dsv`yYgiso^_9gn?xp#qvL+Klh0J>bPIrqNk)`%D*mDMoCrW77LBE&jM`rIYZLB{ zRrlz~!w=P>i}rWC_NMY;{g8=BBocN<9%)@Z?oBg!tY^FS^#Ld4j3M_stdkenIXwxu z5oJaxDYumdXcC3S#!74F++zAM9Z8uXiTj&cKr*lpkYuX!3AZ!-Rzy)Mf%>|&De)68 z$ti_I7s9qxl6-g2M@zLLOsoe_-?|_^^wAVUrwPZ_$=Eq=ea=0IM_!9tr_wgcd-8(m z^S!MK;@9r=2NUMQVp^^RVrG3Go432t0RX;D_7IGoFnrnPDB+;_x(myCyv?uVtaWnk46iPQ? zTXFHk@tcME2{iiB=i&_7$%48!OA^!LnT{NjA1sJk%p?+@ojx`wtLZmkT`A8O*&-<7 z-ApzkgbwzCugBQ~kN+R-fW0 za~n8{cs2P)Eys|-bM6u!(kX_7)Zb2UXKMP?Sg3&&!cHVGxLscZKvc#nd)<<=LPZH6 z2o~PWi!Pyx{Okk2-510(G6AL+;%<9ahkIJ5WsR-7K@*4j^T**tmb%&@)l9mdPlG@o zCnw9_5;HiMos{^gZb~-u)G6t&vwMSCm-oBA6(|F34=LAkatewh-51#4tQ;5vU|E(M zm&l{z!`f5bF6-(ZdOXavNaD%`q+{4Qd?KBZgKo= z00o@0hyw2V*+>Qy89nxlFApgnHr}86ZUtzg@hDNo|69mk6xzy-dBlNX{psh7Wg&fX zbRR8EiwP)k?0l-5fqBjhm1WMHGo`QjG-1kJQwdOZc6S19WJTlQ1E0ZOnd|Jvx%ZJ{ zT_z9mgL0qFN8kW-#CNR#a-oCqY@WZc_w)YGN0t-0-MYC#0w%7?pYBvwtLor+@70B1 zt_GTWR_|f2w+r=ge!ss!U16d{$zQ?0n&?YZS@U*z>8Y!QH%*aOI9#n~>I_FGQM}tViGVIu!as3q*iE&HG?9 zcE_Ke6c_-oSCxJJ47HE5TQsJaGWbXl|1vJ_>3rS`hue*-pAc@3)CZv~dQB%m3&A#q zDc`P0y?tmL{0;+yR?}_Tcl--7((nE(u8cH4k9A&BjTyBweqb_u8^D<#vBF-z#u$5e zQ+K%QyMj3YP($6Np}oud>fm~&lGumMlDvYBp#?+rLi8*_4K=aE5RPc`^n9}&$H~c% z@*KvWS4Cwl{nvBku(U+xwcNLwN1dy;PHz>+%x;XaI(*0boiFHjkVR{mkkv&(4ef0$bkD&=i`AHl&f-9oF@|Idv8F z>Tz+;s`$lwhv9ZKZnh>0J0i2Q_fJkVYU|zsnC_zye#e*a2lxF) zZZWTR26>h@Zn^atUrZDgk$$dY87B!hwSzLr`=>gtrq+gfo~z_vT(&*kdg{ZR)Az9F zqYEf|;7cT6bMshXSy^}OO7F!|z@3?Mm;t1Z;c?oi{9qYKktyY@O(|maij8Wa*n%ndiv;yn0-t*LJOM5v#3WzSEGg8 zJf|%7I}orWT%+81JKjRZ0{%%jBA7=9@G^M`qJku?9*Y8^x6D;+NmDIb+*Q|6t(VFD zMXPUJ1{$S$?%BCFH=<-f*Gb}%T$B>ETuKlo{8mY3hHyb`liP{u$s=*7BzO8-{&}s? zb76-KZABVa=YHfcI$*gG{HUMz3`J%Ej=*pz_O3r9zW>~@`Le29g`=+k!^-v!drVR+ zlQ&(3<@G1A9tvEdIMaFqba(nIpP>1ZAJq8;2Z+)XIo6lj*e9#4RBj}E(hP0*h!PW5 z%Y$&-$y37?#R4;tgus^A5-~q6R#(Sas*sf2$U_4*OaaWT(tu4q>}`?K*AA0N5vHITCCDcVt#38u1U?M zQy00Sy1@6M!kC6B9#XS|*??P`MFD=cL$r!8^0VhmnVMh7BH2wjDWW{ht1VQTWW!1t z-xhQT0Qu$aSA0bFL;f28EQgTopQ(&c>faejjG&wAZqEx(z{GK{5z9YF zKFq7t>j?u1&8+86;a^gc3(#ao3QA{~W3R76g%uCyEc8JgZ0;a4_7*~dOHlW#6mA>L zStTXM+-pN^T-{7loJ;we%udx{ZScqqfNr^uiVGQ7X$JuKdPRsq(`UQdO{Hv^Q(a$Z zplzR%nIF+tH7M-$e%|~0Lx`Dr|J;4O<`$HiVN+mG4W%I*lGYBqNAzE5|pxMOl}4k>ljap1DVou~+HLJ-;@gIs=lw*k_x zvBSQ7dwd~OhH^jg=tFmJ0VIsk6a!T9AR)H+a=)!(Ui1D_2Z#ODFGH@g7QNq$h!W<_ z^s0kV^k9)LZ9Cs9ia$EGaZow%b7}O_1M5p!+mTI&Er^EZ)`aNs7FM!YcAI;6a@P2< zxZZ}^Gcbr83c@m^1WntAi2)ojfPaUTl{I&23zdTzFnop%Aek;xnV)jbU6_X(_%FpRP>P*<@aa{dWaQl z8R$m&n_|fzoG{=9A_f&p35fV3(fokaBRqYPQu8xgU&YPLR@kRYjs5(7L$@IC_N7f? zJP%vAkG2cn&)BU+SfU<4Do}Zrj*gDpp>=a?@O`=xpt|YBQuDE_Z>r8d7e89d<$CG0 z={nZ6#r1?V8^rw!;od^3F=?}wp*HsNZWHp~R&(`pT`j#qm$O|`}YtY1xq zxO7KtQ5&yg0FZoe8|9QW$-#&L*u}#0Gt*#QW=N*g-OC*!H($QT$>9JJq0min;&@Aw z4dRf7lD2st=o5kINtUPL4;V_C%+`uiCN_*D&9}8ic^maUpVkKgcv&13){_y-A%N_J zhtbtirLt(Y=+xn9E^Cyc8bVH9-K~TmAfkIvOUu`tZkT7ZHd|^X7jH48%LZBXt7dw- zN7M0Y44U3m5x&<;)3^I7Y@kK5uCK*qWB4mvNL6s@9ssasH%KxgF~aWw>^h6lb9cq? zu&XYiTf~K&u$*E1(J8_7O39yU#W)Sl+gV1$2`I<2(R;tM9FjIJ&rJuNYgxzI!1e5n zx=t6?Y%ok2xW`qj>YjTC^GZDoPIO}IKhvO}eti;o;_M0;q?3}JwQ{p9p*{u3&3nVI z_qn(PBy=5no0^)wh<#!L?C}PR?ota6B-&{4oPATK{}7+ICZR)MRaFb-xSJ-bp*HKz z=G#J@2`x0kO>)1~P*hhfFJ<+U)#uC!x7p@k$1K@TDQ3TVYGsu2ZPM_3y`4Q4$K)<^ zBLlsf&L+SjpwtWJ#^;xxZvmF>q4)@g-%V<@vQr6~We4^4c6=!cHOg!$XKWUemRe2f zxLWf2rZ+g1^z+Cqe*7x&y~RZ7l3o=ElFr0{-2bTm;-ZZ3Vl^`1^HVkAMo9`F#M%L> zFPSMmO9EC?y#1xv(`@gK$`dyyxjP!_Kx0iHRQR_=<-?m) z5v!4ef1}@%e{w86F*-tF{L2S`9Ft<3>f2*)Sa%9ZLm_#zRMy2_4A8&K8TJfz{o$= zecJrV*59L~8lyF+^*U{K=WbHI19H6$mpU7QNa?e1ceZc6sUYxVHuvInW8~bq_p(`q zV93A+BZwQoWCs|1z0c0h+ic2%?o*531z?M+yMYykrSOz1Ic@%A@keI6VqVrT`{Hy@ zC2y1Zeq%(FU-Ol#fsrAj=~$>sm&Rr5^k(7NITo|c zJPT(v$F{x0CAf3zqLD||8TVKP+2CQ#{eB020`&L--DT6!e zv1}Ya^@69%jf6IpVZ@VHSGMn7Y*Dh~s-s~IpOtf`Hkda0Oaqv3a{6T|BS_r= zn(Y-fhE|5z-3+A0=!(h}c$-ATEfJxrsz<>Uza>?tR%0l#y=;(-KbSZf*DY6?X1V$r z%CS(f)#Z)>cTJGu9^&?!=+!ez8fR=C^nBv){)8R$&NGDtm#{@N-5w68AxUGRf6mPO zIzEtcc({2Td4?lU_uC6Xjfx*XUwr41bb0pr>AFjsq{PY^@?!RsfJ3NzrgD%>omOl1 zRo4LaI*&inp1q_bUZ!4$2@@VUm~v#t&_g(C*PBvq!tu>e5%iwPdIyfy&U3nAw%DaDmH%hWK>@#hU7L3? zR6D?QZ|XFDp0PQi-gBxdc3S#6 zWDl)`(S00-{i3dnw-V99amO)n2#+jkFO}?$^v!~1VfjfeqZ{%pJb8LSzQ#b;>(;+Y zn=Zp_j}s;Tt&vfDuMqWhi!*d(oT_hoF+m)DLrQjN8z%dtybH4@#MR* z#3pC8prs}vdPfw{f)v?2rJS{6$K-mrl4!#oBMI|2Be$&_sA-HNsLzF9iBk8@_|^gP z#-j48O!zqmm^&in-0$qYTferDvC>HJbOy7zaZ|4Ty3cx9@KxfrAyv%9+@`yyVTz00 z>ZM$1h9&koq;Zd4I0mh`#*RY)6slh0zy8?3aQ}nv`;QR|g&T7gE4|~lgp^NDj$0qU z&o`$AC_j=wl#03gHH!nFVv1EWnKN!z*dqOfv}_B*0MyPRQk1l!7pcO?>x!+?bgBc}FCy194a3#qnl_0=%dN7P)4 zCt$AskgYvw?7qe9m`Ka1pbiW*W`2blHNwjP^%sSPo&k&hY$~kTlzU5SkFE&LgITPq zJ(qi(@VObZNbfS<4SRWqom9=ql#N7G`nrxHGh=M!@^uj096`%ThR9e3ReBxOx?EF2 z5(!sVuk1!>vkg^*_Iw+{Hk?1+iJ?}dtO9Pq3LTZjQ&9kVFjY-olVd=uNubi0>Qz`p zIiHE&Eu76Geu>dS2Zx{mt;QOL`(3;K87Nbvls3e?J|Ou_FgSWyau%&w$u`P=Xi&$0 zJTR@RC+^eKjH?s3e-(nz+y4i|0_>gR%3d_3-5ysi;SsIn(fXvJc8gAzh64!xebj!~ zloI@bESdGyT1tW`qtt&;>ZOc(0r^C(lAK9s++^-~>6T0=MYdz&ZhVn;hFd>e(LX_c zRQ5rWy81v|<%z$V%cyHd)fJSrG~-^OvN8t3cAF&ut@#9&o#~9&oAnPs*{>Wo$I43A z9)GT)+4~^B>zID%esP?3qVhA7Pf%dn_Ms`0f=2uYBeq{?xSgGA{zK{5)A0De#aM=+Piz6<7P0Alf(z-;mhs| z^nk4Qi|Pt$Ys-(HMn&k1vi8~4o;zS8TZ>d*TFZ^AmsH#AIG(FUE;P53z~-EZdz3&t zNj5u05Qk!10xHdCl^wI=HPXGd_}TM_+WJde_CjuWWU0*6vd4Asbt)!cV=-LPp(z$0 z`2SLfAo244K~d4*WNPd)s089%kA%$h>SWy8EA^4vMIJ(Lor*P?=eYG2sN+CJS}Ixi ztBaeO53#+Mw$_<kEmSaXdFB}xMMbZc-&dlAo z7YjsjLAci)lFduiu`BDvmV?D*W{oT3y-`HfFQ$m|exls@7(puUAKE!UzZ37)LnUQp z)yOVcBE*~hQFZ{Hd&s?RGOLdueGZjiRT=0#OqEjKp4TCgeLEmcT}|n0CY*l0imK3iLq3-7$ z6Q}L?-gY{_9aN?}yk}ROd8*I3!K{p4`JtNE-qS8)^)l-grrCs<>#)BWWhKE?`SV7RkfV4l|SQhm>#L*=y6)snN12{+mPG;=I8*)kx@9XxI>Dq>v*nU z#nU44^A;OiO0^R%GUMnu+9$7Uqq@xbGi{Jx3(QyGnq%>(-+o#9pp|IjkuDK~a|*bG zrqdKYE2618=U0;AM2~icYqRov)^RLHy=x?Vew7cfd=DkczdszH0(N+0-|%Zb5jppo zAiEVVP- z7N_kc5xWj{mWdwK=*TB7d^W?ZlRTJ2k=c0eKyugpBQbz%7`MRwdq2$zLDBT(r=}LT z$3fC!8O{*O7*MKxw46$--lp>QyHXCX8H+z+J}SsoHT1G^N{zV5YmwYcwL^cB6TWJ< za#+h9TU;GJ_T;%N#%bjj9R|-ZHnh=>Rc)I>3P z2bOQ(GEaZntLA_^d;Gx;-vZI4*Bit2mT*=+1=0fcuon5Ils8sCFi4140j7J4Wl)9j z|ItySpE;<)y%_>9Ma1pt@tH<(*qof|rCKfitO^p##2xQwEla z?)}pNU|FqL_19K*$X-YS8dm+9tOxzipNnAiC}-s0&AOw_nBbM5iHz>#3YVF_*#JS#oO4`^ zdXWVS>8r&G>kKj$wq|o_2(Vy=62Gv~QO&XCgQ%am0{)`?cz_fD7QTJHKC2ymYeGfx zfaVT+<@JOQ$Ih*;6EPjcN<>AZl<9F4iiKP8QbQC>Q_@ix_EuD5T@Yqx{y{=tB(|AC zpup5-L-FxZ5hs}mYev$fZpGM~@Mx*5CQnfwz-Eas+6R%zTSo zAYp{^$p_;{3kL8qdWK6{V@-Tj;&*rCS^OU0035%}9PIzp=WF3?ZD#=7R=o`TS>S%S zUAwNVYJ$YSc%okI+SuZ9Nl&bA<558-K@O~X48O!E$^`NH09%lr&yni4x_@fg-?3Co zf}>)Zn$gKH^ZYRK`p#?fTd2Z5!k<`3`lV8B4+#OcQ zKif0@LP&9F-h^yaDh*H)8%{>m!fOP;)cx21YB)&$viYVaZq@c27rZ5vJEJA7w7frw zY*LaKru<>z*KCp_R`UWdT*kg_4_F!%CVbcnq!vs8DthJ z8CowlgMqfs-VS3W6FMo$y9P{ya*}>2;!IH2mALyssEOvF2&EOgPJ8%1U5muTrqek4 zQUm0^te7C`rwl`ou35& z>0d5H!w}02c&4fO(x=%J11`3C1Aga6evMu+p~~1*B;xG=rugd?wx|D{3P<(z znnQ@aPXD&p@1Kgls$4N`R7AfSwUscb%2JHzg;X{gt5ES{LIkM)<>KkHeGJs?TT=|D4n7~^~ne`GWabz-6;7R5rIFY%L2&4 zJWCpfcF@31p)_oadrhK*#{%O5r{D;H?F49+Uo@GH#-5k)>T0WBYua=-KUy2er56r7 zUD7+1kZ>##-l1+B0yg9RZKE}8=$>WG<)^D9%d{6}1o-&d) z;pyWgpHx7u=CAZ-ER!G*mN{KI-7r>HR4fO<>RkuQ4u>u<;nY!6N-!Ag3coGQ8PMnU zH-eyHL=$Xep&lO)b$)q(Dqk1pv-vRd^T&SmTgk2Z8>E1rjP~f^T}I{Jyak1YC~SFn zcua@qzyBX||7NywylHeFRh#9N){tT`!#{%)AT5mH5n!q{_rE@e;EnOFYnfL|ODk6B zE;Aq&_#18l0`BPY`HCND6(4@;KQmwrDmQj^7sx1og>x9=msLP_dk1it z|0=f9U-!@ba$nq0`12$I(xj-4i;O4s?*Luqf1l;nbL?j3HqJk9RgoO!O_G*V;s0Hb ze`7#C0PX*p1Em)JuQ@2-5m=?asr&a4%QKYrRQ&$GJ=Z~tj`4rZ@pa#)*-A-Ci8R>a ze|bQJgb`N)`e%55X>xL*|6P-py-wfzD^35xNL-B40WH|WRHT1iMRR_i4j>+XBU~mn z){Ws>G*Q_~Z-PD$V zmZoOZp8@&*d(1{UU@>~>&(GJO9@RlLk>3@R{j=aM-MAx}G4$wWyU6;V=RwDcVMMh| zu`u#u{aGT~P+@eCoG>7q{~K)>@qPu`0sSO@Mm`pSy%0J=#C5Hk2Gn%x@(N=RrFnAn zN=--e)ETHm-M)m;itsH-vVFoXY1rg2sIgB1AH`{@Mca(!zAdT4G%GGBi19vJn^99v zco6-TfKti8An7K9q%jU5HH+9@Q{{Gf7yW1}pa>a`p`*FT?h z|Gxq^_xh^`S!V0?)x~PM?`}x9Un}w?mb0Yp@jF7A+s_*5iVw&0wJb-n>8n`57q<(13;GhM)pvgJ~E%PulD#S(ePg7kYr3o z26TzbVWvWEve<~tZnEeAIu>xaRocqk?0b4*w#=xJS&xQ9A%R(43R%^kCZ^|)faQJd zoI9ThX7GQ7J1;Q26Bk4*sqX{dU@|S$OW-!Ky>^A}&DU2VAKF}=9jL|DHEs4H+0wlp zTMee#T+cbwE<8TjIyzrD=whhV>)I++q$X%0%OvjkOWxtP8UA^%%%mg}vKp^vak}6M zB8Oe0`Rc|+Jm3T;xJq&v~x7{t2z+=XZ>b(*S%)K$XRN0v7^bPOud_7|> z(f{&5e&!i1$l?CMGn+XUrIOY zt6v>NLb{>XSDuY2*H@>le756XqD&?yCVIFVw_ifXf(;N(tlA@|wH`Z$7YQwSOtSCZ z6m$an1%HD;@?P_6neJHfoUHUj^Xry+v}QvcUESUn!F2biH72tPTN^$192t(-_yXw! zq#-CM`=qYV)oHf+`G?}RN*YTPg}0_Svb+}r7)vf#Ev&2r4Wvd>4v2>3MJ12_yS`yW zP33~!EW1MVO7eJID@z2%9!;?Q1s%aZDBOO{8FmVVv~~yuxI;3Q%U??#f-txMy9KBh zM$$k!5A5cNkJ=4umBI6UVHxE%8+rPaOpjBM46ud^&aG!Of@iTbwr-wc>Q>4oNL$2g4So_ z6QO3yD;=z^rrSw5r1BB@D-xa}rHs zunBG}pq{HlnqDEMT4$%K(h;=ojbrBLLI-(e5i2^TWG5%MAn+8il$ zHQkbD;}7Q_yXJPgiL_rI$^h+Qeu5Ta5(zUFg@1I9U6`q~o@=!oiBn4Uu^o~)jgvT; zmUVG;?Kr33%nYV=dGEG8JwgvfvL)};HcE8;S1rq;^*q>Aru4kQeWE;pEyHh=s0Mdg zPe*6QyF&n5?MoK=;KgpE`Xga_Admek=-~fgl$t;wRJQ z9y;K<%(ktgAt|W;@RB3LroIqkny_=$_2lM6;p%Gw?tR=Eq>wQ#Tbf%8oivgE^JgX| zJ3~PwGN)2Vn0gjXMFTVvC;GT@6c>-WYKqT#Fg4(LwcY*ePZyO*yyovVWMkJt=NQkx zf>Sl+7YLG#rlUc-?HXjJJi}{Y?_B|th_xC}$%gT9<1MvkLf|jiZC90pqJ55}sr=U{ z6?(Kj+=`OY?V;7W#F#D1|Llo?^u{;ZEk`{SH5C1W>)E0zU zAb)`*+IU0u+v@|2V(yo<<7~KFUdZ6mu^83Zm)Fa#W=;(}68Lzz=({)L?iOE$lOo`i z&-q>;bC;Zu*0yZg{eCJ+WA*O&tDH$w#(}Hj}_v$r8&rqCYcbzJnHus6j*bioVxYx;y zs2&B(8tUwcpG9}4mY2ex90vxJyq>JEo!}62+D$LS`PS&MV|#s9T2DhvPS4VA_ES~W zqxBf%Y^j;lh0euz05VJN!?tVn*lG6aYNA<<-b*p`7ldbGCmwGzr5X(9X0-L!OV59_ zvs1@;A|}cOzKT1}*EuD>EBf48GR#=31;5B{bKJe7!8wRG$?MLXcXyeTR~gEI}qL8$a~P3>VkzT-RBou*Qmd3 zaMUm}DS``KiRv;=R=rL^KQ0QWw86I65k(kkX^}ZW4F$GArLL8)dvoJC*fIy#22xb* za++Hg^YhYe4Fh`uM0GRAA!ZlluPe|`iwQxPYX!^tGV7?r0h7?+YW&5USM$XI#SnVk z*^dD=wF@38t;O-0tV`+X`Y`Rw)1V7?4kKbQ70%49(!4!>FIvtekRdVMrF+rW;<5Sb z&6d*Jl%`&vY;xWlKs|}zl}=NHI$im8*t1A;Lt=&0zJZp4#^XLbh*4Uke`yge`x?q0 z7`ONJe4C@3h*&So$e{S>0TrCsL$T;_B!zAIm&WBDP@b48}!ns#h1YL;Xqom<6vGPUl_K zrRj?Epn9Gms~0=F!T&w25X1h`;;F5bDO8W(#Y!YFvQ16NOZ?SCZC#8au6eA4{ zQ1)Fnu1~s~V#kA$7xve-5V{Nan7XH9Nadlip03^1nMMcVXUJ3A+8)$jKfL?(UjQX9 zi3eNhZLymh!NMuuH}SW|)WilHbJt2+Q4dv29QbJIeYd0c;cBooJc?-4TG%LnSnJf3 zv_O8mrNnSqoFjxF&9RF*@ONje+t>|LeI_xn#U!Fn@|R)jCEnBRToe=(FD1T4t!F@5 zWXP%)J)vOBz2?&z$Oy@1;w?4i+E4xr)v8V-^_%#bPl*v=fQ-x8$%t zcH`I26LuYUUHD#E9`2Ba*ws6|xN#iSMrS;!EwY|KZ1pU1U*Xw5ioM@L7PP6B$3j{dNf)Txw26OSZgR1|=I zoJwZWv9lsHHNRtEU@%yWQ%bEd;V&T7yvFJoDppp^8Q?xP-5DCcEB7>#-T^w-N^L{dosfk6dCq@<)lxCWaqpnS0qEcX=$gT;Wn9T3Bn&#?PBgR?YM{=oLz|vLK|G=C7BoZ&oCMPKl`{SA>_VU0+7&e%5Iz5oZfq zD$T4(>$1$oT3qyls?hVN5M~ePm0${##r0-Xbp%6m_nzQKEZYjiHn`2i&QMfItbU?$c}3;Q zAm&0`=C>l?!Wbjoz|d!F?RPJQM}GUguUpCk;g+Rat~pw}-P8y2)UG1u&yW>Go-f5d4sb`hg+5j6w2{2WKn${0Esg*e|Oe6Hw)lG@->wE1?`55G? zNHS6GjUHg)7mEgM+gNu0oROk&Wj8&She~lV#PPGFPFCHDyKAzSV=wg*RqnGZ_=v|q zs=Gg9td6;Ao!u-DJX4f0=NEF#sNuT&#D03|{HCN|sLP#gfsP(ots!5_;cdvuGZp?o z<&}|2jG8vc4lFEVa4Nicv;7+IHegl<16GJatLN+AWW84>z7rD97KWNvk7lPmIt)Aw zxr9sJVI}x>OsZs@Xzmu_jPpF5e~&fw;60y_fyZ6eoGXrR#b1{kr&=a;H@oUu7NAtx zGR)DrIdgJ3@NTR)%)^);8bf>5`UpxF|rn=3Wbdb^!iLH3e6e7pPc?EFFU zD-SAU!*rJZ2q-UJW^b>Haw%x@N_dIF%bgn-=h7$*u{n8u&=t^D5Ze#+{0b z4364l9o5?-9{U?o=*r#|SCx7UoJG}V#Dacqd(fisFk}<*h%kY?%Z{hk4LNdNqho;{ zMSdlgTrve@Td;?9x*Ku5on5L|JBC@m_kjh)-B?7_O(Fi>pqy;@s)|ObRVTlIhy>UB zX^YYCn~G{`VaFLndc+-xcZQvBGFS~TK@ZyIzk(!tunn`^<6Ip)jkmE}v8~_%6`HLAztuw37iRs#JQKO;-On7<*QGp&+^E%i7asL z_Wp$oBvivZx_iwvI?9D0z3ozsR@Mk&j2^?|G2i{wi#v9Cr7OkNDnB=P{8LN6<)*X< z@$w*T3tERbOg8ivM-MCt zf|thNiQ3?=rQfhjfwfH`yfGQW)xFi@&*>K@$0X1pl8*VJ(K}wN^=G=07f4V2}ZZCCrc-TL>FQ1H{8~~hGY#O zfii&mjINWiUs`HAsK0s2CPuEjA($SG*lqugTZXZ(Qif zKU0t=H~$o5_3_Q>G!Es2YbbJ$|_;OzwixT%6uwn#O$0Evzpaq5=e@_W|+ zn|odWQq^#a+4xsW$%d)~81i=6CQ`jyrc-A!ol>)@iE3t6KbE zZ{2676n`Vvs~TTq`X}YxKU*tYiU3B|weW%ed2-a9ArtLp;gz^@#+43S_u+eLLTbo_ zS|c;Ua%e3&vDRF-bm6qTq+^F9aX&rzEU9N}Qns@bOA!tE3rmBZE6X)Sj`Ds8fE~qb zEcUAuP>=_S)Y@dN&n5;FS@UP0*f}_)@Vn`b_a;P_S~5BhGppj)m+@-*Z7FotWZB-C zKfR5QL%wKf@Ia+yV;(8Te=NP4u>(1!a8OHeacJctnCP+GcN%YuKczArg>Hv|UeL~= zby(Hy*S4S1g~wtO)_dS?yIV^p!)4A}P~FyLkaMmSIA=?NRk*b*FCDwTAqtO|`XGhB zgwCmR^GRHCc0|?s5f2u@>hv{`b|V%2`$)g!7>A}8Coe9pz&wyuUO{LascoW;1d5aV zgzX{cZ&@=9bKVjX>W$5c-9=jwZkwK2_UF`+VVX8?I>&`(9F9J%?=}@S(IOkTIo|Va zyl$yo(t8}fdmp?bgk!hmjGYeBvvC0y z)PQ{z$&X972COiW_9;QTY8oif=x1e!g>hv2e2Np?<)W@LH&F*K7(Ks9mICZ&SdH$Sl|34is(C^VpOR3;bo-z{wO{L15|W8ic_Q1wg`e>4`e=g3P1uP$)k5ge zgk<)vKgY`@xc=H96$+%86bRj&PiBU~bn+jXbKY;>qKrXp=MHDWQK$L3Qxs9#mk^a( zu;cWCE#kJz!L~QkYbCe>2|qg*7Gz&VA&1-89kUR=#=7LtK12WhIg=qtn@Xm0eZAD( zW#?0f3^XzyyjNQUctJzvfTBuLW@gKY`}40nE~~v#(burE8rhKzWfhLV({oO$D4S(> zA+I4KzA3djN=ER?XK( zs(5$84~E=khQl6`bXZBuf90No6y{L`?b@=lFWcve^33D$@@+m(eN>%6Wt>kbde~WN*YYP-##5grSwzz zY?C|2`u_d=Vo2V=Oa9sW9g@yg$=%lwCu3nWBAeGX>06 zXd9J+n&@l&8U2D3nhH1jZ=O_}V14=@x;{T0GY`?*-aD>fm3Za7K)uVQ+`LaMSH&w& z!GnX1KI_3qJN&I{>YtM8KsS;5P@*U6iJ#E6v!kA{{_x#woT}zDX4~iRLp4iZ!d; zY_V^QeYp?2?fs^!2XURBtLs|hIirgfBFP&1Q0GR%$=E|Ef6)*hqB()ef^Y!dxRsN( zEqFX6tYp8G3fMtPYl2}*bT}xk)osg{1id-rL-S_hJkagJ=9;*zeu%l}8*?*&q`D*_ zAz^S6q*GCqx!L)yKGP^v<*}c|<^N!4qR)NuuUT`ZebCN0jf*Dm;w6}+dNFtpRp$E% zx;=b(rD9#7#x$)g>KlMXd+u4;URJ5;|Bj+n*}mAJTjH!T6~>`~7TQGa?k?sBS9a#9 z{Hsg-@8F3`P-FslZIZnW;M7D}9F;A=g9Nl0rcgTI?0KOfd%plWfhO-jzuxn3j<4~a zYqD+tT7kuMJUgSuc1FkE_v_cM{F=ehhl1qR9C**$fmy5dwQEc$kiDOmi{LI4m6&*O z@e&UY&o%r`t2@3RChI>x!N3kUtZEQ&?)6!nzoe2;T4LHtr&(hEK2yJXL{9)Q_tJfH z-UN}%1gHW4!lWQu!e0I}PCx%PB?Yz@=GGhWAJ6yerx!|qR(@)r`Wu3k5fH4C|F+}# z<=1YrL43q-UQIt_j{J2y;7;&1R+T00yZXf#l|N3j3n5D5Jc{f{n!w{Z=x>!${iUv9-ncXpnQRqWc=4ffak}5T?7Xj;KTo$ zw@rszx3CGja^*^A!N1xtejV-GS0E?QY~$uXS^fMX!2mDnL&?kkcu_zA@Lv}pm^JE5 z1f9u%U=O<6qp6KwesQX#K{_B9>iS&t`V0|3s`OWR4 zbjsma@*n@kg&B9-(jAPI;Ag{IK3>FV!Efq^RVIIY`7J9YCA|2_D)7Mw9fN~wcbPUq zejU*ti^|#`sO&1L#6*dKZvhkgRi^m?7xOypqm@O>*?6^*ZU7xH?dj=>1TSxk)ZLYN z1bXg9bn-GGYU(#Lk3p)2q{`pDdfCI>x(A8eQ5iBH8)ooyWfF~jRyIBK8=qBQ2HhHl z7p|N`sKv1gqS-7T4?p>bu|ySz!AE~R8{{Dn(p+u`JG6%T^HrGNuNk@2TUk)`Wnk!N zK^Bg03D!J|JHY8TT18_JxGt_I2p&BtKyML*>5R03>BhGO#s`zBqpXCBr=~G z9#6^l)8zYi{CRQL!2ei>_x{ez8ekO%Ay^4R0F#6`duDHkdDFWqdDi{BKoOS3sa3#c za92auISi`E6Zfd;3NI+Je0B$$z5xF3G7wx;dY9#!wQn)mWe^h*67qWQd+x4xw%gP` zA$%Jeda=%*BElD+c;KKRfcE0f&O!SKlvq}|z~S5qVZG0*SGWbA&Rz8tp3yOdNS<_r z)St^$Z8>=@{H1{cJc0S4dPiQ%D^2B3KCeiL0+<&?tGUZZ<4XJcD~*B&hj(45hg#b1 z*|nQZF9(~&xs}-!{v+ zD*}rUFM|I`mU>O2S>D!L#JQU@>3x7f&5i;w2ff!~M8=%gx}WKf#)O|f+&r-N0 zp3UK09OieX7@7c~V&DRVY7bJdN|iYv)LYEA)J)sL!Kf&TnT(n$rN!O!-PR)MXom?j z4*~El+52FBe@n|UEve->y{-8`v7KGALae3u-nUx^O7%+x%Vi5NK;8^E&34p6Rm0rjb_-pM05m@Mmrv8cf-5GoLZpneKk_ftt6f zpRVESm(W16Wp*n!@E(N{r33SzReNjAg0J0*s^BCi+DJQuYVGGSVM#Pa0*H|v*f zk{NnqZn~D3sbEk;(}X%7JODj3`O&5H_u>{^mqdM&EuXFA)SVF2mRPcR`e-xJ*O+j2 z)N@^a{mbR}zID~DtsCntq3*Wb$?|;c)oaV;D+7)lPd~7yBsgNHUh642&NuEc;;Nc- z$ZrQ=4Ok1uI`?We#<|Y4#kK;t*}ORsyL`4D%e#mGEd{;&Tl7-#=s>$|<)->Nm@WfT zuerb^yr&j)k#d4hTb)x9E;fjL%;M`brSlFTK#Hz zaMfa7b25V|!f?PD=}Cyi(A+SasHGBhJvS4NR-ix`?D5JISEZ@A*VC(I#l`pV(~HdE zg+iN&PcMtAS0@{zvA|wjN{nE?aj1xi!`(tuuI&XA-AOF?dTp*XRBU^wJ6JI(w5abP zS$Ujub*asOGE35Qw4HdOkk&?NcGNNxW+by@;+4zp!`EEX)fa6fQCgE8(@|!Ng}q-3 zVc#?-LlLZbYMOBHQ2hnA!yLaX3l%YN3eZ8O@# zCXjE#>D2z??t})4qD%Z%sA4;P$FFZ>2ByBP%kfn9pav9f?SnuSh!<}OAZ9*5G{q@* zXyWb{B-c#ACH<^Bc!u<{y?V^lef9wie+}>@1^QY3$4GCmM&lI$0p6Xp$v!jy7|eCw z7IxJMoq9-oo1^IzlelLISL+(!5&Bz6A1~m32I!CtljWsg*=!10-cQ!~t%Bm;5KHO( zUSP~Gy9$urXL4-0Mu>QJ;`*2mev9x-OsJa}5Mw@U6Wi9w2M+QV1yxr-#HTqeXhGcEEnz@8Kw}S#_38Zg)n~crXCj9D5AVBax}p?{>rN;`t%?C#Vpdq53zjP? zKWEy@(G9f6@q$gvJU!w1UT)>GwXP=9?BBAAKztoIZeWt>kLy9`xx2l>)O;MZKQ(XW zg115Xb{^Nyiu6u)@rb)%Nw%z}u4#D@M|q`Ldz`HXy(fhn-W8Qfl@tYO3Z{1s%Ok2v ztOe`#xbB-tLT7u11i8xnnY7eZzNu<r=2sUD~uY{KAmb*w(MF0)5 zP1CC?%+}z-y_WfG|LjD@+iZ?{@=;%b`n8w>2P=BrjoR-vMx?~Kr|a{WjAIq5w4(zW zzBXcG55OL9SD4?KDa@h_M8YLOUD07O5i^s(g(?u9nU(|z^LIBF%p&9pQ4faeVkHjt zx<|>}cQ3WrY9yCt1jkduHHfOJw3k2xS*P7W$7%yqF_3pmA4m}U1nzmfc#qi#k&If; zo~!DtyKM5cJkuMRTud_*P4@*rxp^JebwWpbwe4(}M!-HPy46Zqyp-3>y`GdaWVUx` zbp}@^;Fq<|DoowGCr@_tux?L~>$W7?^IaO_Adpak@yW#$oH9w@#gGnrDA)9_ z060JX0TZ30>Z5u)iCMVc8c-O&T%r;?{vy^&RsC|2F2|RJ_bWSK7CqdRqOjZBp7jJ9 zHQk_|wbZl-_ud&o^ty6sw1^RRZ$yi(1n(YL*hc=hDFoJ(aRyeX!a`o!pzdDT8sK4< zoZNkxx4Efo*<3cdTz%j*>fgY%l1d<-$rM-$8V|gxXP11yv(9d#VzHLKvy~sCiv6te638eDqN}B9e}>wR>|Y(J$&nrfXJJ6fOt7R4LT4C zDfm?*v^J4-$(-6I>KRg!648bXwiFRzCJVgHVe&Uz79VJ@hz!Xzrw>+wD7+IKrp9Q> z7?BbG+_b}=>M1*w4(+&wd|{OY$WN#URWYVW$KWU1F1kr~-c`o+@~>ud*d;D;`-u+; zo%3($vfMH==?JFfYX}!rj!%!0077_?{?&oxRm&mErtn_v)9FIXT93uvNlAe8rYDaT zo5E&kW>2bKkg_}1lLF?WhTyv1_N}}t8G3f6vwA&yMC0E-LKBG@_T6W7RC!q|iifJ2 z;2M>lm6~uNVQh;=@t1whdOcA~+vnI!!>H~05cyZ0`sT=ZlO2K72?1%W?PZhNn&AsVyp6ulG|2 zig~*3-bz|(H~S^Y|MSn5A_#A2fzF$QTtZFA_e9T-_IfUuC?59V2u&fg{7a!iwNN=5UiZBwBtNg84p%nIXovZ+=qJuDJJ}es z>5;n9UD|ve27i!IC6)BjlgWL7B|~cJ(O%U&NB>75ows&Om$prQI>h0heb1nc^akh{ zh;nFrn5eub4^=g4z5D#BtPPzN;>ve7^u;36D&lex0ey5)YU)hAB;89$?pI(zr8>V* z2kpXZ8>bZLOn>5K3H#7Xs8*=A>@`mX*D$z`dsl$4kJ_B4P|6PP`&c?F=J@3x`GrI) zRJJ%IW?JoT$n}b#FR~AZ@qG%`6@z4azu{Bd=LdC=HIqs=p#`=0F__0>`bz(yfa0{GZU9R1s2ZRQQu5K!_kIE_(Mc&v+@M%@eY0a zTEZ=gM5a3r&sUgzw0ghWBj1!s%%W`SIq~^>IxDFQ)zov0IU+T?qI0|Ck=pW|VZ~^> zs!}fU5BFsX*NQu0nI!)2@aB7$TFKt)+gPnBNDRGRu#4%$GeghDxW=+Q|M0+|kYLU# zod{FoiyxTxSy!cUE|Z36fs`=24r0rDQMkX@Hrb}Azriae(?);Us|&B^8Sm^Sfo%V0 zOLrBB-SKw=s-il$>H>ss5=pf9DAXW2k~W16$2!WjCYqiEvEsL#d(BvOJJb&ZUpv{7 zg*Ve@T?Qg?(ce6(cBbif{U^H>B?_5jrruhI7ps4S6pj#>18(xMd94XD(Qde`aCAf! z7yXdJg_Ll%PMCb7sJ4j%vDnUHQ!7@{S!C>^7Y{P()*;X*HVul`HD6EYGHfJBd3Zll z==4z4q5Es37%t(g|8d&;mxEL+L&UkRBTioNO0~S8Mg>QNof1-xJuljy+ziiJS$c0h zl^Wz6`%*-gSp$>ic}Lw|m9|+q)@~Px%|exjttA2;ZHLl4;64oxwv3F%R+vFa?|22g zIyyfBB|{6zioCA=Pn^m1Zsi2z9>0}1aEd%2*+a@J313Cak zlVr`QBQ?5jSHMCvs!oZjqL&{ z-I7oe;q0(9X04y!B;i`hwxFHh4#{A6g{ve2u`7&3I4jY8Y^sQZPJ(;B>PWSk)WWHqG+lYQmIGacURgrsT99AOyeax>l@|w}2>uC;; zN5jsj`X@u~<`b}TMvEX@CQJ;-Ly;dxJyt50Q2t5Kk2TQTN7u&tLYobJszNG*NDyxC zh2#qK-|xx~emo^a-U`Pi?FS5`Ob=2IjL8BExHus3H`LehX3yW@C&?N9P)fRyjQ7(4 z5&r~T_$uK%{Rc$SScQ`nD+GZvQsOYV$iU~mVV*x=nGxo+eGkU}r5E1Y#{TAgz-mq@ zBwKtC(5{}+b|t~#&PLXtx3H?AaK6eer+r|jy6Jq7kGBDEq+(DtI4)|WBD^r@Qp9TvR(=rBbi7oE z{q1Jo@a7%I~PusiDhV23gc!IXn z^mVpjx@zFl{;0PM#=5Jz1&SnnJCs_pmI>0&Dl2=r#U5hB_D^t=_Qg^p7cZcck_%#JhRnHy>Oh%d_N zHJw&00@4(`Wy27`r7R*$LetF|crx*2SUj`!vik?&=eq*@5X26-YLJY4t#cUhUH8Gj z1I!t(VDY=+#sD5nTog;PfwA499m&!v?6Sdoole5iAO5j zV+SAt_qbTzIah}P;ZW{Vs&iJlGjKOb8okGci4sHZpA$K~O=|tQj(5mV8AjhaA^b^? zSnRW0^mUOKiHATtzRL3Kqqyc_+=KZI--nX!(ENw*u%x#&B~sl{T=2Vj5-Z+D`kQ6m z(XeL~K#_l`ozGD$lOg_i8<>G2N>?>BXJL<3sq8`V*h@qNdTW#uA92{m0~jw;fqdbQ zY1l>@$$rOe;^k$!z}CQg#dz_eOM!X00>WuUX>C<7ya7_{jJb$JkTFk#7gW5E-k#-? z>y@rA#_X`!wRGYD6F~!448NeW zGW?D4!UrZ{YKxb?z*@CY5DFAl`oHVbDo>Z?q3P>+n7u(w3^m*75Bea1wrRP$PWn7+ zz;9h7BAo}!d2Z^bYS6WU`fNLK-~lM47Q3a zWU81&G|bBMcNsR5Mp{+_IRO^7+w~_I&mGTu64~@k-zZREuMw2Mg(iwRl-+j6XjC0X z3DKJlT0VOHOHFKw`@9whhIP-Qz#FPI2W?Vvp~+h-qx$~T)}&R~%HNsyi!%gd7D0yo zm0xeutezRH%f?<FtjNmBQ>JN=xNBA}+5xj*re4#tl#*!^ z_DnfMfOb}`)swJYEuEDMlE!<%{Z?&QxdSwPF>oJ%YhKc!lVzM3(42X_P`xZ~3bejH zq7QL}&Zc;jD0BLaFeI+AlGX$RsR`8Bn;ilj^Cceemg6opwvN`8)uY&UTQfTg=Mn6; zSbI>MEvuon?fvm7$yV*tXfnv4fVxTQ!WDz<<>A&>9NDucN87skNAotEsG&Bi}l-Pq{nZV)5j+ zdz#TWT4<#aEg9b%%sb_*Kfm+}u_L6NgF+VDUe`~A|KG_QE&-iJ4dGlh zdY+@Z%-L!kbOiVFs1gQZaSC;5rE`7t&*)tF0|z~jW*tJ@io-b2@6a;I9?%>cp+#|H zNQi@3C*JF$xS6XPXlXAhXaP&r1`D5S6__jEk01@&2M~~SV}|OTTlg^dfh2R9|wRjNQf0irz|2EBpm>6=6w;Q$ny3R4c90 zUGL3Y4FjE|$HwKe39rMuprMP2r6j81GA2(qe3&!lMvzqi06~cHU`})#Mjf)tKqTE; z1z?$<-gmXLN9&T+x>-=cc^nXG?e#D=50KUBW8l5#tY0ygvdbTO)Xeqy z2krmklWM8YlMO#&3PGyN3I%2f#)hpjnjE!mv8von&)Xtk!Zy^fe(x#LOi;6%Hnh)O zFkn@TvnCB=e{=&D3fbI6s*36tqAd=qLXbRgW{j@uu!-Ua5LE@JV^FehB=x^Ca%mV; z>R?XOaz~#`74`*N_&_**SGThK^f~eKYNbg)!`a>@F|ObVM1@5sqO@mzJ|Oe&-+?%j zrRwBs-IBA>SnJ0=ymJ%v10D?tsprk61eSLpn=7KO%jZN>Od@o9u~bJ}UzV&%ovbUC zvJlmDT*Og7Y-A}@l>l=l#eJ_7Z((Xa<+FW%07RYyOG5zLhAxm(rnWt09)HQ5Q-hbR zU+t9)WK}GRF6S8A52x$o&xb?{SgDIgGz-+bYSg=)U=knN1B)P8Ekk|U2mgfmx5#Fs zq91XM?=jWV8jxtUd|#bx1&h{-RUT%nZ_)LMxhZwjJTdSdC^ggfj??~pB*Z@>;Yh{o zS5>O((Dsw>_dn^}AKViB0w8WRKA9LCZCueR542g1YohG{>F`?Rm$f$diqh4u;S4dJN3C0 zCcqKjJjHIQbo>a$1^cU7XTT8kBCgIHX-%c{2G6)dRA;9Zh5D1i@W)4%eg|O2uJ$|CVdM4}eE$!;D!g z%;c@)&R6s~dItTUPP+Kl@G|>A$Vs1qh#QwB$wV~Ujkj(QkA#z_{O0?bgl{F?oR`I$ zF+jz+1KwcYar9M6Chp5Jp@m$fJPSvU4F4J2GoFf~qxR9^j-Ze48;kAf#}s}m<9Q35 zOMJzF@xS?(uD(|(S2#bmTifg5qS?mu%-%k5#W@5-`DabQ9SHV`ac~X}rgT1~{e3z) zIGeoWG;*hLHGlg;pVEbJxbijjgxll~$u0SBa(Z)~C3}Ex$)9?uK`$4s901t1V>Is! zUecYEJLQp(d%OOLLET8M&%^N%O#J9evqvt=)}zhK!hT`p0jl5`wo~Xit5e=*XLWPIafS zpx{F^P!}(MI{Kk-3DU1X$kCSb`9Gg_`mZShUKRY4e4U=V!jYm%I1IaQsl-DMgRlM= zX#PyFDL=2=h`D~`V+#}#Vr5ph&UDuPp!!AwB*n2jF!E94wtq5S9E1x7bzX=!P&#+s@lX9K1AbzI(A`sKHd zACLUCLadn^O*UTRPwVWLfw9L9%zs=Y#L~t_KIMLz)^(boLsdV2taab#C$a02Tj27m zfB(jhA6JqCzh*UOY0r7&Et6lw4$yyG#2L7O0gkv8G)25B0vc&0;XA;@6p#gsA`Z6 zPuJj6a6Z-*Iec4$*pT$d`|KyY=H>-<5ni2TpxofswkcHWT$s`}1llm^0~%eYPETFY zuWO*?d?>SeAKjUIFva724Z6D(IKuZvcWmM{k06kO1d7}MbUap76f`me`wmnXEm~Gt zQ)Sjg{if4E7t)T5H935Ml3IM4IM*J>MZicQi#mLZ>lfYLyvVtxO}Ms^(7WJc+FB>17C0 z?zNx%_J6!Vi2s??=HqBfQ%B(SaUgi>`EduK=S?%`9(_K~u+J(hf*V!@n&|f@y;$H= z?w>mgtxzAbTgV8U>_GGCo7!_mfJGEu2Vdys%=>(Sc0F_ibiCX*{P{G7ACJnQ!fb+{ zHK)psycR5eeg3Ln91awx&bam1hi;2}z`jOl=9`up-G}c3g59w@Lg&%Teqj8y#>jz- zuDI=VXl#Yo9Ru0uxEzv=c7xi~>P4&A+UJX=Kw$G|Cf(OC`1-`Ld>|?)-3A<^Zz*uv zS{I>&x69o=+(uI*0Ics(-E>d@V6||f%fkRSh;$t51Acjt<;!Kwn@9wO*IH{aq#PM=uX906~cr)@aO>vkH{uj=gCb23nFsz=ur4 zsFIAQqC*F;aJB$G9G-d7i^ZsGhZ9@+psN`^tA%h&mw=X(U3_l}cCx&t0Hnp*uqZTl;|?LH%l zM0lBH!NPmLBIn6KA?fBi60v(qQIP_@4=%+FnL;TYE?P%~^C#2r zAF52a5eX%nB0J*A8kkji(TVVU-cMnhj74A=Q93}}VjGUvIG=cL0k{YaweGGp^XSr) zjhMSdmR3!}tV{{q>KMaZ$&28<@fLwL}ZGZ+0@7#JR4Fh`A$P0_TKA9pFvTd4q6LXxK@`RiUZt;9DI< zqDj-fdPA9ADc%OaXT$n|=r7T+LqsIFuve?uB*)4}bM)(nMa<)neGs4doA@=z+8qD| z;oA7VW*0EBV?su%tkcQSw!Gt!qJZ3~8iFZ9XMLn!rIBgSf(+d7s-*C4B4M z6_p`QLI;7YwQH8E)~Q(M(P;w>>5}%hC}FVD zZ9B7_bVSrgPwS{N5$-_ODNcA!e+TU0y+{)YuzXU9%7P5nzJ8&_eQYBmxdRL)N{uQu zd$lpi!&cci7$8-#$u5~7MJY;p$I8WvLlZXDnr47f={4R=6E~BDmuG{E$l%lncafv?Y0-Z<3X8?&Hq3fLVNyG<*9Am=c7m#$p13sk5s)s!!G zRe^!W!naALgFXg7&W(=urKSB$3C&%os-B5PN@uj~6C9|{)59&copgN5THrhzkFfNa zvS5Y&44}JQaP{NIE5Wjh1{-k+Y&kt@IU27%eTmWT8PP1Ysyc&nA^4VfE|I&HRR%4u z@g3D^sll!O@undOU}Tp#+?bz-!6VGUIAp|OfDUT%OcdDOWCYd#simLDx$;2}2(!=N zXvpv8m|(d#i_P|M>g-e3n|il3_nQ(9I)lAVFHHbfs%t>_n&p8YrgVQm*`9~`2n-?H zvs*%vCn=;p>eHpZ3%X0hQV2v+?P4!TNR0aen(whA; z49@R5apKZ1W+y-50OFqe(VP5jc+Ny$fzi!Y3}zQ8D(Ry`SGk4-QrwKK#xhBd5C8SkxmBKq+*u#R;*!z*4h>g^^; zC){(!n2vwSZ&cgXqg}CV`lACfI_RL2xd1x=!;9~4`IlKg9+hFS+d~j-0TH=|*$9vJ zSp=Hpspa8Ax2vZ|4X0Z|=q2=}*&t4kf| z>Q0T;#{@sLr%UO+%zh@{ws5!C#0NNk9#m$?#8GJUH}%g$GXzU5QyT|Oqiv?|CSa3; zRjFm@dEkk_NoTGuzpL2v$(3x02`RhnmpOl45-8b+6RtwMzts|(y(?c`Kx>fO&l5X9 zfo^%vT4TYhnX8x|Y*j(gDgK05Cq^uMFArq9iC+Dzc|cOICb;^RZ`I}*?f&>!+WZ-! z*|2BXoa-NGOcyznR^%2^?Y>jqToItmSX8r3NlTN>A1E9Kc1GFOfi77h8_M&u`kP(X{_#5+ynV!gx!9rnm%G1SoBTd7KaW<@U)x>54_kOJR=trE&p}K+fs$e~Vs|bXhgh7oD+rr3p6wtK`PX_Sq zX>X_d^U601Gg9-IaCqjK+-*d)P$LN@N^4lA@jgn z9rW>Y-&q{_y7PywTc23`cn3$$0`X1xf!k*<1rg68^_iZ4P5Y`5q!&DVjhczZ#O7!| zkdbyGYAOc6?qv;V#w2@3+U_mfHyJD0QBCKnfoX8!l%dZ)u#0LE`DRyzQz@A0`zTD4 zDg66O7vswt&Ay{x&L+QQ8cK=x5}8Du6sgvc-&}@@?SFra>m`P_q4(tHBf9@CZ+(L} z;W(A2tR`2RZu)J=*1EZ677bWtKapmc^jwkf?!X75Z;SyoZycd3)9DbZvk`jZlQ1yG6A4j%fin&8SKdnI!?ruvU;04J82S3%yDA zZa5bNxCBQ;WA}Y$Q6QIZ1u~0i4U2U&xMe!>#q?ya`4w#sf>}(_W;(?h*vcu%i!$6u zhrCxjM!StQ<$b-w+LgPneszLR+h-pb7C2Eb_9)LJ-TP)edG6_y{RaFd1*fOg z3Qpx{J4leN0q5~`M12BvW}t&<{~K}QHT<5nz>{Ml(Cw7MLL~X~j9Oo09LSslu2FzC zCE|LrL+}j9@Uhc$9}vZ372M1$whxPQiC}6Z^o8HyIfhG*$$3Sb=T$X?#D;D^$deLS zp!QUxb9-=+EZ%)S%@!FbIPjU!v9S7a_ZHTf5O0|D`>y&+91KVyZB!r19DvpUs_MA_ zls$atFT%?ARitf9*iN_ufcd}xN<%h`c7Et_{JOWd(y!P^jN!)Bon=PC@x zpg!($&J@s!#LsVmBBuQk1&TZcI5paA0i~^dcr>>V1VNFN9e5yhJDTB>=_ld>E*1#l zlz@B3SR+q|-;0|}G|&7K^*LroRJ#!(8?OmK)*a+4@uT$r@yVH;lA?H1k<6TG`Nis+ihdi`qR82~F5hMnY|M_dNO3vwE0Nxyxh4{8 z5^A2KGz$EY3g`q0-(ans96{Ly1HEqNFGgma5(atJ`{wyzVmCRG4i2(Wv(R0m@1XE$ z1vWW4tr*aI*U%oMS3GDds^MB?H(J$sL*fk&1mR^@vHCn?9AUll814mnpaeiKpb!+j zu^Qd-82h~XY&xE+^|XVX0wtg*Z!roMGc|36h2`F3rPDU(%P}-f3R7=-kZh#lqiX}Q zSRtc^fG-c?Hl(W`Dd`JfEDeTA9At;r+B`tmOw>PZU9sBGi6F;zLzq|K1qR!Ls%K`H zhT-o$=wAzK_70x-9Yo&*$W9G`C6%M=_w*YHkEgLB%86H1^S5P0iofUff>GT2WI2Ta zUL&TbJ2cYXnuKezt{A3GSKN)6WzA?HPhR5_F&$nM8L{>&pB8)bfLQEBByc_10U`Td zvF&gRXlS4hd;8Ma235??cgZO1n{xkqP)(QVZZ|d@4pqV*;}Xt=#nACsC7v4hSSr*| zdzexmFSRdltYbzff%uRZ_y_h>bCX6XWFOoRBY0i{0Vh(@je+K#E0I_MVF;YAFp6~uMLTL;6 zheLvY#)WsxfTtlb&-{&nK`G!7A~-&ye*}_WKmCs`LNMD*Z4CX*EmH!T;Qy05`J1M> z`yfp1=$Q(EtKwRAM6fry|2iN)s@Het0sYPU?ZMFpkoevw{0!$&3)(#ThuCfb2?p)s z%p>7{A4nRWo7~Wc?z#KLt-rY&{t5_t*j;{mT=XZFmP0$I{#q3-RQ4>k6PjzQ+e<~R zf7B5Ys=qYWAd)<2nu32 zY!Ci({FMCh4DIz&W-I{)f&NhA*;OG@F#hi7VMPiX3&Po)2IM1O24zFVM6e^fl%`ZrS|2G2I6?`omAnH|lM zaTpN0QdkD{*hTsybrTTybwEniyLJA?IOG*pyz{?t5#OFCgk)Wybs+*QfV&DQK+9auA_gb=@j7bX_z<;Lw^2>1TY*H+q?fO7xAn5-*_BIdQuV# zh)}BkFXr!mPdz|SermY;n_cSi-%Rvc(Z_+KzsIcf4y+a_htA)u%_eL-3Am{Jo1nyo zl~Vq1T;u}{=YtSiTifiG-oC>R2Ke(?PhSFAkmNn5qs?jHd3b5Py}eHCVd;zN{f7zR z{|-A}Pc!?8U#1{CI)O810!w1BF)Zuo*dC$+VqnY-&ZGBeI23ytTrh|B&!@ot*k!Pv zm{Y*2d9;>8`S1Vy(}slIi~OpfI{oZNFL7azp0t0dyLr_5^|d7Be2`PKR0YKO$QuDJ z{os@{7ekZ8BWKYRsVAtnrU4A+9NiBZdgLRxb(@Bb<+I`~Ei7b(1O$*hk`6~p_yW1$ zV|&2t!NRmXZi(4_YtaHrkd={ksrXgpt~j(Y9`UrP!T>?Vo^HVhio*aSZBQ|_g5#ak zge2YX4jJp{jr>U3e)*)T0;%00p<;~`8ujevtolAN4YYNx{`o!k<)Xk5O&|YYjK6;y zcRu+65q%sDV4-0^2xraiP6Osj0Bu2#b=`MoeI^?40z&qxNOdVhgdJfZXQnqW<1nwvW_VN@ zr0IFzTm- zeh4<&OCI!(oC9v>06i1$r#T~r^;dV1toRaoIr_~-8JOP*kE4QPOiFZ2>M4> z7c%n|qNz7NDdH-c82c{)MZs6pvGaGx?=5IC0KZT`UnW3Qp)L6w zr&|I-mu3Ngf4oD3FB<4T_KN{w_o7@@ReS~Op299QnbZZx zt$wr1;g?`Sc5xZO)84I#V;CSz=rcqlwaoyA;B|HjAbaWL`3_O5sNQAPx7RbV?NQDB zF!E%>S##n!4m3%e&N@Za_aX3P_pj37M{j^}UUt#^KfRr2Sd&Q`??D6<1?);^brAt6 zQl*0hR*))))Sz^d08*tSfQVR7Q2~X}q}PNRdMGL=y|++BdI<_4kPtYNeRtn|ca=R~ z&UO5hOOmI|Gc(W3egE$Nztz92#{=C@RG}L7>PbW zq&QidXKF~S`N!pau;0;h(iaKsmEc6S9TXAscl{cllQ4%&MqJVd=&Jc?dPFHO{}Rwv zq)zb*!w0vGbZE(FNf64+!S4X;s#cJv&GA0J_!IUpQ!w~W$m;g+g3xbzOjI8eA`nlI zqoud}OF`kyH1|bs5I8jub!OFFd7ZfRVGFdo=Yp`uI*6Bec5|+IfD-;~7xso+fOTjP zzfJ0JRM1R}LD)p(K&Jm-8?X{BeYAQXl& zzG?a~7r;Uq&7jRKON-*nksNDw4>7A5fd;e zCU+vzkg6MX?AO8M)Y^_*mn!;n`sr6?o?feduLKoD1x<`!2UzdsRH7-DNY%@)VR@01 zAy;ikVa0a4(rS36Ak8JN&%#58G4eoBW>~mpn~1R#CEw|5?=>OOToaejDY;YG6w}0VfQ-Cr&92~ct(ZG22SM1q zTL=J=O9c^x+?)k46R`DJ>NV)MOiT>W={F(e4QX(}zXJ4X=T};cQ%xRpvlSuFmq19l!dmMY93Q!x%LrSA^+g2f3WQ9p&%zcmY z`5uvcSz5gWaQ;RTu=k679G9=W_=?&IU%=|{q!uRm*Y&pf*hoFl$GwQ!1}S-^qW zyP#E4^)di#4Af*{!eam$I9N)HRLWX>O|RMba)72$R{n|~egY|XIk*-SNngBhsvHzZ zmc0^Z$(#pdO`4jm;f?(7G-V)YZh;2sBa1aJ5CZG`cdn-6rdH54kQ$TVELB@deXaZC ze*HJ7vR%_L4-kfR;C_O}@ET^$sW;GgI$-`07p^kF6=UT+XeZAXF?+iKkc9QUL)R1U zHgJe_kM+~9@@Q3KO143@I~+;Dk5!CUj9G0N)31(gP0PU>l=WB4LJG6J{S3Pu)Duk* zec)~Sx}rFS1d?x>^veL@8e{x4IfJ)PQ<7S%RcH8(!tDQTSwYv*NBOt?c%Uymb$>KA zBNs{ia;I_$+k6qCY~ADS)lP0t<1IW_n~)=vi6JPM$S|h#*AaBhBtz)tBcxIhb@7!GZ~N^%tEE{XOPd` z7+~K+2GUp>!B<7urTU}lxR_`>(NNMJIA>6o)ls?p)ca%3EV)AYe4}qGe2%BaH=-YdaU`57nz|?cO?_1!p53C{8EU7{g86Jky>i{yEt&FYNp%TPwM%q6C z^&Wl$`YVBKW|}ka*kcNDGPSv`?uP3~C7TTzIkdCUsA^HV!hQT#kw9p6{8yG6`kmsjgR6M-UW#nL}LDxm{MWLmyq&`X_cfb+yN;yj9S{B#UlGw_b{Os zeAHP@7lo=m&H^)r+nICs?6M&Y=VliJKOSOp%tltE4@FBhzhCVwgs~@kViv)2Vg5_( zdKyG=);Q#0Q=8mKn93?-wW%Bv6`HrY(tict{r)vgC+(a*I^1vU>lCw1Z52(c_5E$K*Hc%{uQCDrGc$tK^Fy=hajIv_<3J`6&KoO`hmt0MwVVt&^nr?=bA<|+ zc}ggu50P_81o5%8Ls!QR-0uGL)!DBstv#m3ff%7RlbfDP%D3$T*9FxD)nujT=U%7~ zaBVoO2S{mfAfAJSyxv=hx>@ySu6}As?Qfs!)R=v9F+(rSE2`yLgt_A?hv`?Ce^|d} zi-Br605C4xx$_ZBBXwcwejjCyG0&Z=^Yvkq#bd={Spzbbx(}1u?M*Y_*Pd7Hz^=WO zopF(Ro3gYB>>i!C++L6e#tnz%4iNJ%=G3NPIe4&7Py_gf1g*Ki`a$e59e#LMuYaM)&i^dz;su|u5k>Uz!zlD~QeX20@kn9vF zJD%9t9^L)h!T{A9*5}jUj=IJzTEwyG#Pz7*)aFirVNe3GxAc@`OI>DOs*#x_D*@7sYjY_2SH7ibB;4oK6<5U2z59_W`7ABeB4; z13B40#MG9u+T~DEQX*qx8UJRPKK?bL>~+EUF)qaRmhv+8>T%>M2k81cJx}C>&oj|8$6Hkw z^WP8Uy72A2(p(}=sV&suYwbp)w_|F~PANoEMtZ0DV}4wX86PhLD)vJk5}(NPe?%8Q zdp=;qmLg_$Dv3m29DurZ^>|(UIDGPEtvpPYOMd7fbxN)59Pm^Wf;D(HC{=$xkz`UU zS6o<)nvb_2{jO@G4Mw z#zR?nkeS#T#CFn7>HG#*M7}LJACN+kLbf(lV~9x&e1X`7JezzkqLd1I>`Z?*!#kKx z;xBpTTCe>#Bt8-JFl5*uBsrBBbb2eUft2FkgmDP;iulHVb}^^6hH#RprB`!i=FMC} zQ;90&Ku9J5pphq+PsjFot>Js9CsiU_fOXDHKVO&?JSN`}^=&_~H-vet$?F`cvYC$= z^W)Z5t%Q+NLCqCfBS;pJIK}kkSZZ-eQi^qCp;b}Ir9ErdaLr;-#&HiB^EXQK(B{@F zmT=#2N8EkS^4R+>EJ;FKaxj*?6gpHJW?-73We7R2wo>mZ_a>^(z-@r^%po5i+uJFr z9Q3*Gm=X!E@9i~E-!cO-$e~Y%yc7<0P;8n>&I^w-h|Tx!9l&^@foPb)mP%wOrWT4# zudN_(Z7W=27v2g$FfCr(Gbr0>j}%yja%@l+>aJW6-D;U;Sw30RdJn>w3-^sX_kM&A zx{=*(YIwFBdtAk{P$2UQs{J0JT{DJ!do@vUNvi)nXI@H}&Gvw&-mfw|VFZs+-x)j4 z&&QM4`MBqT^)ApRvYN^#@;Avp32!V3MHsHz-73QOFV8t*ZnOHCTYc<3)9%BT-Jk3C z`I=5ih4E$OQCU1p%7h^R2w*-IcPG3j<2hHPoak<$mX?`fds&8POZ=myX))PEWk79| zD&^*3h8D9s(cS=ItE4v?K|N5Nuzo3wA;DG11 zEp`>{=RZ1xcFi_Nt0O6Eb2dXBi=l22w|XbniPhA*v&J?oz}o2jGmj+lE!p2V#h$0X zhvrc}v;WHFwc&C)w+_uYGswiczL3RMgJE|mLd9#|bIsPMMBBX+wJ7bODCDBh)OjT! z5Fu_7qB3^(6EOVAcG@xPkIr&08ClIUUv#IW!C=^`POmZ1hw=OKRQ;cNb3F-sR5DiC3~7I=FpSDAE_31nEj zo&FUo8;av6t^m@Q?eCE(+FFmOX0U-vBI{6X_#~l77(SpO?t#AAI zvyl3IKs7;^^FycoxWigMN)$U#KXBbTg+GNULC0A|Ka-h9_Kf1Z`-j7tZ)wZ^>e7sM{_kGp*suRE-|bMkE3O2=v^j(@`1UaCqOmG5o|z4 z818S3mm^0EAz=1tWqHALSj^5p)Cy{zGs=1*2l1&b8uhtcQ8{-d1nsOgPTR&YmQfjk zY@W+EoV#c4nDYSv=Q~w_n8)o$F&XxjbTJ5+KNq-=pQj$;g$ZLAaOUzm^e#--uQnz4 z0e-SBoasuh!}XA&c}k<)lAikAn)a1Y4{tyI;I9Q!_UeBFWbhXukR zP##53<|g0P$gdF6hck~z1FerMeI;PM0k!=?tIdI5Hf>T-i}p$x6V*SvE}WD5Wi(VB z-1U?WcLQxuBiS`Tyw3~ReDn%NO) zJ^>ZrF0MALsjRoT{j*liR`c&`H+_p|V}4u`lWRv9ine+@FG(S{_$h<;xI0_Zt>0Mv zxHev3;US&^JkVQj0>JK0zVvpHeF}AIKGSb9+X-Ie*gavLqTt$FP16XLx=AX@pOl$> zPwtXE^|nBF>zQ+?|JXfIYCaPJ9jxp%jO5yMzMb>DrnO5CFZmmM{?S62NN+tz%UIb2 zo(Dac_5K3SmyjtkJQRbp*3X;7cQOkMjFqZoi=v@%#yIRS@KmujZkETybyF~{b6qY-{u0wEWd46{iKjFTL9S8oW;~e73L$xKlgpp6^Vdq z_g`5pUIdHC@Vw2jQooDi{UrjIj!+!^0Y(fA;9~~F`&%UPEwT=hQ>mdGz24*f;bMb( zLRpl^t3@)4-PmxQ2EPfEL?um^WFpzqpBdn;_1DMU`Ia}}H-Y*RrHT3Wltx$4Bw*9k&1z-W(G#8S2viY%`8 zRbvSxyJn@Ul)?}m%~fJ;Qe-q}4_9+<{eH(WY&v2ndg3m@t9^iz{5WTZr30_-p0eb` z#7|Y>aLvPH%?jvQOkYN+pdQAq7bk2#ut1$H8I}VN9f5wJy#^J zP_l{K-dEpsN?`|Zb;lkGoL?NrKDG<4r;AlGPCpHc+Sh>}8khr-;ImdDH5k}n6-%x# z#dN2hKL;Y-4+Z#~0&i5>x8U2#TaQrLFL;wa;YmddADz8>>f3|R1%>UCI>GLC)*{U+ zTWmrk9Efrjbz?OG$RH>1g3wiMVj3->Mo^qP<$1mWLQLY%ii(}UEV{eeEtF(LJxj=& zyB(=@WpkU%7NZ)7?6FjbwXQZ7*`fU*Q3;QkaUO`}WFCR*RiIw36Zw7tLuiAEngm%b zJg8|a2x&MwG`nSYWZU`O?@O%0c=6Sry`4q%--LW{&e!9j6UoHxoJGOz;J{A4636+d zCpzwNUlF}+5nw(w2r{>!w^0Dst81m@LPlzzfWXhao<=LR6gZh)SSEjg7D)y1N>^$A zc_0yO0pZe@2IS}Kf;vNz6$6A9=)7v+Er6VSIP2M1upwz1xZdqp&g8QQ#O(YbkJazM zCHe>4m0kQ9m<~z+CAhp!wY=~7!In0G0@koR?^26EUJ#^rj4-PvG3YfgKi+@zb}t>B zybjHsyjRj(YIHy?`K)F4x))B_Z%u+G9A7|eY6UeyTX$@<@7o@Hki{5v>eri;01(z5 zLDK3hz+$bXlF_>COs1<+5^em81#&Y`}%~oG&)EiMYjs0_%S-nhf~J zJ7;|(u1ueP9a3EDBRN|6sV65hw_8v_4gaH2=p|B?%4Lw&@>=ymz+(h$LO{(AkZp-J zPxmkaj+t?tL}QT|TpywU_?1>HIVN&GFk)bUTHCTlI`cg$M{EWoWoipqXDhcW5+Abj zEvNev=ckxTd%diH&*Df@5%%k2u7kampR7R`_%3yo+R2|iQ33(s?G{kD>LT5dwf?UC zDbtN!te{>(99V6zUe`?Ytx4PeQ%5P2}MFl$?VLk;^h~(BMLLkaB zGh*|Rc9+Inhr9=*^Gk4vuguWucX}cHEh=B1wwbQiY)y%`#zN6fi|>!w6fHhO)E*ys zL973JnP?`@Hih&lXBcPyF(atYut&(wIo&Xe^J3(8wmP;CF^BJ+_k{jgu8Ru#PV)<- zc@oH?3exynfahd*tAcm)u`**iap_`Hy#0qqhv9uoV>LAeQ;x}-vQmTGfm`2bARJTko-m5)KkAS7cCXNPwd9mX@WIPc`?ijn}c^I2rwkN!p15JLN4U zINn!6YGYY9GAu?Uqvs~x=5D=D2oE(KwWY8b_%MFgyEyUSJapR?8$djiOe0%c0un1T zZR)RVjQ{qO$dfVI()XQlwj@E6$954DGz`TqIpRcprg%4K1*+iGJWbT)Cu~9cfAa2Z z+T-*mm`hw`vC?79vaBJ4ESH2+Zkb}5dRS>e{nYC|b{4dd)W9qG$8|b(Bw+@{gNMkI z=YG}S0Li;k=A4ao&0#a?@PY5A#1`ZaE|cL4C6RR1_c2XoY^Xhtybo!oELz$HhUuJT zF~Y}A&@fzpH)mqwo~;zb4R!r0MlofN3Yab@I5roTwFmLu6kx2-29aNc-R8n-E+k@h zC=~wjxQQn_pQkc_<2l9LW4zqXcTPphhT(3d*bT9FiygI zdXg|Kii*3k7>=Ga5fU;_?dGnKlMzkTDm%aE#*Y)gA5f;oNdOMc`uv;X%yDoUqtN*H zpM+liXqO7m`WiJ17k34ogR}Pmrb~En@z|eF?5|q!|9nd71W1VMnqtuEu1%Nu1T??# zZHJ5>LuLQvCIMC5NuX=v+x0ZUduU*(Cm0LH-fg}J1>BywXb^KqB@h5d|%zl6Op2y$e2s)?S(D9joT`$@B5&(L2EST7K1=Eu{0p9cf zxV?!pM;aMA+4FMPO$CjT$VIS6SyImyj}fieLw9$ znb$z?f_^V_)Bm$6{v^HriwO~F`ft_$s{eKtlo|AYwVO!V^bvWd)DsCXHRpMEw|`;2PJ;$cv2beJT?Q+i z#zJQ`*VX;^sr>V*rJvBIzVfcPpQ%Rv-UvX^G1bh>tV>5nN5)Hk_nV^I^Z)3HgYNl% z>o$La&aaspcbEO+*T>KX!$Q8<>w<`QK{!XvSf_n;LUq&7iQNHVX|!5cdybPa{{W)WK4M+{m7KgMvT^-G9`bM%U4D$`>Da&3& z?fxqDgaxxDs*h1;<4^jnSJiwK_E}w)m!p*a3|#x8FY4#hu9M)mmULeatE|wb?BnbY zi-zw$G|Fk;7gU4H_?*kwt@~f7&0y00m`ZNze4P`);zeOkhoLwl`Xf+iV+wwV9nTVoI0xcobg2+DY?&Tu39Y^s1T#qUz3 zpr+OqnB|OH6?rcw9hje}#GIG|LL|nkAnjasd7(GwO{eOQByCRum=0LV&Q|;9PWx4F zEJNXbCc7=x-d$evWKz5w_fPSon<%#)1a1(ut#of9tiGS)AJsI5r<%TUB z4sjEG{%f6@tt=wB`fIXUrs~hZu2ICX7jQu%fEuZ}Ad**Xs7{^hQP~3&@+4^H+2vp_ z-vR)Lw`E5e=Yg}{r&r4#Kd_2@jhR4fc3ftU;oBN#KCroODA0$vGSa`T^^tHJ1bL^w zzSi>=SqA&x&XD#mr5`x|Ir;E+B!Ce*(`oF6917lHJw=e%wmr(;Wg?{(2tX(l4Seuz zvz`!j0lTY{z3(5;v~O#synbOhXJf^FZ5s$d*)JN?``WQJqR`zSh}Qzl^-1z-)Z{J2A4rsr)w0PK|^E1=^N598&gW2mPzF=iaab`1Wo$ z&r~~Yvqtl;@F@eDPc^1MEJv~F-N}GBl@OfS7y_6Z=2N&ACRzbG2U)q>@29Ekb`PZY zMsB04*M|h6xO0deUpM|a*JUyTu-p`VN9p3uVr5Zl(l^1F^3|G8w?gQsI~V8W)plx% zr0s!UHu5|wHW+W`MRk` zt4z_low^rIEw?eg`iB9l2H%&uSo@Y%R55fh_Q><(h0YMITOuUkP^OuZ<9pV2ieTWu zwhg@D-K^Q6sIE4uZhs5hcfF*tcD6I670}Fc>nRY_xm5)S1xT5-9MRq)y^+`?7Y0m# zO$wxJbn!<+24`(hol&r0aNW%+rw@Qu071GSUcL5eZnU~G2e6#Cqt<`-PCTlobUPZ< zXcv*^b1Eg7fqYbWyw@}ys14N)gIJA=KP{&Mgw$k!wp9VpfqCKN;^N)B7{X(+gQKO@ zny%540kKqR(-DM$1X4?K=vAz1=}KE2KUyb8sNXTJzY=Up^#{sHEvKKJzvIablxJ{* z!1FI8@3_hw!}6fk@Q2dWLScn5t7^X(Ajg6ieGUl$IJBZa>vER+c~#0_dpHyHARhb) zO7o)x!Lj43z(+2ICeWPuoD4#bEG$Za-#-hwxJ>{75DxePz+`cXXP@aLEkx(F0tt}b zk-ocg7pF3gTu^9ei&u0oZ>7ZqHA0T&v{fzwO)uY4)u@gRu=EyG}+MhAl{{;Bp6lO`E6@Pb?0sxf~HnC>j%{5#}F#7ZuWSYEP0KT z3Xi2ivlcSjJ2;`aFN?LL^%7@jCtb$%d9O9^r2_q_)?r^wo&#^s391DG%2dI12?)4g zFN{$}Nk>ST3ad1%$~>L93((S!E3IPR5x5a1*|t#)LMw29kn3?R-c+7zT#*C9X2|$Lz9s~~`jfNd%iQWRt zIh(`P?%!440H&D2zX4!JD4YD(2uEL#Nla=Mcw=!ylwA;DekbMlZMOQkHi7@$+Wc_9 zxYTfZx(^;M=g}mTQ5Iw$V2UPv+&kMnS8VE9O>C+1U9~uLO~9z99_j{w(6?YV8a*d5 ziwA6W?1g<(y(SPiP28)dtaSmTrEFU8BGq5Ipsni7$4?2H(=Q6nEl_&7hc+4EB2Rrl~}BlG8@@Snkaa z2_M2h3^H+u0h(VS4FUD&P-iY56pVAbB+awF+Rfq)MO9=RbL3S;R+*;~j|cQ$Mg?w7 z^5Oi~vYDl5avKeZ6QadO{en`Ds8Pwp0}iM^Ud_KQl=LV|!UiOc?gY%MKi~xb9fM_8jq4su-OJN>zA66O(Xla17*v7r!)Ms$oi5 z>o;3nbE_{k>|b3FtbRCywy6dR{Vl*X&(1K*$dHr^u*1YPj@JbTiRJqFR&{Qc4AgmB znj#qK-TekLbbB(>V#|r*zV|YW%Dpr_lK`1`5w#r(za_e84!3C&=pxm zk`kAI<6GO-S3%WI^eWbU&l*|a1x|DA&}E%O@n;7P%PJdvC#?g)NDq%eyEhyom;KyQ z)5Bij<|>=y>?Xq|xgyDf`)f{WQC-A7Dno*BafBrI_G_dBTn$w@lK@e6?8dzNvSTu* zv?ibN&w5Jz(mk?VWtU3@UXW`%%Xegz2>ENwiHKL&nCRuP*O+!K(na4F_p#85jp{irC z!DWu7TN^#?OmXgFB)pbiulzzB%p2bIrC8MA6;{2ySTy+X(&dGLE`EcZ4VjBzh>lQ? z0aT}+sU`*fJ3L7lKb`WFH7VV0hQQ2q9k7YoF;|*BTVb`*HszxLoaR~f8wxf^v6I#3 z%ku%<3!sqC9AnPHWW9?6fIk37^Wx=4;Bv%AVJq_RkP;_UZ(>1a4^dL~)rv;{XUc4E z^z=16Y`z$_Cg0ZIRGsJzR&U|d#&$d*5U5rd)NFXIA=a`fwTWR3jNg_3ZyP>M@%1a= z=xxJu4FugQ8_-!$yp&iX)pqfW*$H%pzu@*}v4CdRy2;7T3wS!`Vl;M!JU({=Xrw&Ixf0;bep%Aipq;x9ef-p zevC3=k?p^M$R&j2M^Si;mgVRC#~|XApkI}w<>1=LG~m~~Q%n`mMA^0c5GowqJ74(W zS>c2r!6TPLVUBzCOC@G1pv=&Dx@ycZO=_0vy;|MSoR;}DOj4;gaua>9@1911GoMI~ zvFX!!50IkMtlR`O7>k8{vJhnc3Nes7rDKyO-~=YY}bfJ1+FZX-wy zCVw0RHQ$TdAiJwEIMexLOlKq9h6~VSprk6`ZXZb{Lr;m`d=lJf`3mLJ%{`Z7cA{QpOA5r^^e&N%D^ZSm zc&%xgjcTlNbs=Xn{H~poNV7uL+;#`9zVKWc`b{x)qYC9x@tGU3*7`DN!?oZsqsit_(JLMr`D^bz3YjrMu6Q`8SMg6Ik{GRiXsb?fj@;~ zK~3VMfQ!+dMkYT?)hJk}!|AN7^H1jkC}yJ*ez((+WoYMF5tJ!OYlDNm8RDc^fxV_9 zU>ATzc75MaN>t}dOk6^2Rq9dK;(xuISceJsqXcz5n@XB$s_iS|bPq43l>i}3@+#)D z#fnsLRl~n8A_fT4h9hw6F~{74GEtldw1*>qfAFCU$_pniPGGz)?c5=xxmo^D>M(n5 zkJrg0c82A|ww?M#iS4hs>g0Tk*gz4XO*W(<(RO->$O2zFoMe&}9-u@2N#Y_0)6L_1 zjU*(pSb+luW2kXLJ@eyUJ+GAG#m3i8M9FrZotnN4B4MiZW zo5H+zLM?H^*HP^QO$(G*%7H>}SC0-{x0M^nH9tg2vEBh=vp*f_nQM+JrRgHh-Y|W6 z_>4xGy3Bjl(8h!%&Ik1PR5hD2WkxIGhnIBhu1W{xY7J!OJ~7++P1VywK$+f z+wM4aO($?;)AMXQp#$%^QX`!z`>5Pb1wHsG?Ix%09{yOW4d2P%GO0$8Te!YLi zFy%9ITZuSpeXgY8pxHglLQzSVmWZ8a@62y@!Je*j36MLF1(K(bs^93UwG1{3FR`pZ z4-H<5h3F-Euzu$Zd^A%!K~RpWFQ`d(D|*koUzicl5x0s!>Sohe3(3l`mKH$TI6w;ez)2idH9mPegS9wc?jRE`@^W3dwWkHIzkO{;J zZx|zi(0jnXF8zaf8Hm^QShAAQPepI1^Ijav#iwLcJ{-Ki7PYSp${fYk(a12f{42}G zQKsvKc4(*+3tHq8+B#{^MVye76-Rf|6C#5V*=_=Q>U`7jr1q%ZoF$>BMhphogRhRE zk)`SHSNAcju{;vlmy&dSAip}}N zR(-Eeh!gH_Z|8;EGCoCgBa*`{?7{@8dumA{UdlDd3m8t3y6CdI>Z!Q#?r;n65P;TN@}eE_^XSJ!z2TIe(-?xMB zCt{dmDnA8hMa zb@Ij9uu~p}q(7YDS^w4zxIKTPiYp-&&iqEydG#rw9@sPgA^?Aom*pYwR$sZd*$P4u z3)=l(&8{)1vvFUPm``T@HE`oSU-A)6WH9iZe(@X%VhN<8lzzkhr?UEA9!Sm;5BbO|6`QBe69nWUC}HOQx;EuP%(`<5OitI)oZ@R)SX4+1=-^VDaM^k16Y=IS2e zpoLUJwZ_-tYLI0I!K#J{AG6`pbCrW6p^wVuLAYT8KhF(;wrh&$nPGEnx=SmB0T68>-X$BD{3u>xMWa!$QD~? zJjqqWQQ_jI0@O88OlfN&sl0%;LwbK%01O;A>+%~Y08{kP_C?5)r3WE#_KNK0d)~F) zof$$FG;eL<7dP*iJR>_Ou4jhC=TGS;uRdpw;9Bo4<~hkf4`uLoDaeI2R8ZcuUmUi& z2g9c7mt|%;v7XMZyC;*MF1Om4LXw@0E~_@|*B-ugSW4!cFqdKc1muKil`0XR91LWG?Ne3v7z-mO-LeSWXOx&euowNbts z50h%9b!;e~^VONz7n-Z?B>^EHqA7#>``MXs&-re2`FK?6U07c4*L@jUGgDmcUK3LM zOe^6vuG_PHc1IpE&$!atND`kaF+@%= zWi_$B9e`au$z33m;gRsF_4~8?=fGYfBi`GrZOP)^L};h?bXzl-~%IY@3Rn7T83cR<~GM9Cbl zq#(9KcNX^yM)`&pUZW2lL!)2ZPYksXtkK1pn5PTEnXf)TyD0-!-ryaKB@}N2A3Ku7-v4 znSD9VwmA>0&Co~Wgj14V@qoha%muFP0*+er!H3Qd8lg}Jk@%$GYt*DNAl;UpW5VrH zG&~n-{G|aoYVnxAccZU7XB(NJ8$ZO2o7aWr-{i>p`u>KV@)X$#*DCELQ!ldv*&&;# zGdyTZPzJK9H;5V35|^UMBMuvq0%n$JBA&3&V#B^0RkYcha7`g#)*7Uv{=FE^I8f{Ox;yDz&$2zGbw0K`$FJ?Jcw*>H6FD}CXSG@u?? z4CnA!=xeQJ-`O}KM{YCZNWLdRDtf)7D2hw807^)R_oTsLFHG|l(eoL`%RUj<{?VD# zZ>Oa-!eS8{HnaeWTr1xX0Tjce6jk?gzb+^zT!TDr@vRxt+KfV7HFMEOVLhfXn4xeY zs`F8)#F-m0S>=+mCBsaiUr=s@_cqTQg2S5Y-4B7HU#CdpEEoxJ?s9kWvgh7l>ry-m z>?}^)WU6K}<49I1>Sszm(tddr!DkLtNzr>tMKo|Yq!J@UN58rO$$$)+@uB0(ou~&` z3XnwAT%9~BdoK>l&L(M(S4VU=>9_SoK3;g?Lo0a(JKLwS5fXRn_Y0OGKC27B+-1x~ zloinVx!UEV8`ze0i^lIDDtEOJ1S#?z6-=^HV3cy;2%k&@D#rbu8zPY_r`F1`lP9he z)gQT5!3VP>JDOg6muqLvr<6zeR0;yeXAr%{CTTPmK2?!PBAE8VpMO@l(tIm=L8p8S zLH4Q#i3px0%8S4)n_@+CRTWTyxJ?m6Vg?INB&$sL=oTMVG-z`W#QT_)A#@y?lf=Q| z2FIp7><9tNn<#%>vOqM>!OYy;>modM*>neShk4kgD30Twq4=KHYFdKbFB6r%HYbs0 zrlsbH$c~NIEyJ6TjjMLmHx)vxm?x!dR(S>hdhT)p@c8|C!{HhuOij;C>siwH@jt#V(i|eBIQyTWS4q}BS zW6BQp)1YGb4ov=dzsX<+D9KKy)kzMk<%1&Q^yXJL(=!X(;90zH-f_b(wtuJxyPVtS zib%i^&Mn;!GZ*hOI_RjT%Q47FXke->{$$DQ8(MQEpWaX^V8ds^xI|{zFyv*ZQY8 z8b1%I06>uH*k9$U=+K@Z3QE)O;8hwE#xC>nq7;)~8ONXh3JMg9Y+*j_LL-PzmfWq3 zFZu*#UZsEdtx|PP@dodg7=q%B8y+~GHym8@a?&SDhVDju8`vA7?Qy}rkpj{fyj%GL zvFeF%Pm&ZNLubSTGJ+{a09#XSkBNMlPZZmyGjb7%$0cP-w(+YLl3aX%Myqg)Q9_#k<)J9#GQ>T{u;{;#@Em8#@-+P|_r z41doJ;k}el5XrpS$4sS3So*63C^X(atrkQ_zi010;Ddw9797iYWwE-Q3PEc&Vr%~wJOKW7)hef%x ztxk~V8Iwi%R8z5?wzH*^NmjFi7zFo?EF8G<`2aAlwR2Mk#mf-6Eg0$UDuPv z`;L{yF9wRm3LF}f6F4=bwE7MPgB7>NI$SKS+lmaBLw)Dcxt3r-*A)2_0@P6!%+oBR zmt;&A?GOmW7}hEOQ#r;w`<(iy#qhk3#Foe+@ zvZDzmfERW`?D}VCOg9Jv_c2YKWk@$w`FgQ1-gX`A11jO7>qD-4JU`#lk&x#YExInF zwgWPk(dnrz=W~{ec?t=N&rqsI9FZWq>uu`bTSXHT@2+7 zFpCX?V`CPZZ(;C_#WhlFlF=x44U@$i%F*Hd7M}B?!4iXGN|RYkQI$RiudgdEQ9<&` zAU13P`*ihSuq{;&wT{mHvMh#feJ$-!3cywu z*+>qIM4CH=5I&O$+v!C+`yBm)LsWebgJ(+|&5%xKJ+ZU;Lx%~iQX4-dHu)WbY{o_|@ zg1jR~V^5^H^A6@(LqNjk&UjuRgW-K%d8L+N5>zydWHcJU%vFxMwYA zp!6ykCN>NiJM1+Ph%$vQX-^dM|EL+xL|%n?p5-;iX~b&~`ekB<}#*YNdM~v)gTz zqv&UPzDM6*90rR4DIV?8$`GZ5@8iQ;CytcifO$eiSP5Zl6JdY;>+|SpUb89_X5Rsi z23;Iw3k`j^Z3SmTJZg2Lnqh#OX<>L|Kqi(mG*x_Zh{`SrPPF>9A0 zkH0rUlmsu<7&^rt{m;bu_oIvtoj3(|ae1yBu{5;(iihWaANQYA@!ow~D|^w850d$K zbCmxvP5<|3)IBLnOH1DzrRQDkgnoXML;U$h^c-v2Q#~BJeBsnt{t)Eb*Ej!r)W3hQ z&e|h6`=%*N>aW+t-@YJmT`lO}f37urKI(?U-+uJhH%ikVltpq*&zyCZOi^Vno_d)F?qus6{NUa8B#i=UMKvM9h7ZJ7Z+})xh zH{*BvI})_;zy5q*2j+os#x6~e1OYr8l27~RoiwOcYwzA!lltr%yF7pnaR5Hoyhz*S z=+*+@`Tgt90n#_tXt>MK^N)ZAj>?~p{NM5UKc`wd@Mi;4^UnWc82|OPXkpODvf>ng zo7_*2^B&w{n7q@~pZ|3)__#ZYqq}@T0u@9@ zg;|;XbjSwL1*h%X*C8HiAo$l^{jE=0%Yud)tSUac%Pndj222J^itgWj^w<5?!uF&9 zmD6uzG8qPS`o}xquiu>i6*S~8Dk8gGY|}6Q#4ghazeG{(EJzESqM=DsJy|#Gq&ZuM zgOC4p?SC7%bisprv8AQWvmk@Z0jauI5C9a|r@&rLG>nZWHt%HKE~9}ixNgrIafld* zxma-^z^;6<%8B=c`H@VBxw#0fw7VRbG(}Z&v}FCV%Z0ubi_ns#>EUgH-EH$dB{5J7 zD4qg50t2=H4iH7#Qz2;N(o|QPHoR8Flz6R3Z;~84S}m|!E2-@@G{oUvol2aS0UG?G zjn8>RXpUO)z{!vU0b(X_R`L}BGy=!82n|wECs#=d=)3MVrSDxok=gFAV~xWdNz&8b zrfnQ&;6XScsMP52@Oj^~9-aH&V+5_oSUdV#d(;pJ*MF=Q|7z|n2*3KKf)~RY7Z=ym z%EWuPEMhw4tT{3+$$!rp5L1ThgA0<!53Wc0Wn3%QwEi=8y(Y-UMQ zuTpw$IEo6-G{Oqejclg$m4yFvZrHnzTRIq58}MA98H#Fw=Hpru+6&KW<3#V?2w54~ zv`T~T{lY$UsG#QOk7otJ(vuY6gOa%j30YsN+F#}{6eR!|>yKI;z4F)a`}>(&&UUc& z^^XVmIqtY<4TV>-R@7~fed61(rn_~^TB4}_B*BaK8QBLUmR-{sCo&L7#A^k=T}Gh9 z^(3aRg61z`>9&p&q3aut?#hIpw;-5#I2bGH$ngis?-2V&*`vCi|NUbB99C$}V3$KU z0x0xLBAC}n?s8%^>$_l{`p8P$<$>?DKe54%orkfT8N@uJ1OKjF)xCngWPSht0BPxw AUjP6A literal 0 HcmV?d00001 diff --git a/extension.schema.json b/extension.schema.json index 99f2bebcf..0cdb45549 100644 --- a/extension.schema.json +++ b/extension.schema.json @@ -605,6 +605,52 @@ "type": "boolean" } } + }, + "settingsGroupRef": { + "type": "object", + "properties": { + "id": { + "description": "Unique identifier", + "type": "string" + }, + "name": { + "description": "Category name, can be translation key", + "type": "string" + }, + "parameters": { + "description": "Settings group parameters", + "type": "array", + "items": { "$ref": "#/definitions/settingsGroupParameterRef" }, + "minItems": 1 + } + }, + "required": ["id", "name", "parameters"] + }, + "settingsGroupParameterRef": { + "type": "object", + "properties": { + "id": { + "description": "Unique identifier", + "type": "string" + }, + "name": { + "description": "Public name, can be a translation key", + "type": "string" + }, + "key": { + "description": "The key to use when saving to the storage", + "type": "string" + }, + "type": { + "description": "The type of the value", + "type": "string" + }, + "value": { + "description": "Default value to use for the setting", + "type": ["boolean", "string", "number"] + } + }, + "required": ["name", "key", "type"] } }, @@ -666,6 +712,12 @@ "items": { "$ref": "#/definitions/actionRef" }, "minItems": 1 }, + "settings": { + "description": "List of application-specific setting groups", + "type": "array", + "items": { "$ref": "#/definitions/settingsGroupRef" }, + "minItems": 1 + }, "features": { "description": "Application-specific features and extensions", "type": "object", diff --git a/package-lock.json b/package-lock.json index c5377ff39..5fca7650f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13380,9 +13380,9 @@ } }, "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" }, "tslint": { "version": "5.20.1", diff --git a/package.json b/package.json index 1db0d185e..a47dadc3a 100644 --- a/package.json +++ b/package.json @@ -91,8 +91,8 @@ "codelyzer": "^5.2.2", "commander": "^4.0.1", "cpr": "^3.0.1", - "dotenv": "6.2.0", "cspell": "^4.0.55", + "dotenv": "6.2.0", "husky": "^2.4.0", "jasmine-core": "~2.8.0", "jasmine-reporters": "^2.2.1", @@ -116,7 +116,7 @@ "selenium-webdriver": "4.0.0-alpha.1", "ts-node": "^8.0.3", "tsickle": "0.34.0", - "tslib": "^1.9.0", + "tslib": "^1.11.1", "tslint": "^5.20.1", "typescript": "3.2.4", "wait-on": "^3.0.1", diff --git a/projects/aca-shared/rules/src/app.rules.ts b/projects/aca-shared/rules/src/app.rules.ts index 822275c36..ab1e1e74d 100644 --- a/projects/aca-shared/rules/src/app.rules.ts +++ b/projects/aca-shared/rules/src/app.rules.ts @@ -30,7 +30,6 @@ import * as repository from './repository.rules'; export interface AcaRuleContext extends RuleContext { languagePicker: boolean; withCredentials: boolean; - processServices: boolean; } /** @@ -554,5 +553,5 @@ export function canShowLogout(context: AcaRuleContext): boolean { * @param context Rule execution context */ export function canShowProcessServices(context: AcaRuleContext): boolean { - return context.processServices; + return localStorage && localStorage.getItem('processServices') === 'true'; } diff --git a/projects/aca-shared/store/src/actions/app.actions.ts b/projects/aca-shared/store/src/actions/app.actions.ts index 62553ac57..0297858c4 100644 --- a/projects/aca-shared/store/src/actions/app.actions.ts +++ b/projects/aca-shared/store/src/actions/app.actions.ts @@ -28,8 +28,8 @@ import { Node, Person, Group, RepositoryInfo } from '@alfresco/js-api'; import { AppState } from '../states/app.state'; export enum AppActionTypes { + SetSettingsParameter = 'SET_SETTINGS_PARAMETER', SetInitialState = 'SET_INITIAL_STATE', - SetLanguagePicker = 'SET_LANGUAGE_PICKER', SetCurrentFolder = 'SET_CURRENT_FOLDER', SetCurrentUrl = 'SET_CURRENT_URL', SetUserProfile = 'SET_USER_PROFILE', @@ -41,8 +41,13 @@ export enum AppActionTypes { ResetSelection = 'RESET_SELECTION', SetInfoDrawerState = 'SET_INFO_DRAWER_STATE', SetInfoDrawerMetadataAspect = 'SET_INFO_DRAWER_METADATA_ASPECT', - CloseModalDialogs = 'CLOSE_MODAL_DIALOGS', - ToggleProcessServices = 'TOGGLE_PROCESS_SERVICES' + CloseModalDialogs = 'CLOSE_MODAL_DIALOGS' +} + +export class SetSettingsParameterAction implements Action { + readonly type = AppActionTypes.SetSettingsParameter; + + constructor(public payload: { name: string; value: any }) {} } export class SetInitialStateAction implements Action { @@ -51,12 +56,6 @@ export class SetInitialStateAction implements Action { constructor(public payload: AppState) {} } -export class SetLanguagePickerAction implements Action { - readonly type = AppActionTypes.SetLanguagePicker; - - constructor(public payload: boolean) {} -} - export class SetCurrentFolderAction implements Action { readonly type = AppActionTypes.SetCurrentFolder; @@ -114,9 +113,3 @@ export class SetRepositoryInfoAction implements Action { constructor(public payload: RepositoryInfo) {} } - -export class ToggleProcessServicesAction implements Action { - readonly type = AppActionTypes.ToggleProcessServices; - - constructor(public payload: boolean) {} -} diff --git a/projects/aca-shared/store/src/selectors/app.selectors.ts b/projects/aca-shared/store/src/selectors/app.selectors.ts index 46068b032..4ded19b5a 100644 --- a/projects/aca-shared/store/src/selectors/app.selectors.ts +++ b/projects/aca-shared/store/src/selectors/app.selectors.ts @@ -133,8 +133,3 @@ export const infoDrawerMetadataAspect = createSelector( selectApp, state => state.infoDrawerMetadataAspect ); - -export const getProcessServicesState = createSelector( - selectApp, - state => state.processServices -); diff --git a/projects/aca-shared/store/src/states/app.state.ts b/projects/aca-shared/store/src/states/app.state.ts index 70e8b4885..0f1d5feaf 100644 --- a/projects/aca-shared/store/src/states/app.state.ts +++ b/projects/aca-shared/store/src/states/app.state.ts @@ -44,7 +44,6 @@ export interface AppState { showFacetFilter: boolean; documentDisplayMode: string; repository: RepositoryInfo; - processServices: boolean; } export interface AppStore { diff --git a/src/app.config.json b/src/app.config.json index 1c41521d2..2ee711048 100644 --- a/src/app.config.json +++ b/src/app.config.json @@ -26,8 +26,6 @@ "viewer.maxRetries": 1, "sharedLinkDateTimePickerType": "date", "headerColor": "#ffffff", - "languagePicker": true, - "processServices": true, "pagination": { "size": 25, "supportedPageSizes": [25, 50, 100] diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 339dc0170..829ee2784 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -42,6 +42,12 @@ describe('AppComponent', () => { } }; + const storageMock: any = { + getItem(): string { + return ''; + } + }; + beforeAll(() => { component = new AppComponent( null, @@ -55,7 +61,8 @@ describe('AppComponent', () => { null, null, null, - null + null, + storageMock ); }); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 687d21318..03647143f 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -30,7 +30,8 @@ import { FileUploadErrorEvent, PageTitleService, UploadService, - SharedLinksApiService + SharedLinksApiService, + StorageService } from '@alfresco/adf-core'; import { Component, OnInit, OnDestroy } from '@angular/core'; import { ActivatedRoute, Router, ActivationEnd } from '@angular/router'; @@ -73,7 +74,8 @@ export class AppComponent implements OnInit, OnDestroy { private extensions: AppExtensionService, private contentApi: ContentApiService, private appService: AppService, - private sharedLinksApiService: SharedLinksApiService + private sharedLinksApiService: SharedLinksApiService, + private storage: StorageService ) {} ngOnInit() { @@ -178,8 +180,7 @@ export class AppComponent implements OnInit, OnDestroy { const state: AppState = { ...INITIAL_APP_STATE, - languagePicker: this.config.get('languagePicker'), - processServices: this.config.get('processServices'), + languagePicker: this.storage.getItem('languagePicker') === 'true', appName: this.config.get('application.name'), headerColor: this.config.get('headerColor'), logoPath: this.config.get('application.logo'), diff --git a/src/app/components/current-user/current-user.component.spec.ts b/src/app/components/current-user/current-user.component.spec.ts index 56528415d..ae733fe1c 100644 --- a/src/app/components/current-user/current-user.component.spec.ts +++ b/src/app/components/current-user/current-user.component.spec.ts @@ -32,7 +32,7 @@ import { Store } from '@ngrx/store'; import { AppState, SetUserProfileAction, - SetLanguagePickerAction + SetSettingsParameterAction } from '@alfresco/aca-shared/store'; describe('CurrentUserComponent', () => { @@ -91,7 +91,9 @@ describe('CurrentUserComponent', () => { it('should set language picker state', done => { fixture.detectChanges(); - store.dispatch(new SetLanguagePickerAction(true)); + store.dispatch( + new SetSettingsParameterAction({ name: 'languagePicker', value: true }) + ); component.languagePicker$.subscribe((languagePicker: boolean) => { expect(languagePicker).toBe(true); diff --git a/src/app/components/settings/settings.component.html b/src/app/components/settings/settings.component.html index c6c2f6070..46f981a76 100644 --- a/src/app/components/settings/settings.component.html +++ b/src/app/components/settings/settings.component.html @@ -68,39 +68,40 @@ - + - - {{ 'APP.SETTINGS.APPLICATION-SETTINGS' | translate }} - - - - Language Picker - - - - - - Extensions + {{ group.name | translate }} -

- - Enable AI Extensions - +
+ + + + {{ param.name | translate }} + - - Enable Process Services Extensions - + + + {{ param.name | translate }} + + + + + + Unknown parameter type: {{ param.name | translate }} + + +
diff --git a/src/app/components/settings/settings.component.spec.ts b/src/app/components/settings/settings.component.spec.ts index 8baf89d4d..22dfda089 100644 --- a/src/app/components/settings/settings.component.spec.ts +++ b/src/app/components/settings/settings.component.spec.ts @@ -24,9 +24,131 @@ */ import { SettingsComponent } from './settings.component'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { setupTestBed, StorageService } from '@alfresco/adf-core'; +import { AppSettingsModule } from './settings.module'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { SettingsParameterRef } from '../../types'; +import { AppExtensionService } from '../../extensions/extension.service'; +import { By } from '@angular/platform-browser'; +import { + TranslateModule, + TranslateLoader, + TranslateFakeLoader +} from '@ngx-translate/core'; describe('SettingsComponent', () => { - it('should be defined', () => { - expect(SettingsComponent).toBeDefined(); + let fixture: ComponentFixture; + let component: SettingsComponent; + let storage: StorageService; + let appExtensions: AppExtensionService; + + let stringParam: SettingsParameterRef; + let boolParam: SettingsParameterRef; + + setupTestBed({ + imports: [ + AppSettingsModule, + AppTestingModule, + TranslateModule.forRoot({ + loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } + }) + ] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SettingsComponent); + component = fixture.componentInstance; + + storage = TestBed.get(StorageService); + appExtensions = TestBed.get(AppExtensionService); + + stringParam = { + key: 'key', + name: 'param1', + type: 'string', + value: 'paramValue' + }; + + boolParam = { + key: 'key', + name: 'param2', + type: 'boolean', + value: true + }; + }); + + it('should retrieve string param value from storage', () => { + spyOn(storage, 'getItem').and.returnValue('storageValue'); + + const value = component.getStringParamValue(stringParam); + expect(value).toBe('storageValue'); + }); + + it('should use param value as fallback when storage is empty', () => { + spyOn(storage, 'getItem').and.returnValue(null); + + const value = component.getStringParamValue(stringParam); + expect(value).toBe('paramValue'); + }); + + it('should save param value', () => { + spyOn(storage, 'setItem').and.stub(); + + component.setParamValue(stringParam, 'test'); + + expect(stringParam.value).toBe('test'); + expect(storage.setItem).toHaveBeenCalledWith( + stringParam.key, + stringParam.value + ); + }); + + it('should save param value only if changed', () => { + spyOn(storage, 'setItem').and.stub(); + + component.setParamValue(stringParam, 'test'); + component.setParamValue(stringParam, 'test'); + component.setParamValue(stringParam, 'test'); + + expect(storage.setItem).toHaveBeenCalledTimes(1); + }); + + it('should retrieve boolean param value', () => { + const getItemSpy = spyOn(storage, 'getItem').and.returnValue('true'); + expect(component.getBooleanParamValue(boolParam)).toBe(true); + + getItemSpy.and.returnValue('false'); + expect(component.getBooleanParamValue(boolParam)).toBe(false); + }); + + it('should fallback to boolean param value when storage is empty', () => { + spyOn(storage, 'getItem').and.returnValue(null); + expect(component.getBooleanParamValue(boolParam)).toBe(true); + }); + + it('should render categories as expansion panels', async () => { + spyOn(component, 'reset').and.stub(); + + appExtensions.settingGroups = [ + { + id: 'group1', + name: 'Group 1', + parameters: [] + }, + { + id: 'group2', + name: 'Group 2', + parameters: [] + } + ]; + + fixture.detectChanges(); + await fixture.whenStable(); + + const panels = fixture.debugElement.queryAll( + By.css('.mat-expansion-panel') + ); + expect(panels.length).toBe(3); }); }); diff --git a/src/app/components/settings/settings.component.theme.scss b/src/app/components/settings/settings.component.theme.scss index 96644da0a..793cf9fff 100644 --- a/src/app/components/settings/settings.component.theme.scss +++ b/src/app/components/settings/settings.component.theme.scss @@ -2,6 +2,11 @@ $background: map-get($theme, background); $app-menu-height: 64px; + .aca-settings-parameter-list { + display: flex; + flex-direction: column; + } + .aca-settings { &-extensions-list { display: flex; diff --git a/src/app/components/settings/settings.component.ts b/src/app/components/settings/settings.component.ts index a47ea2910..bf48908ac 100644 --- a/src/app/components/settings/settings.component.ts +++ b/src/app/components/settings/settings.component.ts @@ -30,19 +30,18 @@ import { OauthConfigModel } from '@alfresco/adf-core'; import { Validators, FormGroup, FormBuilder } from '@angular/forms'; -import { Observable, BehaviorSubject } from 'rxjs'; +import { Observable } from 'rxjs'; import { Store } from '@ngrx/store'; -import { MatCheckboxChange } from '@angular/material/checkbox'; import { AppStore, - SetLanguagePickerAction, getHeaderColor, getAppName, getUserProfile, - getLanguagePickerState, - ToggleProcessServicesAction + SetSettingsParameterAction } from '@alfresco/aca-shared/store'; import { ProfileState } from '@alfresco/adf-extensions'; +import { AppExtensionService } from '../../extensions/extension.service'; +import { SettingsGroupRef, SettingsParameterRef } from '../../types'; interface RepositoryConfig { ecmHost: string; @@ -64,11 +63,13 @@ export class SettingsComponent implements OnInit { profile$: Observable; appName$: Observable; headerColor$: Observable; - languagePicker$: Observable; - aiExtensions$: Observable; - psExtensions$: Observable; + + get settingGroups(): SettingsGroupRef[] { + return this.appExtensions.settingGroups; + } constructor( + private appExtensions: AppExtensionService, private store: Store, private appConfig: AppConfigService, private storage: StorageService, @@ -76,23 +77,14 @@ export class SettingsComponent implements OnInit { ) { this.profile$ = store.select(getUserProfile); this.appName$ = store.select(getAppName); - this.languagePicker$ = store.select(getLanguagePickerState); this.headerColor$ = store.select(getHeaderColor); } - get logo() { + get logo(): string { return this.appConfig.get('application.logo', this.defaultPath); } ngOnInit() { - this.aiExtensions$ = new BehaviorSubject( - this.storage.getItem('ai') === 'true' - ); - - this.psExtensions$ = new BehaviorSubject( - this.storage.getItem('processServices') === 'true' - ); - this.form = this.fb.group({ ecmHost: [ '', @@ -139,17 +131,33 @@ export class SettingsComponent implements OnInit { }); } - onLanguagePickerValueChanged(event: MatCheckboxChange) { - this.storage.setItem('languagePicker', event.checked.toString()); - this.store.dispatch(new SetLanguagePickerAction(event.checked)); + getStringParamValue(param: SettingsParameterRef): string { + return this.storage.getItem(param.key) || param.value; } - onToggleAiExtensions(event: MatCheckboxChange) { - this.storage.setItem('ai', event.checked.toString()); + setParamValue(param: SettingsParameterRef, value: any) { + if (param.value !== value) { + param.value = value; + this.saveToStorage(param); + } } - onTogglePsExtensions(event: MatCheckboxChange) { - this.storage.setItem('processServices', event.checked.toString()); - this.store.dispatch(new ToggleProcessServicesAction(event.checked)); + getBooleanParamValue(param: SettingsParameterRef): boolean { + const result = this.storage.getItem(param.key); + if (result) { + return result === 'true'; + } else { + return param.value ? true : false; + } + } + + private saveToStorage(param: SettingsParameterRef) { + this.storage.setItem( + param.key, + param.value ? param.value.toString() : param.value + ); + this.store.dispatch( + new SetSettingsParameterAction({ name: param.key, value: param.value }) + ); } } diff --git a/src/app/extensions/extension.service.ts b/src/app/extensions/extension.service.ts index cb6b8da5b..c189c22b9 100644 --- a/src/app/extensions/extension.service.ts +++ b/src/app/extensions/extension.service.ts @@ -31,8 +31,7 @@ import { DomSanitizer } from '@angular/platform-browser'; import { AppStore, getRuleContext, - getLanguagePickerState, - getProcessServicesState + getLanguagePickerState } from '@alfresco/aca-shared/store'; import { NodePermissionService } from '@alfresco/aca-shared'; import { @@ -60,6 +59,7 @@ import { AppConfigService, AuthenticationService } from '@alfresco/adf-core'; import { BehaviorSubject, Observable } from 'rxjs'; import { RepositoryInfo, NodeEntry } from '@alfresco/js-api'; import { ViewerRules } from './viewer.rules'; +import { SettingsGroupRef } from '../types'; @Injectable({ providedIn: 'root' @@ -84,6 +84,7 @@ export class AppExtensionService implements RuleContext { contentMetadata: any; viewerRules: ViewerRules = {}; userActions: Array = []; + settingGroups: Array = []; documentListPresets: { files: Array; @@ -111,7 +112,6 @@ export class AppExtensionService implements RuleContext { repository: RepositoryInfo; withCredentials: boolean; languagePicker: boolean; - processServices: boolean; references$: Observable; @@ -137,10 +137,6 @@ export class AppExtensionService implements RuleContext { this.store.select(getLanguagePickerState).subscribe(result => { this.languagePicker = result; }); - - this.store.select(getProcessServicesState).subscribe(result => { - this.processServices = result; - }); } async load() { @@ -153,6 +149,12 @@ export class AppExtensionService implements RuleContext { console.error('Extension configuration not found'); return; } + + this.settingGroups = this.loader.getElements( + config, + 'settings' + ); + this.headerActions = this.loader.getContentActions( config, 'features.header' diff --git a/src/app/store/initial-state.ts b/src/app/store/initial-state.ts index b733e7484..7cf21fd0b 100644 --- a/src/app/store/initial-state.ts +++ b/src/app/store/initial-state.ts @@ -54,8 +54,7 @@ export const INITIAL_APP_STATE: AppState = { status: { isQuickShareEnabled: true } - }, - processServices: false + } }; export const INITIAL_STATE: AppStore = { diff --git a/src/app/store/reducers/app.reducer.ts b/src/app/store/reducers/app.reducer.ts index 7197764f4..da60fe598 100644 --- a/src/app/store/reducers/app.reducer.ts +++ b/src/app/store/reducers/app.reducer.ts @@ -30,7 +30,6 @@ import { NodeActionTypes, SearchActionTypes, SetUserProfileAction, - SetLanguagePickerAction, SetCurrentFolderAction, SetCurrentUrlAction, SetInitialStateAction, @@ -38,7 +37,7 @@ import { SetRepositoryInfoAction, SetInfoDrawerStateAction, SetInfoDrawerMetadataAspectAction, - ToggleProcessServicesAction + SetSettingsParameterAction } from '@alfresco/aca-shared/store'; import { INITIAL_APP_STATE } from '../initial-state'; @@ -52,15 +51,18 @@ export function appReducer( case AppActionTypes.SetInitialState: newState = Object.assign({}, (action).payload); break; + case AppActionTypes.SetSettingsParameter: + newState = handleSettingsUpdate( + state, + action as SetSettingsParameterAction + ); + break; case NodeActionTypes.SetSelection: newState = updateSelectedNodes(state, action); break; case AppActionTypes.SetUserProfile: newState = updateUser(state, action); break; - case AppActionTypes.SetLanguagePicker: - newState = updateLanguagePicker(state, action); - break; case AppActionTypes.SetCurrentFolder: newState = updateCurrentFolder(state, action); break; @@ -93,11 +95,6 @@ export function appReducer( case SearchActionTypes.HideFilter: newState = hideSearchFilter(state); break; - case AppActionTypes.ToggleProcessServices: - newState = updateProcessServices(state, ( - action - )); - break; default: newState = Object.assign({}, state); } @@ -123,15 +120,6 @@ function showSearchFilter(state: AppState): AppState { return newState; } -function updateLanguagePicker( - state: AppState, - action: SetLanguagePickerAction -): AppState { - const newState = Object.assign({}, state); - newState.languagePicker = action.payload; - return newState; -} - function updateUser(state: AppState, action: SetUserProfileAction): AppState { const newState = Object.assign({}, state); const user = action.payload.person; @@ -275,11 +263,15 @@ function updateRepositoryStatus( return newState; } -function updateProcessServices( +function handleSettingsUpdate( state: AppState, - action: ToggleProcessServicesAction -) { - const newState = Object.assign({}, state); - newState.processServices = action.payload; + action: SetSettingsParameterAction +): AppState { + const newState = { ...state }; + const { payload } = action; + + if (payload.name === 'languagePicker') { + newState.languagePicker = payload.value ? true : false; + } return newState; } diff --git a/src/app/types.ts b/src/app/types.ts new file mode 100644 index 000000000..37d291eca --- /dev/null +++ b/src/app/types.ts @@ -0,0 +1,38 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2020 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export interface SettingsGroupRef { + id: string; + name: string; + parameters: Array; +} + +export interface SettingsParameterRef { + id?: string; + name: string; + key: string; + type: 'string' | 'boolean'; + value?: any; +} diff --git a/src/assets/app.extensions.json b/src/assets/app.extensions.json index 8c4301175..19771bebf 100644 --- a/src/assets/app.extensions.json +++ b/src/assets/app.extensions.json @@ -9,6 +9,45 @@ "$description": "Core application extensions and features", "$references": ["aos.plugin.json", "app.header.json"], + "settings": [ + { + "id": "app.settings", + "name": "APP.SETTINGS.APPLICATION-SETTINGS", + "parameters": [ + { + "name": "Language Picker", + "key": "languagePicker", + "type": "boolean", + "value": true + } + ] + }, + { + "id": "extensions.ai.settings", + "name": "Extensions: AI", + "parameters": [ + { + "name": "Enable AI Extensions", + "key": "ai", + "type": "boolean", + "value": false + } + ] + }, + { + "id": "extensions.ps.settings", + "name": "Extensions: Process Services", + "parameters": [ + { + "name": "Enable Process Services Extensions", + "key": "processServices", + "type": "boolean", + "value": false + } + ] + } + ], + "rules": [ { "id": "app.toolbar.favorite.canAdd",