From 6b27686f39ce5b7d5ffbf2293de18a35f205cbef Mon Sep 17 00:00:00 2001 From: Varun Raj Date: Sat, 30 Nov 2024 11:30:21 +0530 Subject: [PATCH] feat: super charged albums page with people management tools Signed-off-by: Varun Raj --- bun.lockb | Bin 392402 -> 394960 bytes package.json | 2 + src/components/albums/info/AlbumImages.tsx | 159 +++++++++++++++++ src/components/albums/info/AlbumPeople.tsx | 160 ++++++++++++++++++ src/components/albums/list/AlbumThumbnail.tsx | 64 +++++++ .../PotentialAlbumsAssets.tsx | 4 +- .../assets-options/AssetOffsetDialog.tsx | 1 - .../MissingLocationAssets.tsx | 6 +- src/components/layouts/PageLayout.tsx | 6 +- src/components/people/PersonMergeDropdown.tsx | 8 + src/components/ui/accordion.tsx | 56 ++++++ src/components/ui/checkbox.tsx | 27 +++ src/components/ui/lazy-grid-image.tsx | 44 +++++ src/components/ui/lazy-image.tsx | 8 +- src/config/routes.ts | 5 +- src/handlers/api/album.handler.ts | 15 +- src/helpers/string.helper.ts | 3 + src/pages/albums/[albumId].tsx | 104 ++++++++++++ src/pages/albums/index.tsx | 32 ++-- src/pages/api/albums/[id]/assets.ts | 62 +++++++ src/pages/api/albums/[id]/info.ts | 49 ++++++ src/pages/api/albums/[id]/people.ts | 40 +++++ src/pages/api/albums/list.ts | 66 ++++++++ .../api/albums/potential-albums-dates.ts | 3 - src/schema/albumAssetsAssets.schema.ts | 7 + src/schema/relationships.ts | 17 +- src/types/album.d.ts | 3 + tailwind.config.ts | 158 +++++++++-------- 28 files changed, 1004 insertions(+), 105 deletions(-) create mode 100644 src/components/albums/info/AlbumImages.tsx create mode 100644 src/components/albums/info/AlbumPeople.tsx create mode 100644 src/components/albums/list/AlbumThumbnail.tsx create mode 100644 src/components/ui/accordion.tsx create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/lazy-grid-image.tsx create mode 100644 src/helpers/string.helper.ts create mode 100644 src/pages/albums/[albumId].tsx create mode 100644 src/pages/api/albums/[id]/assets.ts create mode 100644 src/pages/api/albums/[id]/info.ts create mode 100644 src/pages/api/albums/[id]/people.ts create mode 100644 src/pages/api/albums/list.ts create mode 100644 src/schema/albumAssetsAssets.schema.ts diff --git a/bun.lockb b/bun.lockb index e85a8f72e5a469cb055c07d868b5ca9d24cd6e09..f868e0adf983b2d67eb1d44470f6e7f6b1ecaff6 100755 GIT binary patch delta 79844 zcmeFadstP~+BQDdlBG-yOij&9)Jn|?%tHbq3(sfC%v3Z$EfwRI> za#K>%@+>Q>vE+LZ$cofQ?WuP*`2!7XS>GC@h0L_v^t5i46}Yk8L|Qy8B`<$AYMPri zCy$kW8-8}--54hh#eN{}QBv*1dfVEMLE`PwoD^@%q@~R*i;c_4$W6&dtuhy|&6YK- zg)}mMdRi9COf)Cd(P<^($>@0xgn%&@%2|q-qMbO zQ@cl|X3xsb?Eyb;37(dcmu3|~=ji5`d?|(xxk}I0wB11Fng!&9nv*pvI|a)-%~|AY***``9m}F*I*?Jp6XY(Z!q=(z zj;>O}TY>DFcGpSn9B^IZyvV%iDc!Af+#745BoW9`r(()SPRq)R0cQ*BXo;Jx2gkBr z0B3xg7^&lhKs{@Ktj!BR+^@K#fh0<55WoujBUYLm+sm>pW=3${I6XTDO|z^H@at8V zPG(u#;)HJk@&@aHyg^QSB)c*%G+sJqetudO3;3hAlDF ze*!6mW1$&qbL!dX@yo}Bh9i>GpEj^o-jhX;RB>O z4f=YenS)1K)}`RL;Sq)A7ngjQXjxbtC3)B5aRb~5-X3_vD9geEFKGwPjdlZ&fp-I2 z1M`6F*CwMS|IOg+*Fiw`Yj+^yUbs=Z=?`Ey_Yp0K8Wi~n242KQ%h zKLz?{1qxz6t-D1kwhYMT%{Cb5&zr$nkwHM_pPV8UuLL%=imj4ok!S)T&|m-402Z4T zeU>TId7~FFo9MsV@Zbrc2e=p+vM?dpl@}r-XGXP0y6P(+ zYyO_Wmw&dYRL@v*+XN1tY93_&l=B}B{%L02iHPF zAZxxlQz}-FlAE4~8PhFWdg^i@=S0hFXF{W>VVBO95tt9;6bKxOvcOqfr(C&jT5fK3 zZe-g0_rN(Jr)6YKjhvHXC8BI{mFIlesAKWV*p}F@c-qb4QXDZyipLo&a@wqv^#AGc zd>~7T$xF+en}%EL0p~tZ1!QeE%#-Tjku)_jXJ-0M^JS(6s(A_=^Psroup~OZeH4TmKDIOA?S=|NCHFC2L;l<`F830 z)o2MTc9&_+?`BzVz|ST05|HJj z;)d*@u|W1j*xgd@G$1R!@owx0%s3VfcA*Ez8%)bcpMw_(R>eJ1fwcK)sVQj5i=|S) zI$$&8dl1Nibk9X*=G_C%_`sodvC-!l{|sOl^gzWT??eAz49E2lS_9Lv=0>JwU}sq& zJrcNa;D&($)sbz-NV!PpG0Mr*LST$GKOsG_3dnuC1jr3(z>`u?6p-9{O7y_%uE)XY54=JOY+N%D z&kD_(o{>5ocgo1a?21QX90a!bk*CdAJ}w*oAHb&Y4+L_|(T%B*v$E5_MLw>UgHJn8 zH;E`7fN*xmvQ3h$1jvr-YW!`1EGEq01E%= z3xF&jCo(f72RHhk7ExYauAW2rDY>kV6^IY?Oy*3?KNOHU9dAz{OEh{eoI`4vnjMFu zDflbfoC(b$ibrjik@?j0@LL8K8^nNSE=b9p%4a0+H?30Yn+Kf@Pc`^{l{^x2UK9N# z@YZZkN@{j~nq>`mT{`wYAlsjpk)D^Iom-rC(+(-9J&+^R#PBXat_3e{z>&(IGc9rk zhUk->!rQzdQ+2S3E5i+2BHvv=&eVCpX23Kc+dCe}az+5T7IVou0?*#g;H+qBMr20Tv~25AQ^CdtPorSo;9G;A1KHx;244em zq+cTP*_whoO-Zq=XN<7UpfXr$aIwLu8Pjk+w5)V+Rvg<_YR1%D>$!KN;0zuo($nUr zIue_-mDo|T=8DG+#oVS?(nz5@EaxDQXU zqktTt?jP&&i%Tv;f(76Ck@R?*Ph{7+0R9&6{|P_mpuJD>eQ)>yU^Dnv0ol_FP%xLk zBp{Dnm=5{{**%$Z!#Q*K&Y)%8_^C8(PS(_n)Qk|kdOP53YY|ag{4c!rMqCOo0@&Z+ z(a+>QSAuh{&pRZ?z{cR*jem#EDH(Y1eFDz28Kf~1#;>xGWf(%RupUG5hOTe=0RYO79Eq8%u1Wf`&#+n^rsk{ z31t4MX=yolugA(XaZ`cXpPibYn~{~4mNzFq0}Trt{8gUxMBoKL4xS~c#dyC32|U8{ zt?!OYhP1rdbJB7PtRMeNhSCDErTf2=mW=uS+?2i!6%4!tcpEzB*o`NoNBRLd#eM>E z2wpYu#V3W^r||S(hBPGDh12+g1zY>;KS+i@k&$!p_MfE7&-^I)hW{*c?q5K*_$=}h z-v#F!*bZd5FBrWKkQMj_@!YQJ134rg{D}VNB-#cC@hOvGIgqPnE|B|V3Xqe0G>{d= zuAp~;zTmu3;DCHJI4eA$TKta!*>e%_vu7>>awxw>g_&>v57?tFMB*JdI60mN@&=Cs z8SoI00k;DgkO^c(lYxzaP0mV7Mu4+r(Lh%8GJ{QlEdL04vNdowkPUhoL&5xC6@M$s zEbyA)6O1)8PBoLrK}O!t_IUKNF9v6erU1Fz#{k)tzo8s9V6E4qZ}>4dZ=9c!lQLyi zn)NEm<+5F1aASzax&plTDxXIOwnMczhJlUlDEy7U0~_891hfSo4P*;UhY9M=Zp)GOu7+wHu#?wn7UVpPIJ_9mvU?bs&fows)3p{!VVhm#J z@zROMH#tqbgM94r7lEw6V?fS{X>x;-@j&*_^WjoX)5ad%(E7j&xc+~G&=?s$gM$U6 z%umDH%DHL7Okf`%=fH^rGF1O)BKe*HvP%P>rrZb48%+nYM@AXM>eD?r3&HOU?l<^`!OaFA zHnkK7&8A1@QMki&%jxfE=>_MMH{p3;%DlgcbPT zH)M#kAkdP^2;oOCW?WA1caXX9CXhq)ERg;;@tGZm<`p1k_v0O%rV$-`1;0!UydDmG z1oS_>Ol_I9B{yP2!*-ibd=qyvYr>}0uat~>r~7Na+#5Y5Zs>=RUF#n>^vK;)o6p_S zuJeP`+c+UDDUo^?H%WN)G3Hhuzv^dg&Ku8iv~n{x;fSGW%?{D z5k8O8cXEtV;7{bNXZ(I&VFP?vjSWy04NKA+?16K$7+4FIc8=K0B~=$qh~>{Rzn zu=ipeO@rcL<$OKRqrIJYf1D@BspywrzXaX`s@L%(MBAsq`suv5j~#I0ar&aR z1z^07m&~)>sYVX&2W%s15+7~%0%N`EIi7*h_Ixl#H*orb?F3_6eY&j1nEi}vpgVu4 zQ!qHezQg#u&W^s(7`y7h3EoysoL)ox_DJkgtfEhM<=sxjkOa>`r+P?&_rj)5uc3a= zAg5qxf=^+M^>NN7#CeW6)k71!*EDl_4fA_uIt9ZLJkL87`1d%|c6+P=Rz}~-Hy3P} zQ`tYx^SV@0NMz-Z5Gr+OskQfy*^JpfzY5FN#Q>%mxJ2per5HVoY|C?*tp zW;aMO$_2FyGs1obY>?JCKWf1G>3P}b63dFwh4Z%aoq|ybY}BX(`wycuU>Dlm@#tj# z+fH9J^z4_xIL`H*MIXk5 z8Y06S6M+GNgo!sS+V>FHSY6E@kz%VuokiI(p?F%7pbO9;1z_i9*_%$qm;~F?E^v2t zQXH7fE3$cp)z|ZC3s@hNr6-E*X)nWU>D4w6jQ6pfMT4U~i=Be83HA>7*b*70Kb&g# zmS1LBqv7*79?ZkTPQkbYd(h>WBJk<8?7i=DwJp?>;KYtku$x|CSy4Loj^WXEAH#gk zqJkKwdVGTC1t&Hs!4B;ZxX&W2oqkS5QiA6*ry9z`9cxDnYwRO1nNn<<-6^1X5~J;5 zV5}?V1J={+=QPB<3U)5;C!=9^+8+~orF4SKuyuyj)7^CxOr{HO*QT@F0F4A22PQ>w z@t1-n=nmkP^DbDtuz}HD`zmMKB)>iOs(=&WJ_jtpIXf`U{t&6*I&&Z%BKd4Sg@bq$GrzF_Rx&-pD zthbzsDG9!_@LlV~4~+9hUhDKq^?Q~%1*r-4{%dPz7P_i+*V@@JBHF$mjJqJl2_3f! zj5pR>jU9poO$}QH+8hIhg0Kr>dDx4=xG`Yf-53)J!Z7^+uWQZOS?dt>9!#V4Z^W`ITty35uXFrYc zIcL)S_KfSKYxU~!Jna-rPp}WeH$rFh?2HM;^k9W;$1^C}TiDZCfX((LrywK29vmI$ zN_01#iq-H{!N;|!E9Jd9##u1K@5@0o`#EPv#@V}&k`{A%hr|YIMCPw$*dl!M$@n<* z?MEunDI6TFFOy^)f$WE}|fd{<}4|m?b4AW0$yE_&D92inG6O*|*C&5>PE*s~Z&585fgoA^m zo<-^=?en1Sk{XNDjoP=dHuWb`H)!9;{&?p_ryfCSI8&alor1gsZ`1&1Tb>_%Qk{pE zqvq%)R$zFv=L@GGAJ58xGS9H1V{OwA?yadk@r(zyf`A``H zbWB3DuO;^00USHuM5Ko4XUG#sp?&P=qcI^m15ajG50^EC9)Bk$6oi8z%k5Dxt{7}8 zJG8{ATKZAm7&C?|QI^Aauu+^zh|MKj#O_X!w|WN-Eky&+KMj$<()AY+{Pp>e)9kV@3$ zbsLRRbb0q9CG*@fI3@&S7_-_}-Y6$i>^FJQb|n~x3r)sm*>Q|?2N-s(8DM6=84_*3 z0ER-?o!Ea{+$5ufJq%BtQHCLIU=07BV61cYhB)70q_70Awk|_I`1LBDjMR7(gBgUa z@D(t2t6mJAZ=8Zf3Eqz5oNbHzo(WF%q6FWTae81&dxs*yX@N)9fN0M!r{cB*&r+xQ zHf$Rx4igDYz%FAqO$s!D%WEtcS1JZEKH9U~sa~Am+YjGFr!XT9XZnKM6YOad0&^O3 z(zDK~z8#ASzQNAf+vDtk69c`-4*CV`X1Uj^F`-Ct7RX9o3ntZI-yH*!u_x<23B#?! z$Tol_*Rl{K6NUW}Lo%5wA`}UkE}m5Fl^tgDEz%2mfPBZnu)E#UI~4QAl!5Z^0F!~| z-97-5Q3Ug~N@2tBxZ*2?Wqc)4y)3~SKE>&Em){n!(R#}^O41z0K@;OJsK05 z8t7z74w&45Gvik<&Jzqrc}(ckx}CBU>|Cc@oEBI_ECio|m#4ww_#{nILa~;pznwSufLF{?E|I9!Ff7jg2q`=xN}4-;?~4h|kwv1=2kWVi2VWt@ zg(ZD=)ohs$dU5&|f%W1R?AwkMjuY5*zeB1w@=K0{Ty%_zZ|>$Nb%0~SS#;EbDagNahRQ3`>epR{(dlK(ap0D zg7pByu`NH^ZZc1fH8@$KjyHe}hStdG`*e(Fo^$3Qzx^$IY=!OYSQryJUlt)YNQ~&? zVC=V0&AbINbD;U7y(0>oUXS>FYvIH$`$(KGWC7k3(5V?n;mPn;?@%N-J8_)Hv2Wr+ znbrDm?|pZnvu%yv*Zfw_inDkEO+>1X&dh`9!}Nop6$#PaBey!+%KUck!obQ$V|*oG z(nqf#)lY}+NRJ5xkw`Y7vM8x9OtVM+OFY#3E)^F4hib8mGhWzEsU>)2 zIW`8b9=AR2w;SDFdmzNxjyDWBFg>%u*xC9W4tpCICy;FHzk~I5DyPKRJ?@aM#3W8d zSAwzg8d-WLh?d+IvCfBerv~m1+1r(hweWK#)hEk|B4A+7I+r0 zkYX^78s_VmXuAT8%R(0939!+%Ea9%e%{iir!8l0rn*BAfewtCc`0hYYk!6GN4yY!E zb1N8|pqHBO53nK5*=fB)?~xkfMLb_dfwA@&LWJxAla{f#Hl@;~c*TNuY+eK7py>TO z6h!)-8HX>I`(nwWkUPN6&D=^bc8HAg*}9tVx_c4lR37Ucf3k5^Npb>Oi@t zz}RxU?}8;b@IIW%!SE6eyW<03tg5V!ufVvh8tEEbxuSNS6vTvraAM%im=`rk1i?(e z%7nt3$zIg^eyOPJiMN9B)_UXjy#l5;ar-Bvq`z3fE1kMcoeahv!doWLwt%q&dGqpu z2js+nBO?0pRxp-=%CCqC1(6lYR(=b{{z470E}~Zo)2;N)1Jg%HdnZyHY2-qW|7jSi zjweOWRnmHC)&pSY8o3XQRYaxm3{}#F7@Ad@WIzemeLeI+$*0$&&jG_r6FkE{LaH0z z{Ih+lrFqy1Q0viP$%w|{N7J7J>k1a8U(+8kn$#!ZA?XM`!romEIon?Gd#`%fIrEC& zPJ38-Le|6@Fm@nbQD;Tl2f)~3bYM=juh}EW=~NDh^A3N+>9x&oFM(q!WSKap!Ptp1 zmI-U5Seb(Jz0u&Ka;_V6yoPct)Cl{S3?JKCr>i8tFFy_k(ev;SupwOvtm`5BXAa98&$@Mi1_d z2?gO(51ULT-nj!~1SHfVWRnaP9yoXicLS5HgbUz7utCU)I-y$+fpJ#p4+*>%mpcpI z^4saGguMb{!3RrC$d~3k)))vNaKT^?%X{u`#`Le|52efYv*dTp& z-GvmFhjd!wt%1eD=kZ9x(7ot^yTN)OCsx9ym{1TlQo>Gwaik&L6BGK1%pH*mz*s(} z3#PS|)<+l^nBr#jyK!hSRgjI#?fZ&FMs2)i1GB21D4VC+S7 zJbEN;hiKS<(ALMmIDKWR9W)wp;t|Pn zhOHdzTy?)Nn!L7%cw0X2XvhKfPJG)rbJ%Y`2xo8H2y-1v_G81a*MnX7j!Z&%M)Uz= zpW#4)3N8R+`)~qyA|@1sh3HSQ?9*Uo0pi28cV(r^qiZtQHOMG0y6ypEugF^gd%;+~ z!Hu^MDu+p?KOuuFb*90dPuY{4-B6b;86GmQf#Zt?mtY7Y>wA` zWLY;t)2}9cYr$fjN_-$N_+xq4HBfz<<25iCH-9;-90B7{;UI}RbpJ%wP7B@3#bCps z$>_fg#wmzX03I)w?31p*N zEO;@2z2$YVB(N|}Zf~nkon9yWcK)X_4tP<4jpK1JE;MWan5*A?>YO>@_jNgd39R4Z znuQd53#T$X2-bp)g9mb%&ka`l7cY3-US;1 zCNC!=4oeTCzcF(sfL*I|a*tjK#)T}S`4Je`5E$OfI}(`UTzW&nq;BY8uR7xN`q}S& z?})SDXTRO>3u!!t9jExgU|cqO;&>nb!s+#k-@E?{XTdLiyT_OEcCvmX*!O|4()!h& z?=aXP{W)mIucSPz0QCF>Fitph0N(Am9}G`=d|>w}QUi2l*)f+MmD=K5l7n-tVR#hc zJhK^$Q%qhQdT2gW{)^y4}w(dGZv2 z$sXaEjd%CK*nj%vzxU+Vdh9R%CXi*v;+PN+c>`%BQp_vk`i^0;_B_YyZm$W3$qTPS zFm{LJdw5gFycG$*SiDns6H#?Q- zy+e@DfxgKZM;ZGX`z_>z#qsUJ63hm=$i8=>zz$$`8be#NbH6;hEJ zA&tPWZw4DEjld%Q1q>hSpqyc+Yu|)_EhYp+zi6|+N9tysov%@b{3h>xqs?Gz!A48L zkD+Z~+^BVb_+ozN9NTDzgztyQez~4QnYzRZU>x8wA=*PhHhqu0?Na0Vb zP=5PAO*v|Z54$$lSY4+blcMc+z@$ZdsxZ2N(srH7)uj;(q%WrXpf zpT|XeqZ+Dh^xlg|A-Xcj`a5q#oyod);D4k?>xuw zF2g^-demya*Ja*o!ql0IJbqts7{*bL+$TtJ!AO-RqRGk6Zr=YTq9_0!1MEkC5=D}R&PEg8}t`eRj z-lNUbnO4Yt3;rsW^9{{iAMJaM%sD$a&U;C7we1qb+jYxAw&O&LBBbl7la|m$j))rk_hbE6E5ojB8e2V%5J&He0MYXuZMAt+vF3g2=+3-Mf1e z6u?3(FAODH_0*(mJS~fn;hiw(aW$csq-5*YI>#eaGM(1}_MMe1J71ex(Z7;%O1 z6IqZ0YXEx)#;Y!}chQ8^o=V1#N_}pr?(W=uw+SU^+1S zCgZM)%wEoHu+2s%GXL|26YIma!g!l)Fy`M5<3*&uiUe0(WWLwfflRy(0dK(Q z{|Ag0k^Z+xaQz)J-`hHuAS?W?;r|`Gi}K$Ex#4>zkk}A*02Z#!cJZ`KV*T(cLtjMl zZ-r{)c>G}eKN>t`^uI-wd9A0VdgNM9XfezD#l-v_G9M1<`l^ep!xb?}}|%Uqm(=TcW;*OkRzDSoas zFuV><(1CO?LoAT}mH=elJ_h?5><8ox2LidMM*w;KJM^hFqdYD9QXXxx{X3*|BmN#bAzXy8qpoohxxKiTwP=}bK$3yXLKTy`S_7cg8WcWMez^p?)_Vbe_?kOPs&jMM2&A?{BoyNb* z_;&-jL_aeA{T|#OY3oyy;WHrT=a)d%_*>&YVem&FZ+zP5XMw!2huvV16$k<6gs_3g zVl^~6k@>?7HtlUPG&6V+kQb4G%?&5A;EN3>)>Cb6#0p@xHpcz$ka~pC>mrM5Z~Vjy z!Mg%k@16$ZfXwFy;{R43!}}XP(2L!f9WvZFMgqxi1cn1sfV_w-XsW?Xi&8UHi_?lAlf zU@P!{8GZ=Jkw0qidmt|&%c%x(YF&WVu;NXMkzfs*0-2#LkUh}B;MG9ZxCfAB^#rm4 zeSthrjRQ6XW&l~?IY2g`5XkcGGX7OSUPPAjAg~Rv_$4G*<5x|Fze8qu&Ez98xfB1e zz<&TaH~&dz5K@2J_=#-rJHm@~f{u6LV8*>BBa#0148CvtMEXB4oXF(I#{Y@&6Pa(H z!TrWhr2kXRZwVv>>HExNByx-n8%|{MOT&q*@KGRJe%$zpOn#4lyedA{(=ry!`jHg) z6Oi}+nR)&X*g%zzGi&s$$xhUJ2L53&UXuR<^0px+ACVKRA&}$V#Q2H4#YI4t+uZnp zy+B}uZn}E8=8$V?Hb&;+z{KLL5upAG&3Pn=7Tj|-=x$xmbjIvKpu_=)s)2C|}&hDZ5ue+pepMk4vOK-RPykWw`M z;qde_{@!%rA~K)f@Vdxy5{%y0V1JXZ7O#MVEw~=YfKdi-FgO}$srX4|N4^P^_Zvrd z5Hfr`{2b9_Ao(pO-+zLPOEdY2Ja%TAa&iJAg@nF{tVpimL{5nUAaA_D;6kI<#mk|W z!O!@$Cf_{0rDxgNzTaZ>D%-2n@K;WcW{z9o@|2C$hlih7&nb9StY4{7#0~ zBKseew5~D2e}c@~&E)?(<7K+aJgl4g|8G zAwXVrk@-fLe2GAAf8z{KGWm%tXCe^gCtLgybY69lkF|98I|56MUKd%wy~a;uh3+@p zh3Z6q%yG)AAhSKnWUGs;=UU?@G9SM^o)?kvj~o9J#!qB>HyZx$kmWpGpC8&t;(tW8 z=UGz_k%60lbZs$yB2KRZ&>oiavT^@AWcXI26IpzP;Y8}&3~o30s?mwmD}n5nT|mB` z`4q_Z{;R%uEFqyY9t83tvZD?g{}IE#GCGm^*T5)Xb4)|d{7ZnmVQYhza(dz-(%;5# zA}i3o0ou>NjwVAVgI5~t3}lP1HvAeOFCy#G&G@?;9t&i7ejqE@*WdtygMqy9aKjUe zk>CwR8DR{N7m*pq8@$>0iS$nfvPCHdrx=|`e=3mWrU6-z>4wiRJQK)rif0=~K9GTP z44-dsp}``9i-6qzmH~N#yMZj|ULfP|Gq?iC8$V$9N+9#CHvA#s#a0;-?8?UtK5a5Q z1LQ^IjW!uhWWl>l0FnFde;NH> zMn7nDB3pC>$Rp_wK#u({K>XkO1OM>E(Ezn$zD7XaCyccXAlF}A2P+Vc48+C;FEjx} z`kMf`j4lVVzzzmG0lCbs1u|b(Ag{Vee>eE~KE*&_5(^lN1Z#RDkYhI1U=olmN(Qp4 zrvllM=|INKFgVL#7LXUQh1!&kqwlNfp3wgn|L8X>m*BoFasM@@9{&$f@1y@$Zp^9k zxM@ILWXC@RKL_X;qn|_EpF{gB1WtjMOa>x(mEm=f`Cl{sx=8=)#veeq^oN124+P=s z4`coTD&rbYBi#uDjcbs>!NBt$4wjhSSOnzKS#0?2Kwd=pm%!MeOR+Tr&VM*~{=-2` z0?vqUIU8^hxhbFja1fnv{=-3Zg8M_l^B)fW`wt8G{5=2R;Q0>+=%A0$csq+cOMEeA9Wt-&wn_Gj^K&){D*_*KOE%3 zIsf4xR|Bqp_hBL5XFLDlpy{CV9}e=vL%!qJjPt@EA1dcR96bNwpq>!=LqqPW=RX`Y zOXU2AgXcdSJpbX~`40!reeC%E_YVih$qUy1>mLrPR?|HM4=X2}9C3xwaQozn!W1X>hoXO_S&frujQ zoGY+bpkuLi776SVh$_*}B?5;8dM?t=$G!;z#)O2>$P*az%c>;W7@e&;H1Ey$F;Le;Iu&E6WX~!phjTq2JI{t zsQ;v9$pSA6gg>R7(*&vnT5QzLEP-7D5l?I9T!Fm;9iP$8B7uDZQO|1U5`jYkJvV9R za)Dz4{&MYHC2&$;&}Qu{6F4o9_?&ib5U3Ft`@D9R3)Fu>vt)sn1;V#z=QM#Tffg@n zXO_S&fryv1bFRQ%fsQY0XOX}@fvBz8xkTWQK+jjSbGg7V0e^*dt`ayYFld{0mI<5| zNZhWS8w6?u#=feZ21rm2@=LUfqfw6CCXSqQAe`=O2 z@UlSoZta{VP$kgfZSBkw*d-A0j&{x!*elTSUF|Fq*e4LRM?04Y91`fcS38#r924-r zr=6<=P6`ZqUpvbLP75S{pq(28Y6Ql9sGa2k^*_=qS>R=X@Q<}~nn0C6i%+yOOJJ8k z#6InuE3j9f<9_Wd64)mY^{I9)5jZ5!^MH0P7dR&1|1a%aC2&$;&}Z6NCU9CH@n71x zL7?Veo|YqOBHOCXz4ol180Ot`RfTufyNe%QH>%sA%NG}Jb)G-@^wra?1C`H>+}iJ! zZZVtoZQC7vIREY|uU&WhJI{Z#B0XXK0}kE@@K`mCYk)T?`=Dn89vS5a@n^nSjgOv8 z9<^u4;TPIoaj*5}`iw2!f2NOqXJyyqk=@Jg8`HV!gt{VSe__I@u7lql^yhaQ-#_-) zxMstr=gxWQwT{WxKe?w)!Q=RDgSkTX|Ml0f1)ulH9kA}FZ=6oun_N07Gks?(d(?r2 z-l^|AGIsC!t;c7az9#09%3G^miT$leQ^#iNoB;qL@9 z4|@9WzW@)^K1o$b?ZZEZ+UAg3ov1Pn>9$o--4movQmsDMYSv+>g`c})Z&ABNjW_}| z@~~S?Q3Z!}>|Uw|gVa>j`G{7FzJOYK#2uTa4u~4{CDiyY+-ka7_Jxi;MD;|FnxSI9 z)avrDpsxAS9XnGU7uA0h>d3F$YNlHKm5x10^=y!ut%e@eYS}TUn~u6;XRB&a6TgN! z>6ly1QyY)z*cz%~U%S;gD(P#jmVX1aGDw}L>~FN1d>m@#H}2R1RUvBlw@}+0cdH9k z#&I26MRiY*TBurmtJSRUpca1XjxAQZMUD6#YUForb&)FgPRH)0dN4>`tU7wv4{|I&D4{o(o zt^PsBo}_v3RKtF9t1DH~Pg*Vi z*>ka~`pJ_}cZDit|E$&IUl5!5vpaURst`5&SEy}%ajOrjj9+wY71cdK>KfJRSFL89 zhFbWmJN8kvThxf(phli{tLs$3X&t+l>cJrOG1d7utrq=WvMs8Q8W<9~On zPpW0V>)1n7PXwtORcy6Zm!E;UrrI6*j5;o={|~4m&$!i1YV{c%dy?weAa%1E`iEA_ z{)D>e4|nYIs#?^jeqLc8meJu-Res!>8w`EYoJyJsaus@qt)cUpk~&% zV=GjJR#%35F}#1d#qBENFNUtHg19G0tX$d3%g0id$J=scq1UzRzqWF>)*`$JjP$t0 z9jd^i)xA^?2B~kT&R(q+)q`5x91FAyQ@J3MEG<2(m?wu1VjF99`Gru|GLnPT7998JxTR! zkotof+C-~mO}!VZ@+Mx_26{?Wi<;OBv6Gs*Oa57HY^q~xsD?FjtG}wGW?C)32x?`J z`kS&Z(rR*ZsF@eJW2;q#sNpT3wrTEG|4{fm1fT&TIK#gzZ zRvV~gvZ)-RdLl@*RqQ3YL*xnu6R_zuw;&P~wm$}vUs^Bsm zyO-+0AoX(9`EsomT>-W9a(8S8bwJdp4p8H-aI2lvvMY4#A*v^W)XpllgI1S!gu14K zJN9aITvUH2s3SYN)kw9vqmDgE^=y#ZMGftw)v_z0ZtCQY?W(FpP3#PH(v@ztyV`iA zj;)~@*4eFIr;<8rwfriml|gE>vaix=^3_l?uX4x6stQrVuYua;YPTAvGOpIKRaEx` zsqw1SHCoMzgj#rwJ2pY>7BwOYYGkBa?W+nRb?jcM2ZPl9s&kZ9i@HE9jdI5hR0l+j zx)y4D7q>cCE$gCV4^cf4qz+ZF*J^cnSEy^Qb;k}@$3^vbgF3RSTOFxZch#{csh$l| zuUA96X|=37)J@&ou{Wq{Q4@PWoz&f}-l#Tq*ReHJ!+N;Yn^aN{t(IR0wK7N@r|j#r zn%om==5_AaBvm15cr?^DJ>BX=mC;kjR#Dv(q)t+;@QYwOZzfx+&frJ6lzYnwS7}lHaZ7 zsf~UeTSGN0!L80wNeNml?*p|mNS&wbK3Yxg3pKNkJGMYoh#KAx$AXBy?qk71mC;wn zR`o;dp1$tbLe;9DR)4Z2&jzXYs-Z))S~e8wrXlXw6{=d)#9>e;4Rx!I+Bj6l)=&)_=2lm#q+wbu9}cxL zNL9)nuGQobP&0?SV^^yRQNu?L#^%w2nPV^=y#3Sq;5Wt7T)LZo1JO`@E_a zHSs2>lg7B!Eo$Q!9a}>+>?XJRl1jQstL0;%RtBkCl|5Fg$>X4Aj&;XYs0vZT$3ty1 z&aG}&8RK+p71cdKYNcv5UaMJ2Pz%SqV_#FdMU9vMH8RPq?ob6uI(9GBgF)&Ws`CV` z7EOd&I>8_7K$*LF#T5d$U%TPlCGUW_Rp6>bR)>WT+!2 zxz#;t^&}m8lIqzY^*uE-S*vBYK;4w=j{QJYi<&qY>ZDuT>PKqhEjqS_YS?7A`iV-K ztkv=qsFgwLer2a6D%8SM zckJhCx2O?mP$Q?h)g!84s*c@D^>;Wrg4E+GHeIXBr$b$n?vDLV9T(M~0d?ebw|YXYo~~n0Qau}_{-B0tXtitx)J+-g z*i))n)Wn%kC(UrHKdX&1bZiaPu$gZ4SCuqVtL3wxRtBlRDSMVylQW@a&T_|As|r!W zv!J%gbgO@;j7%L{MRiY*dRDc{(rQ*V)WR%x>|biPs1Z3(BeR2E0M}CsvgHLZ<%5ue zUI6FlqKjrjEzNPOK6OCUs9dP=v)yU~wQRPIJw)|HkZP;gT&*t8gSsZy9otA97uBB+ zb!47f4O6T0bnHp0XM@znYG}Sz%jQ7cl<$shqN+troC|f*9Jks`ZJeWHYp90Jb*s%) z(p;^U&x2YSq_$M{Jgp|rhnhLh9otG(h#Fo1wat9D+FE7I*RfSp_XMeJRI385W-WkP zSm2Irt9FYTu@GwH0=L>;6)e!Pd#N4_QZHAX7izWWR;Z;5-LV}MJ~HFMJ*p6D{H<=a zlUjDGRu54<5u|ojv4vV)UIcYbp*!|!bzD?`G1QSoZZ%S^F4D0lsh$l|yQrbXS}iMq zx~bS5+f`MInz#t+q!PEjl9FH_EiOU=-9nf z4+g3IRp%vIEm{h-bcs85pgJIG)SXb{m%7!#YS~g9dx+|ZAa$sUy;G~pmqA@~r#p7I zIxedJE~q1yxz&+s^)elMlIqzY^?Eh*F0Gc`4RzC9?${etwWx{rK%I2ATfI?jyj#cC zPz}4st=^=P?$K&_Db&g!b)2$GwVJ#fYG$cBHc3^88h$U-Hp|`WM3u2z$5v6@6QoX3 zt?t!o)_qV5?{&xCqIQcKu>xx3eQq^H72Ky|_fkC=q^7FQE3{g4Kh)9{?$|VSK-4G) zYW)3fHC-*cU&kJzdLl^8P_d3ymp=e?jpL4;sg8^4UkP>O18y}_t$skqo}_vZDa}HBW6^rDJQThAFo?M<6`)yc%le zgYMV@RUvBlLr~kScB>0j#%k|~x=+Si)s7%fp=$MzJ5Q0CUF(srdoEVHgFGdw{lkGM z$+Jim)Oten*GS!_KB(j2f9Sbbb$%oe6{5fY=ytWJj;F-3tUJ_!T8|al4xS~d>zY86 z<-HJ|rD|ENr$m1t^_}WSttUi#mZ{jX+NjV_MBSxU)bX%BcdO&I9U-U*hOP}nS)n0_x=*dE<6$`~RCOIsA9(IpH>^7s#dbMr zW39)lOMXCk)(7&)AET^PNkN`f>iHm#QubqkC>fmxRZ5U&wWr_FlM@DeH`k>Zh z=@EQPb$&7sg|R?=9#@NMJu-q%r~|bg8Nm&z>r-{2P{SwHvRaRf;8W^Itw%<1ql(>l zE{g4XTCJ$#=>yL*>UbRwNAOwI_vt{CjNm4y=*&#P)t6E{Jf^sHOmqBcG&dkfXDO>Xrim9$CrmU5_-LF!gzm&@L=8ER&^JGMeq zh#LMJ)Ha*l>UNc}S;tmU-4mo%s#ecwHS2k(h0nQTUsJoG*4>w0SM8s7dv>S-cyNkq z|CM*NlhY@}Gg-BH0YBiDr=EYoo9f$vB^|C-Z1J|FsCsPij`!TOwRDU3qk5k0DrKv8 zupQY-f6r)1^w#IMdf)SU_HA9X&AYvU=k~4fJH1^Rs%yXU_Eu|O_J*jOcfD7JtpbUVHX&y6_F1&Orw z)mAwyCnYsIKMmjP%B*bRd(y%&kIOCA?!p(H)BlX_!iJd{naFKf4}XDvJQo}>BQHDaIt1_aNuRKP&W*`d|4wgN zC>vnsrsZYNnwy5d*Q-43-BRzl=ANzhZt)(~RZ}-tdqV@R55(2`4{xY%0)DUL!rGR# zcovk6Po0$!H6u^HcHqs9TbKXged!fW7^h*$)_QD;b@NU&^2K@y#a7KScw6I3G_mL#tDW(Uq7V1izo5|GI7gd| zn6=iG#y7?kh}madWqdc8eB3p=7~eRPuL*cp;~Q^$O~J>q*|?J6W3RCHil1W>aV49K z7a@HKGO{~wfsb9tE^T9iQ%t@V@RgvysZTNa*rkh%Zz_E3(TibAI4-zm!p9^w7Hg^a zimhx@AiMr$ga;YH_kQr*!4NamuGx!;9FpP`NqfLxXt+H7$1k@X5*U+Upbp)Tt z@#nS3I6EQDAYQi_ALl_Y<68`5mv@G>FuprXKF*DG6mcyvzN?YuFV%4FEQOB~@ETZ| z@fABJV9XiM|!LA?J&L(@Kt!Q{+My6adIiWYBIiI3gl9%G`=@Y zJ}#y8@Np*ZGQLqrz`f1 z>r>;LfHcR4*8$_3i1f|ev~m5H@!gE{B=|VH`7<&sa1!iWRGPE`*Y(=c~ zXM8zGk2F611`f-c4b!h`fj;BQMcPLCCiZ`QB)IJIV4P^2JPcrPK8zEMJ_gV?2gVhX z1mq9w&^H&x6~n8MDQ_Oqfgkz^gOBoj7=LMj*M;!uU*;+R;jbs~YGMjpfHZ$wh*wkN zTZr`4CSNn-yA?i+qrL)P+*JtH3`56@U*g3I7QsG2J~pz2@uBhZ%S>z$f8mF{Py%}a z#;cXdxCrSjFt+Ft9IZWOp0 zZoQ+bUL#L^uDEzht$Mv)$D#bMqc_1e!+1J-9<~L>Z(3;qYYDp;#uHfttSzh^>@wKp zuq$A<;eNNPv^VOtEuMztbl40SfAo$&gvb9phd+#WAB^8kT?Ts;widPywjRds*iM5@ zgQe3@>~t84m1V41KiST>Ajve~d)SRO1NHm5#*k9sZ=^I-E~1u!1e7Q$|Y6~c;O z{Kbe8*diDYZHr;I!|s4Bfh~pI30nrc3wAf`9<}H7dYy_>(RFFCRp?PJ;~_9EQ$Aq$ zJmK?$&kH^u_&ngY&MliOid!;Q5qDg@!vYIYP7#bx<`UQ<*ln=Iu-jpGz~;f`!)Db( zC=akauugzYgxw5FhNZ%$!gzG!QH{UO_7RLnH2%gNe=e^DtR;*;rN>`F91r6!a!!Cv zRMk5$ep8W5gAIfYf(?ZYgYkzT`=h@6fk^xouhj=>{^n%^SSaiR@DE`h!9Iq40^0}M z1LKb%&4%T|@?cz3Tx;A#xW>v*-LUWe^~?S#Dn z`v>ez*e=*xuz$kdhP?yh8Ey}3FYG-S&u<^VK7{ey_A%@e*gn{P*r%`qu>XR62KyHb zN4a9_5E7rm4#SSX9)djrTLa_sn$O}xFb8}&>^|5mSSE~T0sbWADwu*j2wM$%2=*}S z33TKJ*po1R+x$}4ov>xFyI^;Rw!{iSq7=3qb}#Hc*b230XT6ri3dsjyt6>kp9)>*v z<9Ra!#$N(?6802~Khp6cj9WQ>Ib=QTRTzJT;}saU^a;?~pkC$Rn_(}&w!yyv_9P5n z3Z&1di;%vJ`|15~tbp-10J_6^z^;Qu!(w5*V7+1SFh8sxtUv56#QYP+gY0{-k6`;? z{E>urVS8YEVei4-hkXp&5Bn6h8TK6PdH&i454cqb90wZ@<71zXc?b3YY!ytw9)zui zEroGQFNH0K@nOh^8lTWSgWLk+>5HeWg;>r_Vf=x{W3X>v$6+_Z#=s`laFuY2)P zB#*&v#h?_z=E3H}3SbMEhIRvR5UdA`zh-wWjE_-1Hu*$s3u_1a2@~;W7*D;YVZXtC zhw;$*5{xf6_yU6mR=$4N#xosXH1M1J`8zxDu>L5hFKj%vh9uYo7*Dgu;rkB8)9DEq ze?#hL;4{EyVdb#Ru$N&xt&V_=goUHbX}}ABK3GGT2UZXE27lq}9}tURx54g&-2+>*e_Y(6Xk))v+db{VV#tO2Y!tTF6D*pH~(DcE5&kSE^{?T?becNz-PR0l@?) zc~`)M0VT?sP|;I76%hkwQN$cj!7Kvih*`>rV9q&540mT1b3i@LtQatyp80=krbkf0 z^Zw_%=U#iJySlo%y1Kf$XNE;O3YdwqS->*XSq`k=x~~st0sHW87B&Dgpev#-Bj@5_k?g;K`kdiwxi?@DPYWFFF2dfz%7|2HXIS`8Xub z18~G^3~&tp3E&vr2=^S-?gjSYmZRBcz%C5MZeTC4AJ70C)$-5I73A^f9~%n>EYLfS zZ29l^odY{2M^Gf&0J% z;31F!JO-WsFF2Ta3H%Lw1U>z>tya%A+pHR|+aTHJmZ~{0wc0+^Z01s{)_r-wXfI05-0>yYwe+&$M2h%EGRuymu zK7!~oz)u1BNA#)zXF%tGh8zK&HF3ZQU;y%40qucGfDK>=yho?BNS`8o2AlwP0tYvRwG?{N*fw@MI>1LMI65)f4eSB<#Dq^s_(Y^35Ck*=f&sN7 z%^*)5;71)Qg7Q~r?*Nbjbih5M7SINxg|miO`EVfsH|ro|9&}(duK7n8`vJE>$XoVR zC~J#r{s|`DnkV5t8Q`5Z@2ojr&9gw~?n2O;z$xG~a2Qwx*a9p-OQ0_BD<9X@!$kw2 zArJ&K0$4)c9lwXvyfx#k81JxBfkVJvH0}ujLje|i8Nj~~$=8f0py6~bc+3H20Q}oQ zeSp3|KOho_qN_)Y^O0YQv3+bb?uP?0Kr9dki~vRgqkyKOqmH;{-uyFA{F6|89Sr^~ zvo(+pC}J9T#GZvj5{ z90HsI%}JmD@_6rpCggogbIp#9XN+4HpbyuK&1~2h**IN+GC(cBlaj9*7dG+cZ2Ie( zv9klu(P{v1Xt;(yz^jZa^}A+lSEMQ~)gsQ$&aj-!EcSOu!n-H##Zn-9FYY7%2Cx=5 z5AaW}9|86QJc0Np+1CS;fYkuoAa9p>o9v2S@s@cC5Dg3ih5|!?jzAkAjD24RT(k$; zqCzOrc6<$}`%KP{!hJs=9Owz~9xomFU6FPHc)!;j=mzuvdI1qYZ=es*7vOMlAizI# z&({NhLHt|sgK@!<#RD`EkK$Tr(fm##mq? zFbS)dzTpitB+3|W{PwkQr^eR0o1(>Yh(fLmjO$Gnz&zr zG#N+)pjM+KK-KCZT<4_K#mJ9skILNGN`S|C4X_SiC%FgM4eSIq0ULqMz%kTer@9g; zJJ@Z&R$vQ|7kPZm4wq>eT$g>+0pJjj0vrT>HWc(4Rm6 zD*lD^4saW|3p@lK0FQw8z)Ro-kO{EXo&l?Y$G|Ee8fXnX1)cz`Db~bu;2rQvTn~-K z1v7dJ`~$oJUITvvAArxmN8l5{)AK&k-vK@p;f9is@=DITCte!Zfb$?7id2J?g}4TA zSZV@j0b@V{%z)T}_$NPL4&(#!0tEmoam|8R;yMv<19)-e-B$;oD-Z%K1lXDzBXtJC z0FHlIi26t;0P}z*&_a8puE1QNDcUz0fs0^(fCOMRz^nO8U^*}jm6sS`T^1+H)t3)d3cQX)6M(QXjwvGAONTV@I6l{f26g5nm}#f7l8BCay4y^eAXZf%Ld2|vzFMV*k)M3K%fD@ zf^vC7q(SmE#-CNy1UJoqrgA2x4boNs_ogMlJ!t{3@m2G9gcrgYmdgYEnp7}1K6s21FFUM#C0!#jf}wp zWfz8xihIezX1AMuxM#!W;phXf?QmxyP^>HwPd!zQ+(=)5d(Fac@|a z++*%-c6#nD_l`To)0k8Bp6l{GFX4QMK7+@JGgy^O)B|9~GXT!#do_;*Vj8ApMr!ZM)~d>LKCTx4>Bx^=f{VohoUs$vM}Wh?4qz$3iw>)G8?HA3 z>U3R=YhHv_AzcA12U_EPCDIMRdSD%}7FYvp1>Dib7NnbjO+5a*0&d669^eqb0vrTV zfc?NefXj9RyMVpo{s2<7jPsd>Q?8?4bILR-U%qGlSxY#^UtPm_{pQ|qWXNd+E65o*RMYEz)UJaeN-VNOU$$7wE05j(6`v41e z7q|mF0Gc znmMk`fW5dr1RMbJ;$AJwkL#)cKhLrf`IV9K0Zm091@wFM_=lMofC_*QunT$KNWG}t zBRqQW#H9yd3Ah90ff7JDz!uO0Zop1d@4+<-%sg{y zSS`=0gAvM}$=YTHOrVZHPS>n`RxFos9o86MtHQC8Szy;}yqK;u?s+u%x(u!l=y9Po zz!lUX<~%(Wc#IKZIaq6ITg+Rn!g4Sl9z9=x6~!_zjjW(-_1uxK^7!_eEh#%smXCSB zGGb*@;6gT&>_n^pw);ewF{ciou?2W;Wv8o!GDqZpo0t8b8)SY=n?2u*-w9$#*bM3d z>A0?kl;;jl)TX#@0yF~xMZTKH<&BV640VE#@>13iDf`f7$ZL#i_B#CX5wAWx*@KbC z_1W9Vd}47^7X|&0@~r3C-WTWtbO*WtT~Xc^DfhAs(pG>v>3Nd01Xy4mN*=P-0P|w+ z!4ry|LiS#>Hh9kJkj8TFy>P=GfNhn{#1r@J0G@@+lxa9W6xVEH-!fBqv`3i_z_!s9 zV4LXzbOzXld8V_?@+9m4U=qd3kIlnz(-8;*m^m|KCVb6l4?s1d-ni!SUP#$yxDnN* zF!P=O+d21~UC9p6W^3?^@Ct8?zuF-8kePM@SRl3uW*7mmMpy_|>tKL~X%N6pnTO^H z)UOT?56wX2`2no@>9`+&)D-9sL;+uhi^rb@P{&TSRPNzHKphV539Fua;{ezLc7R%! zty{fj8(==)TD!`d^9e`*%Ff0=LxEvH01yL21H%E{(9c3T6PRI4RWgl(tk~+g@_Jw$ zu$Ja#8aIvIjLYk2a}&~y03R|r0en!%`P-1U71#o_2MaFeI(*Q05NQ)AkXiFY*@gSL z$md{YC$2f5Ie>IOun*T9Pwc}rpOEZDy2lu**o})uC`dsX_Zt83f7fWrb7R*XJD(e0 z(bz$4aZ@XHtXl6}c`q%RQT}U2@h^=Xe0zWwbQ-te$%Cp-BU-Q1YC^r-z1+Q(qcixO zILo&iq6^%sS6q}JV3|V)QB|{z(vax*?8gRl)1?Kj`b}k9%XB{Oo(Lnz;+3(3f!ARS zS=^?VX`?pWdk~Su$P&_EGRBoRtK4c&EzNaNf~szK6!6N}URM(o)}Rpxa! zQH0QRFxT{?HAr+ElCcmM$6Ywpw^r#JGM%?Ogq=lyg20zUStcksaYe!PP4AonK_z!@ zPf&2oiqVS8sGhQ@RLri&DDiYRu>7Z}{NHHe64m|N*hlk{hVk7en*F!&2t7}de5f_m zH92}hV&mO%Eg$Z7QDgFcZS1WrZ%kcY8+XxEqEoLSVGt@G+`|Hb-$yZZGdStK){|vN+y8pzlD=|A0_Bl4)P9=1=6XTnYgt z{bO7RUyW?}$M~|=&Xgv$|7^H9qu(EQ=RwsEjG4fI{a3io9Tw(fi2! zmiHT<07V7#&KFS|ooymHXzEgdcM!TU)l9%$TdK)-U1!&j9CUbpO75v=pEGl(+YR?c zT`ymlpeMcsoC-2ueuIJ~)cX{wR5G>1LJ&X^+96<3 z*7W?Hv9q>l9xC)61L9&z0q-#&vPia0pyCHsJoIiCmqecJxw$Pjt#Fp>`{*j7Q*3_A zCL|Rv8??B(XaG$&Ade5m4K+kwzoSFTr~xM%XwwJi`XOnoBnSHR!MK9%5%{xa@-F=A z+UPvDeZT=`$%fgB8h!+aH#Gkv1~3ASf$6HfOxbkY(zhGvDk^ka=?n-ArceQoTv?A_ zKm60ing^jGR18txq6GiPP3kmj)81>x{*;^WWK%J>BBxKr_E3ApPms%pdUA4{7JWj) zZ|TV=G(4#g7Ejb3m#0i(!zG(npf)>z%Gj1r(a)f3Onp9Mu$It-s$_HpK$+4(SZ zouL~m)a^8Rd-Lws6Q7EfA(K3`zHeZ`EOSVQ$DiL#3wtffD_|dnxyU|YzRiwDj$`)r zM2V+6o2yap+3}K(X5j3DQbWx+vd}=SK{OqSt`)oyk5-cjm)1MAte+Qk*vO#DF4RsV zxtVVO0e3~FcPsG`4Pb4Gvx9$QDN-Cun(k@eAzhZjhmF(4p3*gvy{-%>cruT%Y}>2X zXw3ppKnKja0`2Jy2_{pGHj=G`juC~mldP$YR&v%lIZ(7#3e-HOV_Gz?wzq?E(sIMHz=z1oSnk*}aM!ibD+9Q4$frI&NxGU6E5nOtz@wnN1o|uC|Hv0 z^)EoecG_CAAT8j{A~+CCM!8Q4g^h&q7>qyMihhm4LdZg{?c=qz*VFJs(858o$@ZeN z+`t~nV7fKn$?6aCIJDT|_+e|%859xkle3BBpcTE$sc|@Jwg5q)8;mBI)#FaBLvHw` z)CQ}{n9;DSmy`@vnmV)xiLP}qIh=?a@_gHsv>JP3KquQ@1w8(I#snV471=w~3(r?< zLdZ2)c3BfLfgM#bm8^Ylmr&IH94RZ=C+NYtmagTp`HAmc%sU!oDuwtCE2$Xkgc0ip z|2og_E*fBKhH}QCgvVvYgeRR{!%Ei`CFtcWDv$@VEd~J(_Hm8XwMJ_myaRz3CJzWU zQq4S)TZ#Q3fKKB$Ze_8C*;jqneO$9HmKwFh#g(Szk?egvToqQX_glJG@EbW??xrk7 zHI%UZq*;%=cyGzQOi_YHn$QiFA`Ap<-4CMgT`ERv3&{kshWe6~8B`rZZf2Oo@ifFt zny;xzHs(?t-?F6@`>BAGMYA1aFiS3B+(WDJaw+fPgXJ(@h*bW-wjy2K;@~l>(NuHE z!8|}zX+)dM!S`4Cj9lNof_zFp=dh6l<_yqk%8Cf5h?^o{{thhgGI3Ju>TPm7g zuHBe3MOhCTgEHOsLsGq(mx1FAxt2BE&nG$SN_i*?TmIFlsa2!}yc)pFVIM|(o-yF(`^j;*H=+LdWH7dYT;x;2?e_z1%pVpa^)7f~rJp<02sOCoajXaeoZmndmwX8%PHc2)n zSa^!yaw)PzkyxuWn<>^3`o!n5d$4l*FOlrn*>9$2$ysa!t;)?A+>T(GbrK-y0pbgDx#@OXAvi$ZMl_fLgLb;5c>Od`_!wD)J?azs-onn zzRzr5M7@|SHwkx%T?sua1SRhV0Xy;Vd21i|_vxf8cLsMzdzy+Dh6(=Zr&z|3@fS|F zewFH=)x;p0V$*m+{R%^%_cRHK)~p&WFDwn#^{b(D@_WYvW@lfGD-(257~+438&6o7};L2N>CcT6(v41!iOGQ zn1sQ6j2!lZCjLr?MtEK9Z|yd-rSyfp14?*{lGw7!CGFvGr>qhWl5(}vjC2ZhX^&B-E|DoVP>4>&)0 zdbhZ&l9jX$RW*m`4;##;>-3)Q9#c^VgbHPBF`>WLU8ooKA5eOC3b#d_c0{(4o2DBb zwv~!1A*zGMK%Z^Fs0%sRq3jjav6G(bLUC*&JN3t1PmM2>QOx>_TkA@V?J>TQAYi=> z=)V4Z-8um;vIt^nkv)1C4+3@#FM0&ca2aeVpG@ZTzW zf~vZmAh1N$fz8U9lsr9V3KL)#gMP@pbJnrBIBNv4B|;=kwA z2L!&RzbXWa|B3LFjym?vl0k9gnIv^G$gtwVp`No)tk2+b_pRtjEGj= zlZ6gZm{~O?+KsGrBF-o(bQoryATX~_wRMuWj^9XNd(7H0vp-_%u0hjvIeLFK1D<NT$@Oq75 zT3aTnlUKbp4x*I0@GnYTZNVUlD}rFaB}kd*VWST>SP|LYR&oD4Y(Ao7OnOXg2Zv4P zwHn@xa>ud>tO+2idQ#y$p6qMTA&nC{6h%5tO&$3dvpVi?vGJSKajw?-c zkpg~#<9?A=&ysQ-np<=F5*>9WZKz-1nB8@xkaFy)7r4aK& zQCw}!N@`q63ein&q4+>uG2=yzlPYlJjwr?#aR>hNO(}3(MrozcKQFQ7 zViI}*BPt@Iwp4(%|Mgf1Ju7|Swx+q`5TPHK@Vd44&9b*cuHkR$Ab5uRhbx>w3oC(1 zGTm^O+L!ouag?st{-Ug8TIV6v`BsHLJzgDXlPAnkIVsevrO%#{gJEA7p65Z@t9jz@ zw-^_{5?#iT5r^Wrmk7D?sy+C|QomA*;vEZ9Xl*I*CyXzfJ$%PLjrvvjZVZ(9kn;pk zn28CXj?d3egd7K3G5VhvJXI+;<7CT;$De1H9C3stPus62>T>E=7bLf7VV$3{3zc<_ z3i-OBug5}Z;a_Lbcbd;p6N+EaZSJhB*d7z>oURxWKl&SUZ~ybHsnJLT^{$LC222 ziu-C5DAW&&q&jzU@MOXn$%~||gc#~))=8l>RartI!p>xy_@WmfRF>QN;$Q!RS$}O3 zViqdORie8u1Nif6lvcF>d8mJ8Wl?;e^S>yQMwFCn^4{sDM9`{=viHEogri%z7s_RJ z^Rl7Ln%FqXx>QQVcq-HBCx%jK>Z{f0Cu%9Jd{yhe571Ypm)#lvs}cXu3#gt<_ppbu zDc@WEWlh7v!{d2Jfb|aA{7OmxLGawYnmQdd|J}}nixtj8nEu!1_8&ydZaV)W)W1h& z-&pQeldMOWlO|RZuFsYNxKxNTjtI1*X zm9FKuuKnjFGyBNdm~w+^B%Pb;{%aQ}_fBYl^S-&g$+3Y{Hm`Yva>im8K|u|q&=T(A z-VZ6Cnwwm1py|KZHx6+ZpE)B;Y!E@I4Wu}Ib5QW{P3e<`cYZv1W&|ksAqObJBdAv( z+UOlYV*@2`!!W_Cb*Y1f5!0HT1EqK*I8l_m&M>}RJIa6~KJ^K~+z5J#y1K=JB4BO) zhl}R>-_EM*Lsc7M8Qv5@%^OPI`u&3NNZQPTYumM|$gGr8-BS^?6cpO45p<=YG*5Y4 zWNU)68oNF;B?t!!#rr6o-Ozo_#gm2c8)r&fjdKHUlvtsYcWyNPqx+L0p>m1rKkCx( z7HB@((WQZ&@)@Hw9cUz(Xe0X3?~NeY;C}Ru z(^8S-9*l=qXCjre)7KFRBdzl;=T{my=!H21MN!9KP*M~n1WSRs?fn(^HYIs;$j9Ml zLO>~wa@NqtU>pVo4p0QCb2vQwRH>JdAn?UA7>v^P{^Z>l8t&z+jAO@sC93)7vrx{4 z@ys9ZZBT4u>Z<}1xs)D3hvNnjlmS$OR{lB z-3TZkFBI3!xBYUDa!+Q1NI5_<5^S^~gK7U1uuBI!R&o7ppVM!)P3JD)$REA+c|MpD z(V%YmAZ5pVuu#>%+Xv22p4#K7v-;FtJ8cL(Zh}4QGjeW!EN$H|$^?aW?=Y&~ z?1z+s0>^!L-fCmY&jI~oD6$!hHZVrf{cp#9wN5akR|9)_dGa!96+0{g5lMG>s{4fr3wD z+ikP&u&-~^f-*08#+Hnun<3b)D<-TRKY|K1$B4U)q=4pVqT)!J)m(~Gi!p6HTl_Dk zjcCwEpTm>Y3d*K5a6m#!XnDhEa%>5ShK`{ir21`R6|){c-jMw1a+?}B1XU)x(c!VQ zfGMwxrPP*?z2!J^YK8lCMt~94ngU5n3v==zVbDw>e-`S5Q(A zYT9$|UdNSDTgS<~$n)mY7=Am4-2@y@0hI7Fs+GT|-1!){wlPXX2b)i#{a|G10tz0@3CkZx z(}VomL7}{kVzde++<{|ZUAKK0bOx)J>brMMru=O%qZ-94>)XJhr-o0y>-P6@TP2brJvt?7G;;Y0|fjU5D0q2(-g4K4+0B*LSpY!t4*;HWwyyI>Y&J(RjePcxJsRa}!7reR%Ejl_{y?w|AW;Ufl1s9$)0w1kQ}w+=Fp`MXcChqZy3_# zc~m_Nx?ew!T83dVo6Vu+$k%J=K;Zhf^Um~p!=W?YBTG*y-C$ycz`UNk~Z+nbLREF?ei;K7Zd@*en+7{NDnn*F7 zpyV9tvZ288J(84J{wl<|dCGwpe$9y24ouNuNpuo*^)o?X!xPWz?44alDsb>3>aI*m z=nTRCP9n$7ST`?H!_Jrsk&CHYXUIQ%@s91CB^Rx}>k>t+#Xk;oa5LQEw^TU9GpKJQ z*_gmn^<6^wyMft|CDdU!(osvuANPiMRAv{F|G?eKcXvJ1pfbPE1C5g?kw%oQwReKV z4uMU~%iAb<2~FX;YXrrUjStE}x_Z5!7@yW`Si*q~ zJPlZVm>^}AQaUKK6_+MNB3{C1<`Ab4xAYAJ05ckQgD6cmY9 z$GnV|A!iVBwlAem-0mN!YlXTqE;#;D%rFlxTgdYRGG7rT1xg<9t9{m^v*3!7hfB#b z2JOBF1)tvT_gfMBy2tA%P#6?nSYR2&^#ph4WeUZKk?}W1t+FZy3Vufj{q{l$uL@NI zx?VWCsn8+01pP7?)m}!Yxo!(kScBr%&4;=qd4f?|;xtt6M;NI@FYPb!bM)F6eOJwMK?Jfm8|0=tEyA&=9vy@k_P5hKAcW8qF^qi>>Db zlyG}hD)n%xVZ4Q%j(7)G=|&g5(FUb%T*n;^>gQRF$LF%a!D!{lXm2nF0Y^`>ckc__ zt~UcSe~#aoUa>C^Zt6*%Lh2_eGlYy!XI=+CSjI?`mV_4?sx)lxVBBELNqV zeRY(G)kf&^2knm(lFcpcbbgQXR(Dx+Ia*Zb?6eIuHVOk=VG|YWFL_rOwn>rW*oNx6 zJ2mq|I4a8l3p|Sw_G@no6x(LwpM=-u4kE`AIg}WM$MTP(@GyH@e>}L)wrx4TnL-C* z%x-L^)XA`)Cz}=5u(VVA;fn+NGzEK}X3*uQ&E!7-l&H~q00Ov4TNFVo3qDQWw9txO zA;*(&+q1XOp#hjeMYhswPEn)mKqW<6TeCN>tT6H{Q(U>c)688f1-DZ)Jej`8cBN7JRNF6l3nwIxwS(%G_&v05R(C@n>ayMRKI$#)sCP<-x;#6e z%a$l%%RU&L5hg9G%li?Iu;4+IiKP__nYPYSnmir~5em|87t6@mvBL+LRR4ot)n(fN z%O5TyvdXZGWR3%`&ig3_3yxtBgy2QPv(D&W20xGM4qkAfyeQX(6X%lPWKENK{d@2+ zhnUt-*GXQiCZeu2C?YqXxi_(LHRW|xSq^!zu-C%1-{jRa5rlRi>=-}N_uT0)Gr1Aj z287c$Yy*W36z@ORe$!=DMlDc?x8(nAEwR_9qAuS0jjLMo_Wfx|iSJQY#C3fSP-^xi zMRlF}IH{XhU+gI{PFkQkc(z4EJo1dGokD%tJqtJU1M?A%T>nrk-WlgV?<-m2GQU|X z_6UmB`8{)?JJq+GR14%wDUtAjL!iHjaoJg$G; zR;{+Jd;b>1VrBCQUHUTs%a- zkHW+d!4KXIEpk{19Oi{L`}k@7-A4SlKpxpIa-$1BFSlS!Ye!3ZRr4D0&BvS3lC$CN zZ_1>J+EZy##%{Bl=(=z`xdd03PU^6C8Y7MT!FpNGJo2Lm|2q`%9!9#X21KGL~$6{eo=dL>M)Hnf?Rn7W_gf5JOrbS`4vsZC&iXX9) zim1iZcRXlS1|oRXLWY#_7$vmohx!r%dXqo_onfIxz`R zTkvVc0*p6wu^v=%7B2@PmT7l70gJ3*C(lUa>0b_Mvh7&AO-9GXf^EoU!3M8Y;KhEuU*P_RouAwBCo-ZH>~@{v!9(4cC&uI8 z>g*Y1<@eZB;&9=vtNVeRU!{c^+(HTaSzYYmd~K#Q7%7*?LGRyZ=xIDUWS*+TXl3SZ zEaOp0!(y||qpm$lcnKNxTb#w#_Gzq(3hodCUk=8T9}bK8Q?KSzFc`9Xx*R5Hb@+u3yYq_6l zf5p8%cbT(MvP55Mg1bA;h<>24>eUdN;6H_fHBFl?NjWH1CHo&7I_YyiY!at2_-agk z9;6<=G`&LeW=LK1E>{(6+kK-)jMLVBvsur|^m%fX^fNIMM%R>e-}LT_!Xd`id|Zlw zz*&Wn^)(6xMQmA6@Pc1RQ}6n#9bGwq&!eoV}u?dH<(FT79-Ttkuj!P8pP3M9N`9=gEbu&N#6J-$Ec< z_i^V)F!LHco+;JVTBYsqo+Y)^Xgj6RcK zs(vmg3WCCV(zRP5Cx2tN#1@WrR-{qVJOmxM+@3}^L2UR9#FpH#n1_QRTm7{H#2nMW z51U&lB?rpQzg9KGzCKF$)5c2p3nOV%j?mCu)Gds<_ty`Z-r`ojH*!Pr<0d@8>HrGE zKcL{@+3eul;9%k6c?1tkev9kMApA8xt^cN=;~c5+GZAQ@^mSU#b$Hz{Tgs#>ShoH=l+6VnssVp+vm@`AZ=XJRn*!xt-kZnW;~ zpeL6dd}Rjm+Ks1Y(mb4aR==e*F=v+lkqbLp+Jl0h>tTQ(Rg-!6C}qSgikXK~JY_iS z^`^HKGrv0C^;7jWbkb1s0;O>ZvF*m4XwB?Kr2B}N_f(|@rj@?%dLNaQY?{p4BCGIwwD++G7@c`C9c@=rPNpn26;S~7Z+ z6)z!};%qB#9#JUjV)uoQXl-cnLaEYsPQy}DR(6T_(-Ik!4r+bJ4CT})uV1wh8#L4K zs<^Bh%!=3y3f_$gg29@ZLGFpzUS7OL(TV8tokx_Ah~PqQ%8t?#5jUzce02uJ?ZRmx z!m_>K&6Cg1b)}bOt!c?JZ`n4FXHeiG^!GA|7r_edX3*M2QWgE+eoF6lPLKYxR??tp za_{6QqS<58CZS33EmEx~6r2P}nm(cQ)kxKElklNXBJ!1wfo%C;=*1ISkb<0VD#kt~ zmt<5t_LOdJgdYBSs?3hexD&x;UEj_VvO*6Zo>BxT1p|GdXNsvDUpda=ib>rYS)D5O zjP`<1R}mB(3SAp@(BAg+a~@`~QTX}`q%Xn;KBMZ3G3ueHXn~4PI^B8rdi=0JxuP7d z^hOE$j?kd=rVksJt|^yz@%gU!&WS&t8Vw>_5Lrlxx7vH_<3zg{zj@CnWigcZkr>l14UMQ~1oh%`Xh;PZn zfa#q;$zhfuaHyO6qGx=L#7|o>Yuz#_l|@9)YGzXA9YQ8ow zvR^J$&~|=KDH~vP$^hw8pkF@u$#?bD?3UZ}aUjbF2GH;57qow+Y@KJ<;o*q?OG;h- zrJlCIODe}rY<@{jDA#Rg|y}2fY?a~(Vj>#XJo0=Y(+Zxqh@6~%ALhG z3v7?P5(_KQie70I74hqvIVkD-dSZ^|!c}zvim!0cC>E*ae&bPoGFx#Ye8wO=5$c7l z!9WOyM6=f5edVv!pt2TIm)}hl^U!mxOe`}{viT@>4OZ+O?*QTM)VD-Yxj4>XB~*B`|GkT5&PA2?F63}w@fI{vB-{5L@d zdEl;i4wh_KH0el<>xJbxD6bGocd~o$`_CvyS@=|iE1q4^v;0Nx*KNqFspn>?i?Xge z(8kSD1$ESO8WLl3{93T@7Rl>JL^=@3k#UPQ8vUM|J4Ok=0iTo0lC0W=m{GYgz*y$O zKn)7A4Y62;bV*wL_$(CYVk z53|N;uaAwMGr=c9tKoeHx?nUxqW#-od#fb6vJDZ9sG;p4k?VHlFlWkkF@2*2t$rxoHzfSkh>-t+0~!Ev#;-*DS!Vd&vV&DtHR{6)xdnr*7&;}Xw=_~+;>7a zCD^kuf%Pb%oV~Wb8Aa~|NlP=@2oggVGi8(>nx4+ED;&*lKeBg6jokkT#1J$31Rnay zsLLyLv$Ub5-Sm~Zpf1Ox@Go=CC~y}9_&PZJ(Q>`qjJEEQ%9c0)o-W{7>xts_emzpn zHYhTvg)Eo6@?m169|7-S=A_@9t&>=DmC)7RHk^I*qayS54K;<)*y~D6#s0m-wFy2r`+chE%T^mF?X4} zeC%{0A062TtF+Ffe>fHKi#{DSc-o#{p4=?h2Y(<&u})}Ai4X#{mGaYiL^`@!`8D!> z?Nhs{Ei%pp@S7LhIu<{ng>1deB}&;;koF&fe&Ca14#=SuJ5Xns^P7SiIhM1^Oo;F3 zuQ?!#AseIM>I{iYEtDU$wtP9t($2LH?~2*9Fod%IRhKDy@=k$=QM|Obz7T4$XCI+4 zOfFfg{v1&Yjs8nKjFDI3Vf!Dfz-k1=+m#tsBNaw&pWd9F3twJ6wiXgyH^!BtHQ z|KtpB6KtiC!*nWMqujNKYRbFT@?Kr_T-~h5^)PHh@jmiyzd!DC@IrCm#a5$wpJ`Tf z{V;m3Y*FpC*o9icgD8KPL`SucNS4|}D{6j3a?!6rT?f=%eDKx~*Yi_&6y@VzFQX$? zG~)<7R2nEsgW_L36qK6dYOBt_ZC}*g_EQcV7=AMw#bqT}#?|y`x4sT2 zaF*rn?Pb)}hW3KHKHq$0>v`w#ZpRdtk7s2H9MRyf8R6q3Zr{XK;c-8q|BK`rulGp! zD=++qB|MGnM9IOs(^nR@tNSLaq>L?9KZd$hK~Wg8d)AuQeut@rHj5%ilq_Bq)8naG z$#YpHVYW0Gb@lxPg_%`#x1E|>1IbanR|?&=+$BI z=S$Of;ZJp``krM+g-%Gd^F})A#k{SX}Zcm=Eb!b-I9LG+s8)Qo#P*)#@x;(n$rM2DKTU8mHRkyz_d7cFK zaYC0z^8{1CgWiX-C~_P-nfnG?is8D4McuSfT>`ukt{u#(o8#EYbu(<~B&OmLvY$ErJBDd{wphTmLi_i2Rb3R62U{k9vH8jn)scm_w-czo%126enF$4>r^Q)Z!rI4 zDELuA2vGgA0C&=-XRvCx64we^nT|_^KW+ znDS9>Qq`xKjjGIf9np8NTrQ1&=&-r$?9ydLx&H~5VKyDh4U9`u2)g)hxVQ=kT5#`wZS;bwM)pa8PeO%6mk14h!#JUd_PYZZRoI(>zQp zscv->XNvYUanpsVyuOfs$)OGg6DQNKzERyf_98126Ft{0HCN=l}AH5NSd%4T@Z+f+yS$O&R`#yVhN7pv@fA;J1FKz5{?aPI?uG@Ov zqJ)0G`z&5Btr*{~tpC#yT9JR6Syp*54Ceylfi|!(Gd-gibmvIRY65)+koJxS(~++^ zb&q8=2HxDjvRVO;0b_s}MMarK8J3j~o%!A88q>6Z09 zn#T%C(sO3BSyREE4SlpJaDc(W%%Z&6b2F_{IJW~|3}iW3_=gqGOfSltmup$ip;gh) z&oz90Fsp>+Hh^As4v1c+z{`N_!373`Me#Fc=S{P=!J7rQgq@gz(pjL_=)0k_!P5%U zGct=TEA=eNcQcR;`2a}%7j%sNp$;DQS%Wl@lUX<;vzujwD*g!FWskGci;4@-(!$I+ zMQrq%rV_$yFisqbjX;!L{)Ut6ZD$uEx~#5;lkRObaa;?_V(0RMh3Um;RnC0&*|K`J zluj1U%*`xK;$|%2lvA7pzX3R`4EG{Z@x<FaA1Hv}Qz2#n|=rQHo=t`s0A)STSedFfc*J)DKUR_*&?x^s#r0_hb#K_)uee3z7s z=_)N84rFM)?;*JZ(AnbbqWGej>D{efs2gjcye*KWPREpw&&n-o2%SCnsF(N^=)tk9 zg^A+-9guDO6sTuSlH^+mM15uD`$4ecy#}3R>2lN+mW3ryz7RSqo{^W2u36TP(DkaD zLCvz3q=mZ@eF0rhrJ~@*B58zvVb?CGvABA7%Rdm-|T$e;F7Z6z2cUe zd3`a9UC~GO=y?XG0I?Fw2O5kAvUx2G)&^vj~gs5njM^GSys{j zu|EVmnmerg9wfM&E*~fjSpl7K32o-n<`hlKE5YtENF0x$!7TVI)9_s=l=@=l-G;5o zh7J`i6orqVa~ML=a7V!^&zm+U3k6u4byTeKeaO$&WaQ<|3T9eXM$YtE&Th6GX<6q3ufi<~-7hPDGR?BEYRdaviA@){0(yJk1*0tMLg4ps;LM%_I~}h9 za`pEEGOnLpCHXId-U@mPAmi!<(r@9_5~eqSG0+QuY|yp97FLQ?{>d1L-}?p!f?z=h zQ8DM!dqB>Uz32fe%$QwtJ$v|y)81}XRykrT|xjGsQuvY>P2zYk>f z=|v^E8MA`LlaQayco?N(zbwBGNdM))XyAMxcam+_NZc}@vsH5^$sN|jx9ooeHawo; zaSFv}Dhgtp@~@MIO#rrr-p61lo|i#qLs|ftzjL}Yd?~P*Rc4jnfJ9Rep?LkL0ZcPJ z+LR#+<_hSX5p%HEII!0Oxo|FqofR#_Y@+>FkLckOKo4*kGNfWcGL)YpBWK1NK!)lm zAX~o9;4MJh3+rZ1C=#Ithk85%`8XucWlICiR4q?|oek>>;;ghu;d&%WUpe1!J z`@TY!L{r%H2&j?!9KbK2HLKTN94EayHQz{DeFjP@qZe}qyW(&J>FaxKOX!M+s z)N`AA$(EHe2XUIw&pcSsy758Sn}823mhT*C=nZ#C52u1>PitpO!-lRB-VbC2p~X4} zQHq0ZDX|X)vfQk~yqq!&krit3SF5GRIhlD_PB@Jiy$aYC4*6?j>^=mt;(2+6)8`eY z=kGQ4*NmN~{+u8l^Pn@b%i$jjeC%GSczRxOe12hGJ|3{j@}^~5)-NDf;8aw~$x@t| zlV5^#|Fu%UN+2g)XqyNj^TkgO7S`S;V}E>|%$@A@QW4sLQY`CP=#Ah%7Rd6=xu!gI zJ>t*puMqbKUV#l#P!AwOn46Em!W9~#f`=p$6M${N_XD;BHrOcT9D7*wSD=#*Jw;_b zBKFYpPH20IFGB%r(Y%?#jG3q?ScK^XYy&%cJf+f%<%6=lZwEGmy&;gR7-7tapPe`3 zG34WFSr25$3p0xg8A0spc)FOL8Hz*$l*?5z{4wz_>xYC5!w)Es#OJ1hzZ)C} zw(KO_W8nS@0>S$KW&$~Xo7A6+(TJZ>lAoKGUre|3Y10Zb=VDZ!mjSrP1bB(Tfd(ToD_^-?Zi(G?NQQaPxsT*@5?gEciH(YveU_j006XCo4W1BcyhU{wa`C*oJ=$4`)+RVJjF$ z06B+~ft*^G0Gk2N1+t(RAg4q(6wFBZpc5Z?O?VvZ@pu=^LCku1Qh79kTmOck$1&Q4Hc0(JR*1g}^_&a;Wl4_|MMZA_IfOx!#}3XZ4C*zMKPR_@+w=9Xmywu+1Veu%unDlg!5Lpl zOQ!%im6Bm+sQY{+J(-<3m-Sk`pwoVl!FV9^PtVNE$45F=XXDoqsO@)W z&hXaJWfKb}0HJe#K5i}<_%sEGTY9lo^^NeEMFn#*aff;GfDG*`A4vz-9Fg|7{Py$| z?}>JW4*pBQbI!H+P681LwC#?$uV)r;$sRn^qXpg-+_Fb zn}d!^q+djS1~U5JG6&ZI+0mDfpI8B%L%#sXii-{J1vcjHwFw0@1MvnNVt@{G&Yc?# zW*dGK@LbrFfZQH00&>!y13U|e&7d%IM&|Vq&{>{okY`G0@qulZY4_`lZ2t z09pQnz%s6$JCIkrA-7dm@%F_6o@ zB`^lK4dt){(>xws;cDotxHvsOecEh%kbeO2=W^|h3W&2JJl4g)?|^g+eMlJE_#TP$ z$Q{gVfR`g*JLEeD$PNq#UI2_hdDOrANE*5y$Vkm>=+X5|0!BkmjVcqzG-F5sa$^hF z9vz9bKsq+WV>l!80FXV1Z0ylP@GJC2&@uLSlHzGj&JfFBXUOLQ8L3Pl$No}N{<%O# zsHiMj3it<*v-J%id%g|Gv0NJ?1*Bh}iDO-1W)tJ+1+wDD(K9x9@!66u2gpc5Jb0pN6MbqX4XHU1rwUm5)fQ(2dgXb8mZ6Wq* zAbb7+kaJ?E!777m3@#EZD=#pH$p(iS>;+`cE;4ww!Bc1$?cV~~&;tfvH~5Uf6V0Rn zhk+cjKko?E|Nl}?{?9rx`M>B%SzGBTZYg?hth`v}#;rh}ZgPQ~Gq-k-A(#*3?9S@o zbc*eGMfhWD==nGc^MUUF>9O^|?5A#z-PkB<&2#0W-s=9+k57$?ySurk!l~{Z@O~TN zbV~L6QX(y@yAz+9;>mR?Qv>$v(62y|k;92KX_6gYU z!OmJDoa6pPPZKAuZ@})2#nTrypVPHZqP+mx0B8-Ud3HM0eFL7(PF%l$y%viGM>v#; z7WyL4|K3iHKgAPps=?j@y$4vYgI3tPph^9x$8Lxa_YQe_`a9MA19lN?qhO0C!fwCUdTAt*@4GP$Q zhlM@#It4!@MPVOcqavKHS89cx4V^7_YK7fIr%Sa$A25i0Yo~H>z#fB%FXbG65hV`^ zc)vN@$sXdj&&OuOrulRTuW_n}1U!#9aYF;%Pn$Z~L;ao>PUX;mFAEE-j}zY~#q*34 zH!R@&t(lWO%yYlsnb<_c^BRV_1=FiPt&aH_st zLdXUtdipqVX`D{Mw1C|l^LU8%LOx#sG`|!7MDHjjU~cGi9hhjh#HQE{nvCp_It{V1 z=R+H$InIf_(E8~)dDgl3Vni3rDhE52qXO*EsDS;ZVHz@gwiowCM%i`>(23#Dq%@4J zcl~)zr-0x41zv6LeLF4!~a0)V$qKx9x)1+Z61_Kl( z+pt7m5ZXB1%$Ja2N256K_9>%9I*i&7+#O|Vp)sDjoa!+F`yJR=VHC&8ZiM@#OsK9e zCq)?rwPRl11dSEyNn*bPjYDiX$G0X$wU;_=XUpJ3d$iFo(N}7U#bclz#+(};uqR(= zS)*a`I9>ZDdLDNwVQX;_rU-0$4cpg2W6UrO#wB{5aDo#8_UEuAXxs7OiN3QghUVn# z=kQy$YLlb?!Y5URP zDfShYh4QnJ^Poxj2&=sn8VAth93PhG{q-{E$aQ{CniDrQV6VQsZgKgOqM&ft_2%T6 z>Qqk+c-A^`=?HkdWsT9y@#_-p0T><5J0AzlcN4T;4%&bnA#Pg0V>`iV0eeE{P#*Nx zv)ZYK?QPh)zz=)Htx4FHvorjj2~K53z}^TmTWE2j*hioZgNDeBNVLAxNt#}S+G&_w$~qrrOwrXx1w9!`8!??@!NI8~`Bb`ersYPxveGtjP3@g7faPYBe|}O>{P8`+Wg4wx1K9mSV3$N_xsk{XR7I6w41==}ugJz`h4MqKtlV+udh0eJi%TnD&eSHmm-Ln8R@e0pDIkY`ha+km9=-Ck08} zfYexRd#5hd0i$@ewp|}e*>53r70QcITcSNlk^L>}N}XdCQp1_@Y;`J&0^U>oog+nl zge9&Rokvd)6Ea}f*n!%w*~#{ z3r&U`I{`Pqxwx+mayHHN+aoY@Fe2s6oGlrN9_7T%3wS>n;$+YB;}1|O=LI}to$7g5 zq4d591I$@Cw(|3|$q|i~bs$IHl$Fe z8Qzc5EGt{Q?@Nlj(z2%LOfMrfRhzfGnG}WMr%9fHiN1%R4bao|8>DoZK0n5AjFU4o z#kUfvG|hTOqX?Z!M@lb8Ulmfrn9u$hDOAmn6eZeAuMRCN2H_`Y3<5%e?J9YUEMY9O zA&K@vXmEuex8ARyafEP}NlWy#8><%`7Scqd#yRn$Q+$sjg(U#nainm7!7}bVPJgk_ z`GXDcR%l(MM4a!Qbt)GIyg!X|jx6+h;+(jf0`^Vg>o;dNG*&4`@itENO##n1C+=o! z9uq=0TkJLV=g_1R9Hb5tWsYD6{1q$smVj>~ER&q7Ur^)Xxe__$(54d%$}z-O0Y)Z=Z{K$LTCJTni1kjrJlm{NHG?8KHQyifPcK z3Qm*PpmEYjDJ`ehkI)KeCPLRIML}UGC1YY{sM{=ZEws8&`hFnCR@)=9>f0ZLhLvzr zZ%>xfslsm$oKZKkQO3>CFl9~oA3~F;aL>AErZib@y|avlmf*PgCN$Fl4FCDTy3oQe z8=4Fbr^eIJBt%{BN{YhDuB*T|71{(g+4nY5gLLDY%?ddee3ukS#fhKW+cV4Aw9;?a zKtCPsn`T=U_BQ;H+VRCno*XCJ@%wzaCJMeQkwTHfaFav|dwzKfy%X<;#v7HKM`QBj z_7tTL0)3!y<>4`FP@*p%S}%Ra--wjl+!*)opm8DT1?TIZ&oz!`LEi$Ta6-T{ZWU6! z>vDJsLbut8NU4=u5;vG zzx^1D>;|5qaKO8Go-8QreTZ!qG)7JySzd$2nFP+C=#9DF>9p4Gn|3`n(D=0}zTHS= zF=by~BGXtufZO*$<1~rT=ef4?WvW8MV_L<0XVW^r??V`IAL^T8x4l6cj3XslGZ`8; zD|9B1=zZ`8XVZGWT?-=zTn>EwZZx6t%|}WeAYVkvxZRNywE(wwWN)GmskcB&(~V*O z-h;-XP(EsFSt^l`K&C-s-!TIay z(6EK@IhCVzlMJ?K6O5+MINqflr)CKOHq$ntyxiUMWmr2F8E@Ut?&OqVHpfMye$S=9LpN*e>$h5mwgc`=? z7eHf2Mr+XG*)vozfY-oBML1 z;Xw!c@ry{Y9@&&?p|Q0R0RIZ9Rea_{)4RF-G*S{YhW!Y%)BU}0rGyC|ih!E~jqR3C zB=bqh__?eQ&IVKZhC*qP!?65{hi=87Jx0C?i1OCXYtNZmho}Jb_1Mh-s}x)B=lNj%?y#^@sBD16rVsO)vJT(5=3+qk}|+D}5`jw9Tc zM#J41`)A8a*-d3B1)*^XLc{aRi_ln#Y(Nb*Ne{56VfBoLHW*xE{mI7~Xk2Fc69V76 z(1tiiZ|oiQH(4O~mvmQXwka@AMM`#qd5^sPuPUpl*u-}E&6B<6_!q(L6B`F1co|F^? zg#(LI5@yZ4(2}6ZHham-bxlFHGof7p4igcfUvD_%#8KiPGzI}H1lnagL#6PvnE}m& z3#B{+Ek)PBX8s6`%U1>}@JeXGpcG#TG=A{ZI}(Xe`qQ0uuc8`$>}Ka6C7W%*4M|Z@ z*fN<`KS1lFHPqni{u($vg>#sK9fu|?z^9@0(;Dw%C!n$4h{C3%s9kjp1~nfVw?Wy{ zc0wBf4W|^$9^Y=6S-2UaqkWWX$rRot};l8BQKFK7*F3%f#q<+wO6WeD1fe z*%N9t57euThD{&Zd(b!+l!AWptpL2(pxaJ+B5 z?Hu{aZ_nFXzX^{U4YwP7;sb^4k(=d3`=pOD5w38`-(|nTzPunQ z63Qs23ipGTkm77dCfos{-;qU*#$bMrhsLEA;~dXTih{y=C1S5bV_zjko_A%~5NS;I zp3v~601k$WkYZ0|H`oqMCN1OhGqkJ0=?4qnwD)*}I*Jee?f2!rC;giUZI~{BTmOU5 zI8HcBB62@LwsO ztB?8dpVS?guvYJZ#zE7EDepI*IGv9B?Xd^tn+a5kHgAN+9$;dO#z7JqzNf(Rg8z_& zSJq+$v?0hP&y}A+V|eAGj`KeaO)YNF+sNI}y1+pmLf?Q!cZ@5- z5_woOY#dlieW1za(6s;?F*F8HA3D5G9MQ5Xk6i(1TzQgn9yA#*?l{kxoTwby3B#e8nB13F%Lnd=Rc@kpSGCjWl;8XA z5hojP;kVAFQ-0sI-(n-sH~R{t7=GLqQWL$eeCu@T{YnsCg2oo( zwu>_!lmS}7eD#hv>TLSM@BQR^=g1#^U$0{vJbv?dBU0=L8jI#V4NVH@dV8Yp-_Y>U zH8zc@$3x#QU~BQc2u*wYF8nv2lK6GfBS_)YH|Qym^u z8l8}Db3~g4ZL}2i5IP8LkmSU<>Q`uZw8a^r+t2kIcq24?0gBJApG0bm!Y`xcE3uK zu!+Mt6Po^p*7pcf_>(C_^b4fM=>8}E7V7`;$%)=&zd4;I1fpP*aW23JcKBW5C= znslDy_9`{K@wGU`k*WG;?!5Kz#11qSpNvB51`Tug z_QZ%!Yb`aai6_-g{X=Gw95LrW8z%+hmURePn(o2nmPbF*V{ovg+z4%&GM|Fzv`0YacH7g?~Q~h~p+;MRe#F;F@>oE(Lt3MgqHQ;2^*$ZtjH2op3Z#n){ zb+XE7>ggSY6t^ZU4?G+u;ZIzzfQHQyqf-d2Co~)<<|ai!@k7yF@g0NKP45;L`aJrc zstdN~K;tA8?RjWpMcbVe*#H^!J!e`2)u|c!vKbc61#BO|M0*4NsFdxMv+QrsIDIhN zKTY(GjZ&SO(<4f(2=n+v@3APgiPo#|cd{dNUY;kOf+jPJce*zC+t(5Lo8~8xl20OI z@b|O5bxrK$u+tiD_lu$RsN=_p--Blhw#m#M97Z%3!j38=a-HTcgAM z&yuonoW)GM6&lON9Rwb)Lc0;eDi;I&v9eLTXMtJ_Gxtx!QCd_#XJ< zS56Uh3tx>aI7f9lAJNLe-{@Yc;?MV__+CVEq$KTDb;)t3lMkLw9z$}tEzzi$(C*EGF*2$`KV4-wZ~t2o;F@_Ze7-!ksK_=zIi&?^t?Ljgwx6ONDh>|U!G3J zonL3otxMWZA=w{c!06N@`c6Q@wt~<22e(z(7a|}l@E5gLs+_XMnh?;c*g^{R3$9Y~*x3mK1e~ zN8iL{>rI2k{aK&5?Q&?cck`)WA2eO!0L1|V51ntB=uWykWF=6yYq#av_Mr@}1 zi+5`hg#MEuyy{~l^mGWzoetqu4{@_|8~%sRg0mnjC>z3pav;3wBO8_r@u-9Go>uKi z=kp(2e}*1)3fVkf)ic4JE8Tw!^)vO@>${=j)LJ0MnAiRjQ-vXh1 z5rkJgEVH6uSf-E;n`}3t9nb2)P@=t3EP1_d@7*pTV_2UVnz{$OGIFSnxy4 zg^S379)Yl+M2yoe-rQNi_B$b4_;T!L)yn@0cd5p#eA?KO_X zMvxC7F_5nyyohY*VS`@-c@b&GfxP|)4iLMGpd%g(^hKnO2Lg@% z)>g}Mp&Hp0BS{9QeSHzxj$a_`A5O6PB61`>D3bw;VA%$l#6e77e}$3A5oP>w3ep#F zv|bXp)$4+B^VS!UN&FWtx?~e!hKEq;SQxQjcQ~!Yz(yMe+*K`{X$OXwp1Xq`V9sQ_TeF$750aLD}5Mq z{Rd=$R~pArCf|R99yPhUr&T)pIRK5m}HIVc}pm1d@tkm<%$16X-<#)D!up2Ai3D%{Zq?G&cq!3vOw2BK0;v<~!He ziPXfy~$&i2qxDqxUs>e;^|?)ab*Feie}0 z>UBU~M3ytnU?z~%4E%%o%B3xh|1yofC5XCNnABXoz6It$1Ko(&|= zUw30fE;1MgWP`f_SyT@o8xR2UgfJPvxkgMr2a6F`5rNLBJGbFtTc8a?VF5FWU|WG zHyb-p4hN4L;t4|#S-=*f*GHN{zY0p^P;4{#h)lj{bRrwP1IV7gV(dgFcSWH8%}=56hW{(%P`z*R5$XSd@!xOkL^kMS?!PSfpN9A=WI+c_J|ZXE7eH2c z#Mp^U9>qT_x5n823|U^;F_VEvhvP=CkIeXku@jl^N2AwAHt1JlC(`eCAR8Eg?$R%k z-9C+F;RdbKv4L@DXfVn+{uwfVBk;r~CV#Za|7XaC#AyC3NpQnBmj*TG3iIf3zVZJn zWPPzFUwx$g0%Ip~8;CRde}Nb3NOdv}L{4)4!YxhR`HyOwVh%*TsNy2BUx`Mqk8Dq} zu@jl^3ZoObEe!-R-(X`WQXgt`BK?LL9jG@CZD9MZ1VJ2QGW zedL`b3wBc3k*J>;XBmgt#^KM9`EtN>cIE?FQK89SADOSn*y|%r^NhU?%U~D);srze z8IpR*@I+R;)96I58UBaJyoluY7<>!J2EA=?FOV0JYpTURkl@@p2qgD8{_&{2sh(Eq zlT;k;V6wg;VUWH5*64qQyc_+1d|VcPnEXUGhGR_haI6hd_d;jZ28M6wGn;#q$w*{I z8^~TZ29i1(|FB`rjlBhpxQNWx%INix<+L{ZxdzWS`A#GH&mLR~f(~&8I~lypI1tHS zZZO_p0+0>p0_0U6nZGOS9KJ-8Kgr~)!?JKgJ)MqzqZ4^t9Rg&*Lyf&YvLVBboyhSY zZSr4jaE#$YSjNOf$T$zk0!mEA`9S>NVgs83?=vz`KDQntOq~h-~0}Mklh| zbwK9ZVDtxtFSD3nL6u_oOJu>DOuqWaNwwM7{~P4w-Kz6nA_=Bnz&|8j1+t>ofNc40 zqB_{eY^HBRv$l|^N@*=XhuYt_>jlpWe{}))MBlw+hAhN(3 zqZ2t&e;A#}iga|tkXd|;`hSVc+PHyi=16G2KSfr^pF`(G|iXA<+LwDf>--`$c!CK#!G$(`do5@FHK|O%1pr^6dN8aE1z}^v< zW%&9?|FW5eAhJcXjQ&^13TK=Ac|hv?C3#-;kqs{~b|Ul5H#!mZm01f6QECVxBXE<^ z{|s5s&4&LgWDggad_?*!2GX=t+pm-aGgg=k%Qb=L{{I45;GHHP(X00M_q0l;`7Wr$ zRfZ-~Ukzkn)&rXXw*cASXMmQf8h|y)ao7fx*`Ehe-%i>f^_QV10BeAp`ab}f|3`yA z0eKN=KVftttNRs5zdsDm>CAkdhUNz5g@Iw=?|+c+alRX5S&fao36Od-APZ~-WCPDN z*w)|$KvsOQ(JukA{5WHe2l9$#y7b|Uo+K$iP} z*voW+`|eXF!!stsR>@!y*`w!yJeGW#(~HTM}S;3zXMrdtwDx?i^>KvUn7Pk3~6r+J3q~c1=9ZlARBr)kVDp) z$a_LJB-o=wAVZxBWKa46>Db@kK!byUyomgmW3tizPf*{J{!bj)fd2)*GTp=L(1TVe zaIWde|93cx{rKOh7}LXAU^-AAIg|@wk5FxgV#DRsSOk?*sKR6?QddT=kIa9!vDZi1 z*BE;UVbWh~{>4|D@^Mgq=EOy0K?4j91pdGLZWATAzUvIryUU!;f|-HIhUooEXFK!V zri9_lcbhURcJk21`T2^O9z;%;T}J;aWWF~{J|ef$H;w+^BQ{7DwAVNi`FL{XyG;qm zpMA&4b$#Z$O+9Y|n7o|7ea?KhdFH##Gv94mXTIBPgu4k3`)9t}JoDY=pMA&4r-(D( zZJzmVlZPl=7XbNW_+Nadsq-V>neR4v;;R3hCL=@r%y*lN#F_6lIX}*Pw|VBf%|H9D zlb@)a`EHXRL*x2yzT;%pGv95V`EFBx$;r>y&V0AY`^%Z{HqU&wdFH##Gv94m|3CU} zb1XN$|Ml-SRdUdCt;bTUgP!q#8i7ICT3aV@LLhCHHf|KC6&N>L8#fCy$kEyqfvo~D zx!RZ|utT6_o;KzR>=ua4*T%U5`vf``Xk)3seu0ESZCosHNT64dHdY867VsBq<7$B# zfkAV$ah<>kfwZ~WxKW^1VB9=y+$_-GdaX?n*eVcHqK#PsI|N$J*T!6d-2$;UXyaUg zeF7bC)W%YQ{Q?OKv~jV(A%R|{+E^iQSioPVjjIJ}1O}CB<2r#80%;4iaic)3z_^>V zakD^!o3%DYV5>mPE!vnRutT8bB5lkS*ewvdSR3aG>=WpCt2UMj>=#J5O&b>r91`fY zL>nsv4h#5i*T&TXH3Ea~(8hHFCj`=#YU4(MT7hxPv~jaQg9@!p5!fmavs@dq1a=6t zyi*%<1$GO>uF%H00{a9yuGGd-f&Bsrjy5h9I3&>PE^Vw3I4s~_rH!ivY6J$Y*2Z-L zCj`=zHf|KC6&QE7Hf|PZutsZB1hxvq+@p$GvPz#)NN>$R~$;IM%Ier;SWP$MvCgEp=cI3bYsfHrOvs1+FZpf+w6Xz-BM zrU+~mhMLxK7}NK-zQK zxKW^1VB9us+$_-Gd96(m*eVe7f;MIe>=0=AqBiCV>=uaKu8ngA_6c;{p^c>i`vnqS z(#FLChXi`Ptc?`{hXwpQwQ;pTjliH+v~iul34yd%wQ-|Bt-!d~v~jaQgI!viBCu5; zX16wG3G5JP`MNgd3hWk$eM1}P3hWc;xJMgH1@;Rhys3?g1r7=HdP^HC1P%-M-`2*} z0yP4I_G;rgffE90`?PVRK&`;IziZ=Wfd=nrZHmBFftYu-F-u^FK+E^EF;`%>K=0;qP#bdvb_>KF!hgB4?2xAq{}Rz-)t*(0Guv3T zBm(dn$~btcLze zvlX9%t@@YS_d0b_SpUDkPX5BprmM;?v~LaB=r7%DhMM@LX4icIc6%6`sqC*boAxEx zoUh!zGt@R=Yst1f>}G>1cv$;x{tE2gFm{$|^R;HD90t4KYqxKX+9Pbt*I?tnakF`< z_gsNiw!yZIQ{ zynAijrlj&_#fQta#iw!_T54DU>LhX#r>$++#kR$`_b*| zr~|^r{s=bpCpWuFE%{0N?)%ZxTGjmI3DjTRN+qArZ0S$%U3FIQ(^2{HS`zFR{RXM>KC`~dUaA*|1V%CpLDYuROLzSTSGSbS2z2hn)s_` z*PR5rJ&b)=*}rKv?N_ilzqx%MQQL&ACENCQH(RNKziZ#kzk%Hw#{NyUIi=Ytzk^+H z%I#aF_6Qqu3T%9>n|)lB)N0=yWDkb1TU6X1n$4{RyX+6Q?^Ei4u(5x5TCGa;y0+(M zRxR=B-I?4`H}|Yc_K;jv>c!Z4+~hV@tx1*O0Xx#`W?xWiyt>>&WKV^$+tttr%~p89 zRz(?1TSGS5=Vo736MdRp7YTNI7`sc^4K$nP1Dn&p?fbgg zCTuO)whi6v9u;h;eK$7%yElw|OSOs8?39LJ7eu*z_o_X@#zcXQx83aDRf(;AcaS|8 z#=fiK8fiAy2D_}0+xLBSK-kzuU{f2r*$>o`#@cru*`s0Xhbp;=W=k7`UE9R%dq7nS zo6rR8$Y?kFPqijm`yL{DDvUj-hQ?^NA{uN}jNA89by8S=4A{wMx!KQE;x_!S^+k~wp+qRjTtyaNi+IMqPuzSPUZ&jP- znw`=N?1JWQ-=k`eurbZS#-HP6zgH#aXx|;py{*-ubG)t{^tg&^q1oJX;Jd7a+xG`` zK-kz8U{hPV*`L&smfCk8*`s0X&nmfYuDfiHsytVhT|+kdJU1JmCZ4C+ zb?1WJ9>)5VeZFSX&I6lszT3B<+9qr**|u%ntgV7=weRNh!R`%X8>=?4nw`=X?1EUg zZ?xJYY)mZJ_;zmgELGA@`|conFpO=g;x5o^Zac8cE^zxcR|kZRy#Q=#dpFxcEorZP z_mMps#5a;%dS9^qwi31zo$<20FC7rbI4zdTs z*sdz>GR@|80=w)ow{LfKK-k#Jz@}dAW_zk7muugBWRHfii7Gi>v!$1VT^sNAO;**y zCd7jsnc!wq)S3kCdx-3*Fg8^U?X1~~1h7?|-M#^JQdoayu#>yE*}kf>i}tM{8{O5- z_E!_TYIa>0u-n7ffy(Zt*|e@;bGo^G2dizu){<@8-OUbF!S333b2qSi!`R`fO%KgZ z=?->554Z0~wMW>P9$@2py4fpLNl)#&gY3aD_9_+EOS8E>!7l6N_Ptsi5H_|KSo}z} z>v%O*Ex`{@^XZrD(J*$rN>0*jX(HIQNp9bXs#@5DB(Nis-RvZ_CRzI)B6})~oveml zq1lRLuvJ&MeXmm|h4o(nc5;fFO;?pE+P8*mbZvATyTS0GeG+u>JQ&j1Khp~)X;&Ntr!5dYM`4fQzwP>4+J}Tkegko zDhFxb8nV%Y-R#Y3;$Y3L8w7TH7`sT>Lo}N<7;MfEx9_cLo3OQH+YWWJOH^>E_T4-L z?A|c;4%KFuW~U4VyI`2xcbVEFY|JpQ@x$Hha#b>1`|conFpOQH;znpTcR1K(Biz1@ zIv{N92(YOm-Rvs0WTf`pNA_qKt5kBDW=ltcU7P0iU8AanO-KVf@=7;*uUd1Z_B}-Q zR2aKf4IQP~iYviZjdJ_0S0{z_j{-aSDmS}9RbHiiYsf~AcC!zviK8{U?kcd`!`O$F zeYIxOMuW|{+U@&@+9qr**|uZcY^4g0(Y~9n2D>+m{hMktR!Bdvhi-;r_=#qW5k0Lu5~dvD?+qNt&&=25i+Nx9>~pq_F-; zU?*SeW_PN}Yqf6;+33k`_Ej};vS!y^3wC=LyGz+qG@CXVY|a$7@9S!tu(f2{Ugu`_ zsNi+lck>jmd&AhbRGX=qopK%61ykL=d(|FcW2Sz*o5g|M`pU& zf2uW^+V>FIQ(^2uH8e}J6`5eGvfRF(s*}R{v%pTC;buQql{2(&4cX|KZuSc`ai(V1 z%>cVSjQvX4LCvPk1e+6d`+lvq30q6HZMK`OR>5rTyEzDUZy5WnYBNi-Q?kJ>nC138 zs`dyQGYf3|Y&ZM8Dw(Z)caS|8#vWI3IhxI#4R%?M+xG``K-kzEu&KFj_9wL@SNrZG zdo+ywStaLbwlo*)+B~=KNmVUuLLS(W`EK?%wI*Nt9wK`RY_xUyTa8m*HLO6Mfb&6D z6}ag?yy{n>{RN;W7rM*#sLDcJb`9C+A~zeMCKhRST_M=*VXRNt#hOhk0-ICp_HC%P z30q6H?Ho62tKb~%ySW(b-Y~YYYBN`}Q|5qOFxTxHt@a2TGZ$?9JU4rmDw(H!caS|8 z#x_-P*K0O+9@u5qyM3Fh1H#5$4>q;L&9+cWO0@4jvPZ+%Rw{YEW=l)JuAT4pZKJA% zO_&dMs&aw$tsxs- z>Sixe6H7I_ZUNZsVQdFwmuWVw6l_kJ+xHT+P1stpZOh$koC=m}-_2!U_lB{TsWuBW zJEa`#f`xA1c(q5^n1x{DZ*sGpRmn}-cL&*nVQg0wce7@5ZvwmQX18y5bwJqIo57~u z;%0lQCAVnbePoY@v56{qk!DM80lRjQ+c#NN3!AVA?8wD#Hbt#jtbGrWJr%~Ls-d@P zwqh~Zs$1Q@0d-PX|E*vr-{xlfs><86Zw=Y#C2qFAnz%%>>uv+PJ&YZw?AtY)wgha> z?QY+}YMZdNWZT~1W{0Za9ol#E?O^wYvBOoHrJ9{`2iOHm-M%B$9${maf{kD1X0KEw z%e3zfvIoQ1t5jTtW^a*Hnsw6>T)+bRxMesefN<)8pe)S$#-hDbUE0y zce;His%l{q?gTq>g`1tE)~wLJhsd4^V<)SjD>YlO0&LYvx9@f8q_F;#U?)3nHeFRZ z+P8*m^j&T?LruI(v+EqN+r!vQWv|j~+Ff9CR=Is=sBOa5l5M-%%?4F)wf5b-3hdr6 zc9v?RG&^NA*agb%o1^v!8>7I+-|c4eRLR}icL&*nVQhhlTcg?ByTLA7!Y14ccI16-_6D`)KJSPo`mQTK z5`CjO9%fmfhOTwzDODTlERp(GrOMRFdKUgEX$|{G~?P_P8MR&`(L$!GzRH&YJJq{&7O(s~w~Bixscbv=6h7%M^1Z{!*Z`$Q)iJ8yiXlJZP6pRRtSdDnniv3$$m0s;slf2tJ@Zn?iYH1Rqor>nt*Y52-D678${Z zmHoGnmyF;>m0r)nem$bL)wA@0|VuNRNUjTw`>Nx>~S~ylsX`6?Bifl zpK!C!s3lLx-a__h82hYB-XeR;6JXbFar#%E zrycr81vkEn3RcdkD~qjNsvDM*UN|cgUFx?0By0avo9`;u|Iv8)THybvf2J<9QFc*$ zNqWv~tBdn&=>cyqPou9^>sL{fN8GJ;eC+MkvkH1!?86av>)%B!$C9z~*GM4d17pC^ zHfP4{U{*1gWFsf*^uN9!CUB6NFN`Za5MurMD#llYt& z!5O-=sb6@bTE4jrKb~Rg&dtj&MiZH+gUCB z5`onE>2}_)D{T3o6gq7e{>$Lg18}2`Rdh};CzI8**@xzwW?Reflz)p-<32Papj#T9 zSu{JCi>7^fpl$@%yXm?c_Z@}9?Chev+@7dutx7-UZC-!SB}AwH9pKev zJ@$+@N~ZEtzCyewrt#^X=}@Yde?w(qjPA3#{}h<>FZN)jGdgW@k!0{PW(O0pi{$sR zZpBMrTB@N>dk3@TCZ1=9{NcUs*+#eVwE(z^cc=l+Mg-c{{vj7_iJ-9C>g`MGYQwV; z&C8NurW2(F?YMeF;eCP9kp^6SV57Saaw!eC`ohMk)&LSu1FrriAJ(|lSzGWccE-j# zcxPtDH4rwGRi=MMVF?*r!%W6TNdEwq6%L1ul{AK&VC}d@ntTY0H4&{OpJwv$qlRmY zZ4_*52-bvk9eaaoJZwy2u3A(1L+ZRHnF7y7db-JYttqf6Z2e5Drj;|*BM)L z*seCVsm68=Y?F*F-PjoAp2jwfzw?U!>mT>)WsI4|hRx1OHny3@89ScU+t`A})&{oW z5MJ5Fh84yaJ?d+gv7HBPq_NF5wzBhKm}rbS#)$1jznF&6&V`L(j)mNZ@t|$K$=43) zfv_>eH^9cgT>zPjG)I)3)|*GO|9(77w*F=Ja}Y~5hnYHaryTX)#_`=q??HMSl|Z-j7y-e+vw%;sPo>G<>YD6FIx zzc5H2U}L&i4+X$Qhd zi?^!C?~L|_@;F++i5L3$p;V-wF}BcaR{XF%YiyOKzyNH|8`~yRaUa-TFt*1`zP_;C z4;v?Pm9h0>|MA{WeQh?z{?IsSE&zsJ&oTh%c-YzlpD_6bBF$G{a3*hojh!C^;XJws z_?*c%80mdT^V()?Ly&%#Hpc&XV;qV!$Gao&1t9rh5RNx(JAiB}*E+|W*GtC6wa!V( z>t$o(TJOpAifgB_r6Ju5HcswWVADT)dL@)s(Q3}_S53xINbiD;*K5Xh719@*0(TkP zXxKi3jk9~Vv0aVyWZ1a>^Zhbx^ccuh#`g_l8_WK`h)Ot<`QjO790$oY6~Ae0<6+A) zwzrIJ0&LmF_O`KcBjc~~auw`_jT8AA$m?ceeqr)Wf{nk}oXGfpX^hvx82ZiNuZ(Rn zZ0jMM@rR9V3etDNnKS-tW4jLNmB#jsu}y`IzMS#Z#+Hus1!(j@;1SwTKVM;Nbu`9r zO~wq^K1Z6@cg8jy>3<`Uu(9WjjO}`)xh&bE#;_r&-_ZHK z=?Gu7!$!}CV7JqaKg-x|fNd+5fNsFq#&{!ijwcIlYHSOT=6KT9%-BkiW@MPZxv|~P zmf$+JPjz}fqN7^+O2l_%e0jj7kT^&u$YqerA@Ps|2+w2uPlFdjZiDbBb~|J#gs)!s z9&!wF9Kz$*&yZgrCn3K z>aBqAHG35v<$o{Y+8wME;837sDK$X54 z(Xr`xq$fZoLau>KQeV9maY=(rBzrbc`*%mgwogSm9RbUL4R}tM;k6%7%=EjDt*oOoUtmnFQhaaWZ5oBposhk_0&i!Z)kc zK;DMD19=znJY+qDFRbIs@Xmp>fbflZd?(^K2>wIs7;6F&6Cslz*Q&2}MYJp9s~HDC z21ABG_)f^aXfodwiC6vVcj@vcVSSJWkiUa_2l6iDJ;?ize?Z=X@a;u-#gR1}G6M1g z+RNR6i-&84C%F>H4UiilrM!#ts8WEEsJT;fE$pnnUw9%-KHc#69LawB8`q!h9caueicb@WvPbs3Ts5FXm@gsgz9ggB79Agdt? zvIcSwrh0KON0}_M`gbaf4Fv1s7-UV3&Sq)K;yCG{JrHFAE zq#VLuL6{0jhfITHsJ^d7G;cW*$si;fG7B;rk`tw3-;21gjK|A<5Wb|N9I_C?7ilbm z)Szm<)#C=pDhS_)u>!(PyE~e89;&|;`fZRqAkV@65aeM9U*Zuul9ofC1w9+W-|KGz ziH5{PVXmExL^DWpNDD|yNGr&Bkn23Z2R9m352F32j#YKVd?g4_nV1F{rS0^tVD+tUmP zPZKV@NK2Dxcd1@)TsS*Da3~SfP6nf{tfvM@{xM@&4})0d>7d`$SBBF zkkOE+K@(bi7;@)6_!+pF4vJ%7PKvqE%Nf55kjcJ{*1r)g0K$jLyHLnakP#@bIpk-gdHRh+BzzElZO8Wv{Ra6R@-J{- zKrTSp{G8x5l;0d>odeM?&f5?2BgldOtF`X{iz0d6W|u*X2nY!5DxhK@Nsu+6Vor!T z2QVj4z=Vi`IpY0NzjX;m0;$JMYJnfgQk3fKNd9#A7e858#uI z1HeiE9}$`0*^x;TAQ(`4;R%6?0~SDTop}#|d563ZSOioCEhA2ce=0!Dd`NH%*G@<` zK|O71n^FYcSf6m4FKvCwAJHuOfK7-)1gd~omw*b7uT@I`Owt|>Cz> z=8^?rX3ppE5B?kXM&4M~I*M`RAT$B#SBx9~4e=egXV?7#fFGB`5FQqudM82mJHV%_ zyo6{Oaj=7 z_|K%z0C+=L8m&!3Iu#fWL<7Tsp+E@0fm9d%OH-Y3(TRZy9g%hb+B0z90pNTNy#@fG zKp&tha1Z%Ckah>U0lk2pKyQE}wEjRpAPfix1_FbCNO2v7bTFR)4Z#IVHU$^~!~m0l zF~CT1AB&Wo!zgiI7>&o{ngzqy%a1cgVXYSeEvD@1Nup3MQwgBuae@7j5kL!`Lt7MnC9oPosM;>4A2AGz?byI>d!v;=rZ)EVdj@NQ`_z}6g$l+RDP0Q~_LqCV0I00Aw~b|<7IfdxQI zv~MyB7fpb9z+7MsFdLW!%mAhX(|{o09vDnTIt7>v3<4$r6M^vnZ`;NJJOxW29SigZ zdI8;l@_-NE40HuTwHSZi=eYo0fG6Mqlmm(bI)GznJ`oxNrhFR2VGi#dSqtcad`iTp zMZ8z!eC{R3++5ZSXaMm3u^rO3zy$DNKfszQ7KeYB5vz*T;smht+!!-sl{2AQ&d$RX z_oV>t1q;Y!+*|Gh3&Hm)J@aCr%K)VTcfbu$1&ynM8*aEg!0Ph{m>H{z$Ck&Bd&D&L zfVx0cfQ4Y%3IMCr8}J4EM9OuTcV)l;R0JvkOrz4FGjS%hkWmMy4Fmvu&r_-z(i%W@ zpe7IqaK2iurY(`r8f0PF0J&k-65ABp3=7y0XaKOFT;2$2WBD56&#G#Qo8~|>ITPcA zv<<+$X$^2sS^+FM4@W0}HPR7K?Svgm2<};!duW6;$g_m$xD6JR>vjRSaduEWc>KAr zJJJ#WGqVQ9B9Et6Pk@P^$7L@I!1E3oF z2wca-;2+i6hvT{kz~`RP09$oGK(+Wjxb6$Ekug}HtirHSaW7fetadX1_f7y0M;O4i z!<~gdak4}_^;9)-BjEt|C=zJ|5Csea1_1-bebzEA9}Elu*hX0OLxJo(IG?q~0^|e# z(LDd-aT5w~k9fMVy17^EwV0UI$-=Q_IG^*lH>^tTG50nrJ@=M-$DQJ7%&B_Mb@`r` za1L%}^7wHEtCER&1I&0P!1;Wy=CMFb!?etZ?SqGk8)qR|_45HP=bo$l0&vY*Re3JN z^&;RN^5a(GVub)_?85aCAQjjNtO9t^VYMdXdJCXV*LAq&MQAP3L|`@07WZqAZU#01 z8-WeLdSE-iPb#+|-3lb}_{XBc4&3Yo4g)Me3UCNG0PF|2Y!9#-*eC7}B2~*cpJ_Pd zI_fp2Or!GUd**Lk!ZH5p8qVuC_l6@^?!_-4JPw=yeg%F51|pvRjW%ZzrqEp z=_SAge9tMX=p*m}_yl|gz5vXGg{lj6R{_|dY9n<(S_0sn@etKQUQHkns18&GssTJq zT+b1-4rpJl#J#DE%)EdEm;xpMGt(mFD5x8t5!Zamln2*lz(HIe1r7t|xL3>a;hLW~ zRRkhFw`(>nRSf3I%`p$Q47LO=Uze7Z~%nJP3IuP^mFeUfUWWWvAK-dBM15H&hl7KeX&0X+c>y!^bFd)W^6Z2;8^@FZys@a$K6-xl}GjNJ)ODE1QBwZW5E2Y%dp zp1fHnt|#u}I^Z9kh0K(R_5&Sp%_f$enaZRS%De%#jUE8oOn0Cgz&6Y?olTY}VP}Bn zP*)%n2m!hP%p3gTGHsoi^#)WU>W6D?urE?J8g4{&Da^bNz;@0(XIH{DowY$#cutwO zXi)AT^Hc=F__I+k!~OuPgoR+W4gq+a1_SJ++XL+B)#2fx8HBvb0IPmB?xT>J0+B!j zkaf6Jf$||=HPn4*U-o6Jc5a+|!m8)qH~{uQQ9!NCmaJZ@jIvw1%AE7(0ds*lz%U>h zs11w&h68zG@Xwzbvi{37*lHB6M*^_`Tm3wW{L8eZ1L1Z7Fdx_o+=4twNVm}Wzwp9v zGA_3R+kj4}#RpML#)p3?NSi`u%#G*9Zrm?IJ_j_raLqx@L8J$O{kZ0sVn43=lw=>$ zy}%xzDe@2T{Tuv)|7xAbIVt|jAJCDyzc($$xqIT?n_kyY)CW^LU31ij6N$I{xHY!W zlZGX=nl3(G_#dN7hd!7(=*EEn0~vp}-fzpE-~Rg5NU)sVfB=u#3w$(n(4PPu^dGlW({(Ka|O%-l6qilnu#ic;ut2AYa4%?t)#K@cHdfWWAQPlOu zDARN>*HsY&duhnK)pu45H4+5V5fEs)(@my}0i7)vxlUPUk<{|h8PHYo^7Te{W>Vo# zAXo|lY#!nrwoY(5-*QrC5E#5Fd-?dAtfAVUO#O66K~Mk$$9`G8s)Ox|2)U&SUX>Ar z(dj^HW| z@2bnJDuYm7ye(03;&B20z#VJfXf??8s)V8Oq_3YLrU6y0L10#@W!VC~_9w|zxf>=8 z$mfe`6HQBs`vN+inngetX*RrZ*Ed&|3%ZJ!i?rj5sh`G^?)ys)n$o2C3N4kVrbx(5 zC)t_#ftzx#OM!{7Ql74#QuxZ1Jm@pON5KM$3QND5T5G1z)~}{l4NuGzVxJm`uCZgS z9MPno7k6KN--+2o>-(nNpVZ|oN_c!x^1+M_elxY#@mnVOLGdE|_>y*iG(QE33h1~$ zA_}^%lN@yX^qL8D8{+OJ{jtkL5TM`A^nE!x;!f4Rqay~YYKFTy6v=ljXett2C@Qni zdOzz*u4$!~p|Zc1FSj2-hna3Ht(qFtHF7)4t&lAmTT-DmQBR9=h9RjHv?YNGqqLQ!Yy(Dd)0W_5j!TE0*xB&kN* znQkndLZZ72XTi#KE8pPDhmJO^+=@^gI(LG;gFt)DlG0X4wvr#~%Fh(-X{k3Si)e|d z)WrV;RKwbt+GF1KgExM^tJOd?ta_6Uu(X0GnP^^ad6U)2>tw-w*yPtzF$oeoQ-2AP zMA3K&{0y`YnK=uN@kV@1E<8BO80Ado?NtHZn+7sn08KUn+q(GOOtjU83gkg%F`CXv zS*pQ_Kef*zx#t^&*~Eg%h3=(J!`-lFdV-Wylj`W2=Rip4J)uH$?Nw3Q79rW1L8uQ! zDRii0W5$!nlnR@JYe(|AF4;*?VK4eTR&tv~21A3lxfbF|&O zHCoM49(FBK+$81oC=Nfvun|$)YV9;!QK6D8= zM?lM_+d;GRX1%{xu+#GgE!3tE+56Wou5e55(V_M2fYds&1Twc4DB)gg9_X_(ETcQS zAb10Arz)L6JGv=O3iI>rHl{WFbH!t=W)!b5Fn}1+TF6V8oB@NOj6kjg%+YUo}Q4j9Gz!owaBS}RJ544$v054T%irS7br2g^$l4r z*&E3u?*7T86m_Q}W##$>Ki$%N~MjFH4PD^2(K_6_o6C-vq1Df3)+e5HMyWSP9J+q{9Uvg$sfQHq5G_Dlst`na-{@qww9~P%Bewx3PFucs7xWu#!fV(khDZ&Nmdq8-FzR* zC?jb~Ar_KDkxpR3o%B4st5|RMct0)N=u{Nsvn{KLUK~Hd3MNJAGjjDc1o@0er>oooNW>e?=gHa+3 zv?Prz40$WlXiLeKHW!xcs?`Q@&Kgup%42zBN20wN8BaBR-N6n9$u?V-z88iwa;MoY zl1(0e_K%gRi6tyA_d0Uhywo1?ggY^Kc`LQ)p{3-cd+(*J&;>W9rB#uZ_CZsaSlpIE zR6-`?~plfx$@9cZxT{5Wrl%5Asb1S$ZRdOMiuOFg?g|xy7T^Csuy)dp7=%SV6 zr>g;#asN)2srBo{h_DA*jkmIf@q4+}sMCt7T4Q8HRfAG>wY4-_3zr#x8p{YcRHPb3 zU`4gS*AmQrIrV(ksF0vYSy}P~&%qJ$%&v$CUvY%(XEGsvp(jP>6K!fDB)2_49)bT9c31X#!7M}rmjqmbQ)4zH)(V+)6odZF|Tt=b8n zaity`w(a1m$s(dta z$;S?*e^#a-yMM4#sVLbFjPlaBFG%#ylo-t2rkcA^VC{VBqzBi+Yv*VWrTXr_EktZ}(29pD1rNe~A;yv3YYF-?Iq*8w*`pcl>=po_50B`Ap zW4N)M=k;l2aX4Hp9&cjb9N)08$(&}Tw?_(2aLJA+!LB-f%He&MH*Y;0&n1|V_|K1C z7Dt~akc$&MNF)t(LW>P)vJ*1T(jnaCWVXC8!KECeH%xw#3OGZ+tFT@xG!j+oRJ_Tc zE+b^GC!6=Rh7{;5`R1@YW%GuKnOKldNzCQ7&X|Jo;0j0RfW_*3b*TvDb&-N}!-5nG z{HOH~qk%3Mci96vXzXaa0d-PbB;R~$9e0W>E!EAh-d<=z&8lNYD>byYn^0^C*!Zg^ z%KYj&?pWh>Q6Y9(4NHiwnrLZj3G`JF#~cnAdA8KNl2imYn4wqze5#>Q%aT$age|kZ z|pt+LVQAEkBDCnL}LwCb(2gkEm85;l2TAf zRs0+@Aao;CP>VD^sN`Hma;CIWl65vcWVyxC=n~88&Ddhi)Tr3F2 zgNG@LcwI|Kj4WxObIR)>`O->P$>XP_&n8y3+e|5Gf~!>TCm7bn zuScW1o#=K=%(O_ds)f7J#L*(cqd1&U-kpRgr zpW3W!y>+waDizo=m-|RvbSm*br+PMSN+;BMKhbMjdgY5b?;fHoCJ~kjCI@5{iYa|2)gbkg_Qb1C&b_= z-St(Rlu8~!8!AZuP|$xHt@5Wor zeK)|}skAwajp&v##Q$Krk!Dv#`$h#DjXh`U7VRq5t*ZY&TDh^c%;C&p%0Dqis!GJ< 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[]) + const [loading, setLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + const [filters, setFilters] = useState({ + faceId, + page: 1 + }) + const [hasMore, setHasMore] = useState(true) + + const [index, setIndex] = useState(-1); + + const fetchAssets = async () => { + setLoading(true); + return listAlbumAssets(album.id, filters) + .then((assets) => { + if (filters.page === 1) { + setAssets(assets) + } else { + setAssets([...assets, ...assets]) + } + setHasMore(assets.length >= 100) + }) + .catch(setErrorMessage) + .finally(() => setLoading(false)); + }; + + const images = useMemo(() => { + return assets.map((p) => ({ + ...p, + src: p.url as string, + original: p.previewUrl as string, + width: p.exifImageWidth as number, + height: p.exifImageHeight as number, + // isSelected: selectedIds.includes(p.id), + tags: [ + { + title: "Immich Link", + value: ( + + Open in Immich + + ), + }, + ], + })); + }, [assets]); + + const slides = useMemo( + () => + images.map(({ original, width, height }) => ({ + src: original, + width, + height, + })), + [images] + ); + + const handleClick = (idx: number) => setIndex(idx); + + // const handleSelect = (_idx: number, asset: IAsset) => { + // const isPresent = selectedIds.includes(asset.id); + // if (isPresent) { + // updateContext({ + // selectedIds: selectedIds.filter((id) => id !== asset.id), + // }); + // } else { + // updateContext({ selectedIds: [...selectedIds, asset.id] }); + // } + // }; + + useEffect(() => { + fetchAssets(); + }, [album.id, filters]); + + useEffect(() => { + setFilters({ + faceId, + page: 1 + }) + }, [faceId]) + + if (loading && filters.page === 1) + return ( +
+ +

Loading...

+
+ ); + + return ( + <> + = 0} + index={index} + close={() => setIndex(-1)} + /> +
+ {images.map((image) => ( +
+ {image.originalFileName} handleClick(images.indexOf(image))} + /> +
+ ))} +
+ {hasMore && } + + ); +} diff --git a/src/components/albums/info/AlbumPeople.tsx b/src/components/albums/info/AlbumPeople.tsx new file mode 100644 index 0000000..0e78a01 --- /dev/null +++ b/src/components/albums/info/AlbumPeople.tsx @@ -0,0 +1,160 @@ +import React, { useEffect, useMemo, useState } from 'react' +import { IAlbum } from '@/types/album' +import { IPerson } from '@/types/person' +import { getAlbumPeople } from '@/handlers/api/album.handler' +import Loader from '@/components/ui/loader' +import LazyImage from '@/components/ui/lazy-image' +import { PERSON_THUBNAIL_PATH } from '@/config/routes' +import Link from 'next/link' +import { useConfig } from '@/contexts/ConfigContext' +import { Tooltip } from '@/components/ui/tooltip' +import { cn } from '@/lib/utils' +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion' +import { Users, X } from 'lucide-react' +import { useRouter } from 'next/router' +import Image from 'next/image' +import { PersonMergeDropdown } from '@/components/people/PersonMergeDropdown' +import { Button } from '@/components/ui/button' +import { updatePerson } from '@/handlers/api/people.handler' + +interface AlbumPeopleProps { + album: IAlbum + onSelect: (personId: string) => void +} + +interface IAlbumPerson { + id: string + name: string + numberOfPhotos: number +} +export default function AlbumPeople({ album, onSelect }: AlbumPeopleProps) { + const { exImmichUrl } = useConfig() + const router = useRouter() + const { query, pathname } = router + const [people, setPeople] = useState([]) + const [loading, setLoading] = useState(false) + const [errorMessage, setErrorMessage] = useState(null) + const [hidingPerson, setHidingPerson] = useState(false) + + const selectedPerson = useMemo(() => { + return people.find((person) => query.faceId === person.id) + }, [people, query.faceId]) + + const fetchPeople = async () => { + return getAlbumPeople(album.id).then((people) => { + setPeople(people) + }).catch((error) => { + setErrorMessage(error.message) + }).finally(() => { + setLoading(false) + }) + } + + const handleHidePerson = (personId: string) => { + setHidingPerson(true) + updatePerson(personId, { isHidden: true }).then(() => { + setPeople(people.filter((person) => person.id !== personId)) + router.push({ + pathname, + query: { + ...query, + faceId: null + } + }) + }).finally(() => { + setHidingPerson(false) + }) + } + + useEffect(() => { + fetchPeople() + }, []) + + if (loading) { + return + } + if (errorMessage) { + return
{errorMessage}
+ } + + return ( + + +
+ +
+ +

People

+
+
+
+ {selectedPerson && ( + <> + +
+
+ {selectedPerson.name} +
+ {selectedPerson.name} +
+
+ { + console.log(mergedPerson) + router.push({ + pathname, + query: { + ...query, + faceId: mergedPerson.id + } + }) + }} /> +
+ +
+ + )} + +
+
+ + {people.map((person) => ( + +
+ onSelect(person.id)} + role="button" + className={ + cn("cursor-pointer h-16 w-16 min-w-16 rounded border-2", + !!person.name ? "border-blue-500" : "border-gray-500", + selectedPerson?.id === person.id ? "border-green-500" : "" + ) + } + src={PERSON_THUBNAIL_PATH(person.id)} alt={person.name} /> +

{person.numberOfPhotos}

+
+
+ ))} +
+
+
+ ) +} diff --git a/src/components/albums/list/AlbumThumbnail.tsx b/src/components/albums/list/AlbumThumbnail.tsx new file mode 100644 index 0000000..a8a5007 --- /dev/null +++ b/src/components/albums/list/AlbumThumbnail.tsx @@ -0,0 +1,64 @@ +import React, { useMemo } from 'react' + +import Link from 'next/link'; +import { useConfig } from '@/contexts/ConfigContext'; +import { humanizeNumber } from '@/helpers/string.helper'; +import LazyImage from '@/components/ui/lazy-image'; +import { ASSET_THUMBNAIL_PATH } from '@/config/routes'; +import { IAlbum } from '@/types/album'; +import { Badge } from '@/components/ui/badge'; +import { formatDate } from '@/helpers/date.helper'; +import { Checkbox } from '@/components/ui/checkbox'; +import { differenceInDays } from 'date-fns'; +import { FaceIcon } from '@radix-ui/react-icons'; +import { Camera, Image, User } from 'lucide-react'; + +interface IAlbumThumbnailProps { + album: IAlbum; + onSelect: (checked: boolean) => void; +} +export default function AlbumThumbnail({ album, onSelect }: IAlbumThumbnailProps) { + const numberOfDays = useMemo(() => { + return differenceInDays(album.lastPhotoDate, album.firstPhotoDate); + }, [album.firstPhotoDate, album.lastPhotoDate]); + + return ( +
+ +
+
+ {humanizeNumber(album.assetCount)} +
+

+ {album.faceCount} +

+
+
+ +

{album.albumName}

+ +

{ + numberOfDays === 0 ? '1 day' : `${numberOfDays.toLocaleString() } days` + }

+
+
+ ) +} diff --git a/src/components/albums/potential-albums/PotentialAlbumsAssets.tsx b/src/components/albums/potential-albums/PotentialAlbumsAssets.tsx index 2818863..ebe6870 100644 --- a/src/components/albums/potential-albums/PotentialAlbumsAssets.tsx +++ b/src/components/albums/potential-albums/PotentialAlbumsAssets.tsx @@ -9,7 +9,7 @@ import Captions from "yet-another-react-lightbox/plugins/captions"; import { CalendarArrowDown, CalendarArrowUp, Hourglass } from "lucide-react"; import Video from "yet-another-react-lightbox/plugins/video"; import { useConfig } from "@/contexts/ConfigContext"; -import LazyImage from "@/components/ui/lazy-image"; +import LazyGridImage from "@/components/ui/lazy-grid-image"; export default function PotentialAlbumsAssets() { const { exImmichUrl } = useConfig(); @@ -124,7 +124,7 @@ export default function PotentialAlbumsAssets() { onClick={handleClick} enableImageSelection={true} onSelect={handleSelect} - thumbnailImageComponent={LazyImage} + thumbnailImageComponent={LazyGridImage} tagStyle={{ color: "white", fontSize: "12px", diff --git a/src/components/assets/assets-options/AssetOffsetDialog.tsx b/src/components/assets/assets-options/AssetOffsetDialog.tsx index 081f8e1..8c8036a 100644 --- a/src/components/assets/assets-options/AssetOffsetDialog.tsx +++ b/src/components/assets/assets-options/AssetOffsetDialog.tsx @@ -5,7 +5,6 @@ import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { formatDate, offsetDate } from '@/helpers/date.helper'; -import LazyImage from '@/components/ui/lazy-image'; import Image from 'next/image'; import { updateAssets } from '@/handlers/api/asset.handler'; import { useToast } from '@/components/ui/use-toast'; diff --git a/src/components/assets/missing-location/MissingLocationAssets.tsx b/src/components/assets/missing-location/MissingLocationAssets.tsx index 6b58ace..f2a4ee6 100644 --- a/src/components/assets/missing-location/MissingLocationAssets.tsx +++ b/src/components/assets/missing-location/MissingLocationAssets.tsx @@ -18,7 +18,7 @@ import { listMissingLocationAssets } from "@/handlers/api/asset.handler"; import { formatDate, parseDate } from "@/helpers/date.helper"; import { addDays } from "date-fns"; import { useConfig } from "@/contexts/ConfigContext"; -import LazyImage from "@/components/ui/lazy-image"; +import LazyGridImage from "@/components/ui/lazy-grid-image"; export default function MissingLocationAssets() { const { exImmichUrl } = useConfig(); @@ -61,7 +61,7 @@ export default function MissingLocationAssets() { ), }, ], - })); + })).filter((p) => !p.original); }, [assets, selectedIds]); const slides = useMemo( @@ -126,7 +126,7 @@ export default function MissingLocationAssets() { onClick={handleClick} enableImageSelection={true} onSelect={handleSelect} - thumbnailImageComponent={LazyImage} + thumbnailImageComponent={LazyGridImage} tagStyle={{ color: "white", fontSize: "12px", diff --git a/src/components/layouts/PageLayout.tsx b/src/components/layouts/PageLayout.tsx index 28aa171..cc64db2 100644 --- a/src/components/layouts/PageLayout.tsx +++ b/src/components/layouts/PageLayout.tsx @@ -20,11 +20,11 @@ export default function PageLayout({ children, className }: IProps) { : children; return ( - <> +
{header}
{childrenWithoutHeader}
- +
); } diff --git a/src/components/people/PersonMergeDropdown.tsx b/src/components/people/PersonMergeDropdown.tsx index 28ab97c..0de0fd5 100644 --- a/src/components/people/PersonMergeDropdown.tsx +++ b/src/components/people/PersonMergeDropdown.tsx @@ -36,11 +36,13 @@ import ErrorBlock from "../shared/ErrorBlock"; interface PersonMergeDropdownProps { person: IPerson; onRemove?: (person: IPerson) => void; + onComplete?: (mergedPerson: IPerson) => void; } export function PersonMergeDropdown({ person, onRemove, + onComplete, }: PersonMergeDropdownProps) { const [searchedPeople, setSearchedPeople] = useState(null); const [similarPeople, setSimilarPeople] = useState([]); @@ -127,6 +129,7 @@ export function PersonMergeDropdown({ description: "People merged successfully", }); setMerging(false); + onComplete?.(primaryPerson); return; } @@ -159,6 +162,11 @@ export function PersonMergeDropdown({ if (open && !similarPeople.length) fetchSuggestions(); }, [open, person.id, similarPeople]); + useEffect(() => { + setPrimaryPerson(person); + setSelectedPeople([]); + }, [person]); + const renderPeopleList = (people: IPerson[], title: string) => { return (
diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx new file mode 100644 index 0000000..72a9009 --- /dev/null +++ b/src/components/ui/accordion.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { cn } from "@/lib/utils" +import { ChevronDownIcon } from "@radix-ui/react-icons" + +const Accordion = AccordionPrimitive.Root + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = "AccordionItem" + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + noIcon?: boolean + } +>(({ className, children, noIcon, ...props }, ref) => ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + {!noIcon && } + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..8d02b28 --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,27 @@ +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { cn } from "@/lib/utils" +import { CheckIcon } from "@radix-ui/react-icons" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/src/components/ui/lazy-grid-image.tsx b/src/components/ui/lazy-grid-image.tsx new file mode 100644 index 0000000..6fd03ff --- /dev/null +++ b/src/components/ui/lazy-grid-image.tsx @@ -0,0 +1,44 @@ +/* eslint-disable @next/next/no-img-element */ +import React, { useEffect } from 'react' +import { Image, ImageExtended, ThumbnailImageProps } from 'react-grid-gallery' + +interface LazyImageProps extends ThumbnailImageProps> {} + +export default function LazyGridImage( + props: LazyImageProps +) { + const [isVisible, setIsVisible] = React.useState(false) + const imageRef = React.useRef(null) + + const setupObserver = () => { + const observer = new IntersectionObserver((entries) => { + + entries.forEach((entry) => { + if (entry.isIntersecting) { + setIsVisible(true) + observer.disconnect() + } + }) + }) + + if (imageRef.current) { + observer.observe(imageRef.current) + } + return observer + } + + useEffect(() => { + const observer = setupObserver() + return () => { + observer?.disconnect() + } + }, []) + + if (!isVisible) return ( +
+ ) + + return ( + {props.imageProps.alt + ) +} diff --git a/src/components/ui/lazy-image.tsx b/src/components/ui/lazy-image.tsx index 9027779..c942d43 100644 --- a/src/components/ui/lazy-image.tsx +++ b/src/components/ui/lazy-image.tsx @@ -1,8 +1,8 @@ /* eslint-disable @next/next/no-img-element */ +import { ImageProps } from 'next/image' import React, { useEffect } from 'react' -import { Image, ImageExtended, ThumbnailImageProps } from 'react-grid-gallery' -interface LazyImageProps extends ThumbnailImageProps> {} +interface LazyImageProps extends ImageProps {} export default function LazyImage( props: LazyImageProps @@ -38,7 +38,5 @@ export default function LazyImage(
) - return ( - {props.imageProps.alt - ) + return {props.alt } diff --git a/src/config/routes.ts b/src/config/routes.ts index 4bfe2f8..dbd2949 100644 --- a/src/config/routes.ts +++ b/src/config/routes.ts @@ -19,7 +19,10 @@ export const EXIF_DISTRIBUTION_PATH = (column: string) => BASE_API_ENDPOINT + "/ // Albums export const LIST_POTENTIAL_ALBUMS_DATES_PATH = BASE_API_ENDPOINT + "/albums/potential-albums-dates"; export const LIST_POTENTIAL_ALBUMS_ASSETS_PATH = BASE_API_ENDPOINT + "/albums/potential-albums-assets"; -export const LIST_ALBUMS_PATH = BASE_PROXY_ENDPOINT + "/albums"; +export const LIST_ALBUMS_PATH = BASE_API_ENDPOINT + "/albums/list"; +export const ALBUM_INFO_PATH = (id: string) => BASE_API_ENDPOINT + "/albums/" + id + "/info"; +export const ALBUM_PEOPLE_PATH = (id: string) => BASE_API_ENDPOINT + "/albums/" + id + "/people"; +export const ALBUM_ASSETS_PATH = (id: string) => BASE_API_ENDPOINT + "/albums/" + id + "/assets"; export const CREATE_ALBUM_PATH = BASE_PROXY_ENDPOINT + "/albums"; export const ADD_ASSETS_ALBUMS_PATH = (id: string) => BASE_PROXY_ENDPOINT + "/albums/" + id + "/assets"; diff --git a/src/handlers/api/album.handler.ts b/src/handlers/api/album.handler.ts index 9097c0b..3342c35 100644 --- a/src/handlers/api/album.handler.ts +++ b/src/handlers/api/album.handler.ts @@ -1,4 +1,4 @@ -import { ADD_ASSETS_ALBUMS_PATH, CREATE_ALBUM_PATH, LIST_ALBUMS_PATH, LIST_POTENTIAL_ALBUMS_ASSETS_PATH, LIST_POTENTIAL_ALBUMS_DATES_PATH } from "@/config/routes"; +import { ADD_ASSETS_ALBUMS_PATH, ALBUM_ASSETS_PATH, ALBUM_INFO_PATH, ALBUM_PEOPLE_PATH, CREATE_ALBUM_PATH, LIST_ALBUMS_PATH, LIST_POTENTIAL_ALBUMS_ASSETS_PATH, LIST_POTENTIAL_ALBUMS_DATES_PATH } from "@/config/routes"; import { cleanUpAsset } from "@/helpers/asset.helper"; import API from "@/lib/api"; import { IAlbumCreate } from "@/types/album"; @@ -24,7 +24,18 @@ export const listPotentialAlbumsAssets = async (filters: IPotentialAlbumsDatesFi } export const listAlbums = async () => { - return API.get(LIST_ALBUMS_PATH); +return API.get(LIST_ALBUMS_PATH); +} +export const getAlbumInfo = async (id: string) => { + return API.get(ALBUM_INFO_PATH(id)); +} + +export const getAlbumPeople = async (id: string) => { + return API.get(ALBUM_PEOPLE_PATH(id)); +} + +export const listAlbumAssets = async (id: string, filters: { faceId?: string }) => { + return API.get(ALBUM_ASSETS_PATH(id), filters).then((assets) => assets.map(cleanUpAsset)); } export const addAssetToAlbum = async (albumId: string, assetIds: string[]) => { diff --git a/src/helpers/string.helper.ts b/src/helpers/string.helper.ts new file mode 100644 index 0000000..7f5612e --- /dev/null +++ b/src/helpers/string.helper.ts @@ -0,0 +1,3 @@ +export const humanizeNumber = (number: number) => { + return number.toLocaleString(); +} \ No newline at end of file diff --git a/src/pages/albums/[albumId].tsx b/src/pages/albums/[albumId].tsx new file mode 100644 index 0000000..4dfede8 --- /dev/null +++ b/src/pages/albums/[albumId].tsx @@ -0,0 +1,104 @@ +import { ASSET_THUMBNAIL_PATH } from '@/config/routes' +import PageLayout from '@/components/layouts/PageLayout' +import Header from '@/components/shared/Header' +import Loader from '@/components/ui/loader' +import { useConfig } from '@/contexts/ConfigContext' +import { getAlbumInfo, listAlbums } from '@/handlers/api/album.handler' +import { IAlbum } from '@/types/album' +import Image from 'next/image' +import Link from 'next/link' +import React, { useEffect, useState } from 'react' +import AlbumThumbnail from '@/components/albums/list/AlbumThumbnail' +import { Button } from '@/components/ui/button' +import { useRouter } from 'next/router' +import AlbumPeople from '@/components/albums/info/AlbumPeople' +import AlbumImages from '@/components/albums/info/AlbumImages' + +export default function AlbumListPage() { + const router = useRouter() + const { pathname } = router + const { albumId, faceId } = router.query as { albumId: string, faceId: string } + const [albumInfo, setAlbumInfo] = useState() + const [loading, setLoading] = useState(false) + const [errorMessage, setErrorMessage] = useState('') + const [selectedAlbums, setSelectedAlbums] = useState([]) + + const fetchAlbumInfo = async () => { + setLoading(true) + getAlbumInfo(albumId) + .then(setAlbumInfo) + .catch((error) => { + setErrorMessage(error.message) + }) + .finally(() => { + setLoading(false) + }) + } + + useEffect(() => { + fetchAlbumInfo() + }, []) + + const handleSelect = (checked: boolean, albumId: string) => { + if (checked) { + setSelectedAlbums([...selectedAlbums, albumId]) + } else { + setSelectedAlbums(selectedAlbums.filter((id) => id !== albumId)) + } + } + + const handleSelectPerson = (personId: string) => { + if (faceId === personId) { + router.push({ + pathname, + query: { + albumId + } + }) + } else { + router.push({ + pathname, + query: { + albumId, + faceId: personId + } + }) + } + } + + const renderContent = () => { + + if (loading) { + return + } + else if (errorMessage) { + return
{errorMessage}
+ } + return ( +
+ {albumInfo && ( + <> + + + + )} +
+ ) + } + return ( + +
+

{albumInfo?.assetCount} photos

+

{albumInfo?.faceCount} faces

+
+ ) + } + /> + {renderContent()} + + ) +} diff --git a/src/pages/albums/index.tsx b/src/pages/albums/index.tsx index ffc0c4b..83041ec 100644 --- a/src/pages/albums/index.tsx +++ b/src/pages/albums/index.tsx @@ -8,12 +8,15 @@ import { IAlbum } from '@/types/album' import Image from 'next/image' import Link from 'next/link' import React, { useEffect, useState } from 'react' +import AlbumThumbnail from '@/components/albums/list/AlbumThumbnail' +import { Button } from '@/components/ui/button' export default function AlbumListPage() { -const { exImmichUrl } = useConfig() + const { exImmichUrl } = useConfig() const [albums, setAlbums] = useState([]) const [loading, setLoading] = useState(false) const [errorMessage, setErrorMessage] = useState('') + const [selectedAlbums, setSelectedAlbums] = useState([]) const fetchAlbums = async () => { setLoading(true) @@ -31,8 +34,16 @@ const { exImmichUrl } = useConfig() fetchAlbums() }, []) + const handleSelect = (checked: boolean, albumId: string) => { + if (checked) { + setSelectedAlbums([...selectedAlbums, albumId]) + } else { + setSelectedAlbums(selectedAlbums.filter((id) => id !== albumId)) + } + } + const renderContent = () => { - + if (loading) { return } @@ -42,22 +53,7 @@ const { exImmichUrl } = useConfig() return (
{albums.map((album) => ( -
- {album.albumName} -
- -

{album.albumName}

- -

{album.assetCount} assets

-
-
+ handleSelect(checked, album.id)} /> ))}
) diff --git a/src/pages/api/albums/[id]/assets.ts b/src/pages/api/albums/[id]/assets.ts new file mode 100644 index 0000000..56e9456 --- /dev/null +++ b/src/pages/api/albums/[id]/assets.ts @@ -0,0 +1,62 @@ +import { NextApiRequest } from "next"; + +import { db } from "@/config/db"; +import { getCurrentUser } from "@/handlers/serverUtils/user.utils"; +import { NextApiResponse } from "next"; +import { albums } from "@/schema/albums.schema"; +import { count, desc, eq, min, max, sql, and, asc, isNotNull } from "drizzle-orm"; +import { assets } from "@/schema/assets.schema"; +import { albumsAssetsAssets } from "@/schema/albumAssetsAssets.schema"; +import { users } from "@/schema/users.schema"; +import { assetFaces, exif, person } from "@/schema"; +import { IAlbum } from "@/types/album"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const currentUser = await getCurrentUser(req); + if (!currentUser) { + return res.status(401).json({ error: "Unauthorized" }); + } + const { id, faceId, page = 1 } = req.query as { + id?: string, + faceId?: string, + page?: number + }; + + if (!id) { + return res.status(400).json({ error: "Album id is required" }); + } + const dbAssets = await db.select({ + id: assets.id, + deviceId: assets.deviceId, + type: assets.type, + originalPath: assets.originalPath, + isFavorite: assets.isFavorite, + duration: assets.duration, + encodedVideoPath: assets.encodedVideoPath, + originalFileName: assets.originalFileName, + sidecarPath: assets.sidecarPath, + deletedAt: assets.deletedAt, + localDateTime: assets.localDateTime, + exifImageWidth: exif.exifImageWidth, + exifImageHeight: exif.exifImageHeight, + ownerId: assets.ownerId, + dateTimeOriginal: exif.dateTimeOriginal, + }) + .from(albumsAssetsAssets) + .leftJoin(assets, eq(albumsAssetsAssets.assetsId, assets.id)) + .leftJoin(exif, eq(assets.id, exif.assetId)) + .leftJoin(assetFaces, eq(assets.id, assetFaces.assetId)) + .leftJoin(person, eq(assetFaces.personId, person.id)) + .where(and( + eq(albumsAssetsAssets.albumsId, id), + eq(assets.isVisible, true), + faceId ? eq(assetFaces.personId, faceId) : undefined, + )) + .limit(100) + .offset(100 * (page - 1)); + + res.status(200).json(dbAssets); +} \ No newline at end of file diff --git a/src/pages/api/albums/[id]/info.ts b/src/pages/api/albums/[id]/info.ts new file mode 100644 index 0000000..10f9625 --- /dev/null +++ b/src/pages/api/albums/[id]/info.ts @@ -0,0 +1,49 @@ +import { NextApiRequest } from "next"; + +import { db } from "@/config/db"; +import { getCurrentUser } from "@/handlers/serverUtils/user.utils"; +import { NextApiResponse } from "next"; +import { albums } from "@/schema/albums.schema"; +import { count, desc, eq, min, max, sql, and } from "drizzle-orm"; +import { assets } from "@/schema/assets.schema"; +import { albumsAssetsAssets } from "@/schema/albumAssetsAssets.schema"; +import { users } from "@/schema/users.schema"; +import { assetFaces, exif, person } from "@/schema"; +import { IAlbum } from "@/types/album"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const currentUser = await getCurrentUser(req); + if (!currentUser) { + return res.status(401).json({ error: "Unauthorized" }); + } + const { id } = req.query as { id: string }; + + const dbAlbums = await db.select({ + id: albums.id, + albumName: albums.albumName, + createdAt: albums.createdAt, + updatedAt: albums.updatedAt, + albumThumbnailAssetId: albums.albumThumbnailAssetId, + assetCount: count(assets.id), + firstPhotoDate: min(exif.dateTimeOriginal), + lastPhotoDate: max(exif.dateTimeOriginal), + faceCount: count(sql`DISTINCT ${person.id}`), // Ensure unique personId + }) + .from(albums) + .leftJoin(albumsAssetsAssets, eq(albums.id, albumsAssetsAssets.albumsId)) + .leftJoin(assets, eq(albumsAssetsAssets.assetsId, assets.id)) + .leftJoin(exif, eq(assets.id, exif.assetId)) + .leftJoin(assetFaces, eq(assets.id, assetFaces.assetId)) + .leftJoin(person, and(eq(assetFaces.personId, person.id), eq(person.isHidden, false))) + .where(and(eq(albums.ownerId, currentUser.id), eq(albums.id, id))) + .groupBy(albums.id) + .limit(1); + + if (dbAlbums.length === 0) { + return res.status(404).json({ error: "Album not found" }); + } + res.status(200).json(dbAlbums[0]); +} \ No newline at end of file diff --git a/src/pages/api/albums/[id]/people.ts b/src/pages/api/albums/[id]/people.ts new file mode 100644 index 0000000..63b7392 --- /dev/null +++ b/src/pages/api/albums/[id]/people.ts @@ -0,0 +1,40 @@ +import { NextApiRequest } from "next"; + +import { db } from "@/config/db"; +import { getCurrentUser } from "@/handlers/serverUtils/user.utils"; +import { NextApiResponse } from "next"; +import { albums } from "@/schema/albums.schema"; +import { count, desc, eq, min, max, sql, and, asc, isNotNull } from "drizzle-orm"; +import { assets } from "@/schema/assets.schema"; +import { albumsAssetsAssets } from "@/schema/albumAssetsAssets.schema"; +import { users } from "@/schema/users.schema"; +import { assetFaces, exif, person } from "@/schema"; +import { IAlbum } from "@/types/album"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const currentUser = await getCurrentUser(req); + if (!currentUser) { + return res.status(401).json({ error: "Unauthorized" }); + } + const { id } = req.query as { id: string }; + + const dbAlbumPeople = await db.select({ + id: person.id, + name: person.name, + thumbnailAssetId: person.faceAssetId, + numberOfPhotos: count(assets.id), + }) + .from(albums) + .leftJoin(albumsAssetsAssets, eq(albums.id, albumsAssetsAssets.albumsId)) + .leftJoin(assets, eq(albumsAssetsAssets.assetsId, assets.id)) + .leftJoin(assetFaces, eq(assets.id, assetFaces.assetId)) + .leftJoin(person, and(eq(assetFaces.personId, person.id), eq(person.isHidden, false))) + .where(and(eq(albums.ownerId, currentUser.id), eq(albums.id, id), isNotNull(person.id))) + .orderBy(desc(person.name)) + .groupBy(person.id); + + res.status(200).json(dbAlbumPeople); +} \ No newline at end of file diff --git a/src/pages/api/albums/list.ts b/src/pages/api/albums/list.ts new file mode 100644 index 0000000..45fee66 --- /dev/null +++ b/src/pages/api/albums/list.ts @@ -0,0 +1,66 @@ +import { NextApiRequest } from "next"; + +import { db } from "@/config/db"; +import { getCurrentUser } from "@/handlers/serverUtils/user.utils"; +import { NextApiResponse } from "next"; +import { albums } from "@/schema/albums.schema"; +import { count, desc, eq, min, max, sql, and } from "drizzle-orm"; +import { assets } from "@/schema/assets.schema"; +import { albumsAssetsAssets } from "@/schema/albumAssetsAssets.schema"; +import { users } from "@/schema/users.schema"; +import { assetFaces, exif, person } from "@/schema"; +import { IAlbum } from "@/types/album"; + + +const sortAlbums = (albums: IAlbum[], sortBy: string, sortOrder: string) => { + if (sortBy === 'createdAt') { + return albums.sort((a, b) => sortOrder === 'asc' ? a.createdAt.getTime() - b.createdAt.getTime() : b.createdAt.getTime() - a.createdAt.getTime()); + } + if (sortBy === 'updatedAt') { + return albums.sort((a, b) => sortOrder === 'asc' ? a.updatedAt.getTime() - b.updatedAt.getTime() : b.updatedAt.getTime() - a.updatedAt.getTime()); + } + if (sortBy === 'firstPhotoDate') { + return albums.sort((a, b) => sortOrder === 'asc' ? a.firstPhotoDate.getTime() - b.firstPhotoDate.getTime() : b.firstPhotoDate.getTime() - a.firstPhotoDate.getTime()); + } + if (sortBy === 'lastPhotoDate') { + return albums.sort((a, b) => sortOrder === 'asc' ? a.lastPhotoDate.getTime() - b.lastPhotoDate.getTime() : b.lastPhotoDate.getTime() - a.lastPhotoDate.getTime()); + } + if (sortBy === 'assetCount') { + return albums.sort((a, b) => sortOrder === 'asc' ? a.assetCount - b.assetCount : b.assetCount - a.assetCount); + } + return albums; +} +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const currentUser = await getCurrentUser(req); + if (!currentUser) { + return res.status(401).json({ error: "Unauthorized" }); + } + const { sortBy = 'lastPhotoDate', sortOrder = 'desc' } = req.query as { sortBy: string, sortOrder: string }; + + const dbAlbums = await db.select({ + id: albums.id, + albumName: albums.albumName, + createdAt: albums.createdAt, + updatedAt: albums.updatedAt, + albumThumbnailAssetId: albums.albumThumbnailAssetId, + assetCount: count(assets.id), + firstPhotoDate: min(exif.dateTimeOriginal), + lastPhotoDate: max(exif.dateTimeOriginal), + faceCount: count(sql`DISTINCT ${assetFaces.personId}`), // Ensure unique personId + }) + .from(albums) + .leftJoin(albumsAssetsAssets, eq(albums.id, albumsAssetsAssets.albumsId)) + .leftJoin(assets, eq(albumsAssetsAssets.assetsId, assets.id)) + .leftJoin(exif, eq(assets.id, exif.assetId)) + .leftJoin(assetFaces, eq(assets.id, assetFaces.assetId)) + .leftJoin(person, and(eq(assetFaces.personId, person.id), eq(person.isHidden, false))) + .where(eq(albums.ownerId, currentUser.id)) + .groupBy(albums.id) + .orderBy(desc(albums.createdAt)); + + const sortedAlbums = sortAlbums(dbAlbums as IAlbum[], sortBy, sortOrder); + res.status(200).json(dbAlbums); +} \ No newline at end of file diff --git a/src/pages/api/albums/potential-albums-dates.ts b/src/pages/api/albums/potential-albums-dates.ts index 1998ac7..7b87b9b 100644 --- a/src/pages/api/albums/potential-albums-dates.ts +++ b/src/pages/api/albums/potential-albums-dates.ts @@ -1,11 +1,8 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import { CHART_COLORS } from "@/config/constants/chart.constant"; import { db } from "@/config/db"; import { IPotentialAlbumsDatesResponse } from "@/handlers/api/album.handler"; import { getCurrentUser } from "@/handlers/serverUtils/user.utils"; import { parseDate } from "@/helpers/date.helper"; -import { exif } from "@/schema"; -import { count, desc, isNotNull, ne, sql } from "drizzle-orm"; import type { NextApiRequest, NextApiResponse } from "next"; const SELECT_ORPHAN_PHOTOS = (ownerId: string) => sql` diff --git a/src/schema/albumAssetsAssets.schema.ts b/src/schema/albumAssetsAssets.schema.ts new file mode 100644 index 0000000..8d118c0 --- /dev/null +++ b/src/schema/albumAssetsAssets.schema.ts @@ -0,0 +1,7 @@ +import { pgTable, uuid, timestamp } from 'drizzle-orm/pg-core'; + +export const albumsAssetsAssets = pgTable('albums_assets_assets', { + albumsId: uuid('albumsId').notNull(), + assetsId: uuid('assetsId').notNull(), + createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull(), +}); \ No newline at end of file diff --git a/src/schema/relationships.ts b/src/schema/relationships.ts index 4041dcb..4b5d15b 100644 --- a/src/schema/relationships.ts +++ b/src/schema/relationships.ts @@ -4,7 +4,8 @@ import { assets } from "./assets.schema"; import { person } from "./person.schema"; import { users } from "./users.schema"; import { faceSearch } from "./faceSearch.schema"; - +import { albumsAssetsAssets } from "./albumAssetsAssets.schema"; +import { albums } from "./albums.schema"; // Assuming there are relations to be defined with `assets` and `person` export const assetFacesRelations = relations(assetFaces, ({ one }) => ({ asset: one(assets, { @@ -38,3 +39,17 @@ export const faceSearchRelations = relations(faceSearch, ({ one }) => ({ references: [assetFaces.id], }), })); + + +// Assuming there are relations to be defined with `albums` and `assets` +export const albumsAssetsAssetsRelations = relations(albumsAssetsAssets, ({ one }) => ({ + album: one(albums, { + fields: [albumsAssetsAssets.albumsId], + references: [albums.id], + }), + asset: one(assets, { + fields: [albumsAssetsAssets.assetsId], + references: [assets.id], + }), +})); + diff --git a/src/types/album.d.ts b/src/types/album.d.ts index 0e3eece..066ebc8 100644 --- a/src/types/album.d.ts +++ b/src/types/album.d.ts @@ -17,6 +17,9 @@ export interface IAlbum { isActivityEnabled: boolean; order: string; lastModifiedAssetTimestamp: Date; + firstPhotoDate: Date; + lastPhotoDate: Date; + faceCount: number; } export interface IAlbumOwner { diff --git a/tailwind.config.ts b/tailwind.config.ts index d4d8bee..5fd6ca1 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -11,72 +11,98 @@ const config = { ], prefix: "", theme: { - fontFamily: { - mono: [ ...fontFamily.mono ], - }, - container: { - center: true, - padding: "2rem", - screens: { - "2xl": "1400px", - }, - }, - extend: { - colors: { - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", - }, - keyframes: { - "accordion-down": { - from: { height: "0" }, - to: { height: "var(--radix-accordion-content-height)" }, - }, - "accordion-up": { - from: { height: "var(--radix-accordion-content-height)" }, - to: { height: "0" }, - }, - }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", - }, - }, + fontFamily: { + mono: [ ...fontFamily.mono ] + }, + container: { + center: 'true', + padding: '2rem', + screens: { + '2xl': '1400px' + } + }, + extend: { + colors: { + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + } + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + keyframes: { + 'accordion-down': { + from: { + height: '0' + }, + to: { + height: 'var(--radix-accordion-content-height)' + } + }, + 'accordion-up': { + from: { + height: 'var(--radix-accordion-content-height)' + }, + to: { + height: '0' + } + }, + 'accordion-down': { + from: { + height: '0' + }, + to: { + height: 'var(--radix-accordion-content-height)' + } + }, + 'accordion-up': { + from: { + height: 'var(--radix-accordion-content-height)' + }, + to: { + height: '0' + } + } + }, + animation: { + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out', + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out' + } + } }, plugins: [require("tailwindcss-animate")], } satisfies Config