From 5d821a98652849f2d16455a8872243acdaa6ad81 Mon Sep 17 00:00:00 2001 From: KarimTamani Date: Sun, 1 Jun 2025 23:12:06 +0100 Subject: [PATCH] Create new Database --- public/mysql_logo.png | Bin 0 -> 38845 bytes public/postgresql_logo.png | Bin 0 -> 21390 bytes public/sqlite_logo.png | Bin 0 -> 13988 bytes src/App.tsx | 23 +-- src/components/checkbox/database-checkbox.tsx | 37 +++++ src/components/menu/menu-dropdown.tsx | 4 +- src/components/menu/menu.tsx | 19 ++- src/components/modal/modal.tsx | 33 ++-- src/hooks/use-table-to-node.tsx | 18 +-- src/i18/index.ts | 2 +- src/i18/languages/en.ts | 22 ++- src/lib/database.ts | 32 ++++ src/lib/schemas/database-schema.ts | 15 +- src/pages/database/database-page.tsx | 143 +++++++++--------- .../relationship-controller.tsx | 52 ++----- .../table-accordion-item/field/field-item.tsx | 8 +- .../table-accordion-item/index/index-item.tsx | 6 +- .../database/modals/create-database-modal.tsx | 129 ++++++++++++++++ .../create-relationship-modal.tsx} | 69 ++++++--- .../database/modals/open-database-modal.tsx | 84 ++++++++++ .../database-history-provider.tsx | 3 + .../database-provider/database-context.ts | 16 +- .../database-provider/database-provider.tsx | 49 ++++-- .../modal-provider/modal-contxet.tsx | 14 +- .../modal-provider/modal-provider.tsx | 54 ++++--- src/styles/globals.css | 5 + src/utils/utils.ts | 3 +- tailwind.config.js | 5 +- 28 files changed, 616 insertions(+), 229 deletions(-) create mode 100644 public/mysql_logo.png create mode 100644 public/postgresql_logo.png create mode 100644 public/sqlite_logo.png create mode 100644 src/components/checkbox/database-checkbox.tsx create mode 100644 src/lib/database.ts create mode 100644 src/pages/database/modals/create-database-modal.tsx rename src/pages/database/{db-controller/relationship-controller/create-relationship-form/create-relationship-form.tsx => modals/create-relationship-modal.tsx} (66%) create mode 100644 src/pages/database/modals/open-database-modal.tsx diff --git a/public/mysql_logo.png b/public/mysql_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..79eb4f36dbb1416c240b608f53413d77ee8f4b4f GIT binary patch literal 38845 zcmcF~^+Qwt_y1^+29X#@3IY<+C7^UlcS$#lZidv90n*YaJxV}eGztum?yilHkna5M z_5S<~-~F(A?s=Ygobx>A-n;vJ?vs|L67dtdCjbC|SVdW02LK@G0RV7;*jQ-JK&S`< z`oOQHuBV{nsXjB((T3dg62EGU zOjmSmQI~(XgFAY4aBP6%t99NZi72PJeheh6I4GF4wcU@u!4#Q(h~*y{fe!$&#H|F~ z_zc+0=#@@S--te$ll`TgBj@AdOf@Il%|DU`N|AeG-c_=rj><_FMS!|u=4A$fpz%BF z*?UY8l`p|$m$mc1cFC;@&qk5HZ9j=<<_g{}jhhf$f+pS~FtV*Bp|e8bKC@urW9#Lp zxb6X96Y(5=?#=?azh%WAF)P8{DML^a^i-N8Aj=m9#S&3CaxoiP%E1~7y1&F>9ckkl z3tNp!koZY0i2;XnQsgXqVcb4fu2eCHLIZjB7-pWs)yP%INy8Q$&X>R|kzGui_X z)0IDB1r1?={IQs+NJ=!tL(QG;!v=7O=Lm^|mp^O1Sc zqY+eR35SSpXcWdl>xZ)yfx&%oIfp3q3e2iADd8Bk=eWzAQiDy{0kgT-3mgrlfi3mY zf43yF_jVN#7(7t8tw;CHNF*syX`n^FLWzFF#SR;;)A*i}w7`&lWrFVp7EdDlx^UB; z!w@l?29sTM<*?qp?5h|a(N2i&8Uz+|jS1Y#=LVhf~iNmQNlbd%%(_t+D@&sA6bIo?Jq@ETceHW6Kutps>M{`fXo zJi`Xy=kI#?o_o>D%a7}~&`Z$i>9{99jZf=$lB@LO)`y42kt;rL*0uQnspi&pvFGuM zk#B1j|KT18kHI(QiDha|r%`4cm56m$zkJ_IZGW*j!7?nmG^vQaOJ4!U^9N9VY>ee> z4Hh$ANIm4%rV9H>!bv>pL8Wzb1F6-I5l;}NVLSHA#PZ;tqK3PTwE0aFgJZuoON2$u z<)*%=t@O&hXE5C1C7uuqNLu5rQ@FGJk(t+ua|3KIr3UTRoYlxHgn#+#USx)|d%0{} z`w-KKJ^4cm5k0u*&5Sq2BVJ5b)vl&|C}-x$B-$aIjOG07E?H_U>>uM?I2E;Ur*P|& zDehWM`-l6~hk^c-AD)Qb`nb2VtbkqbWO^fpO3PhdB*m7+0EV!tW_k7Wr^jMF;z3(8 zsla*^CqQ_fWYB%yv|xZ)_|S^mr+__oBlRz1Ydk3mSs|HQA^YazMu?oAmHafPDD)Qo zd=w~N@OskLgS{=naP!%W{&b;EC=i^o!6)nWrgRiN@P^}PZWZBr^pwpWK6l^)GC zU68CJoHrm=U~0C)V($yhd}!etl6eIb}T zSTAO#2pah8%ZmbY6$imT3xh%{^%MD|IRos6GonJ9_mTj@1~7#_?YEaHD@Ru+`o~}6 zcx~yma+!SToqfzNs9Pg{{S8z0_xBe~m>F%!^wTeR$2XDYOx0ahum~g~Q3_XBwx+L2 z0*OfC!Rm9fm?0L1x3`xcMCr|6A(}Z@(o5*UyIj+Hk#cv92^D9!95!l7U=Pc3UQ%b3 z33rd5{g=)MyMD8}7%)1k;>him5<;AIR0uQma=%ysY(vH4sm|)c&x0d@*&aFDKNw}I zH&K~?H9}{1EnoRmU}^+;YFR}H7M>>3ULJb+dNBm>$_gh;_*&;)Q=FZg1t_4M!_dyR zcp^kIi6)keKPq3!GMGO)(J;;Er5W=c11V`KB;STnU=yzCzbzMPewTgU;?uzFQeQpGY}JaX zX6dWV-UtMv#sO4fPxh8|cE(M7eG%e=7-hdB31St~i_c5BN4@4wUBM@;Fwz}5P}8Tu z3j;edR~&w=h;7hmnsXjBJD344HDhH}>LFaPyL4qh->-bvmtHI4EK2T8_M>z%s6xV^ zJpt>F+IumfA3_K9BfXy$V;4+$z@;@Q=t-|GwcxU z&T6~TL+e&-$1{}O7*$F5nh|T2hQW+-gpJDGaqfpgmFNwzQZ+L5is^Dig0A=5)3#!t zlWG0AEGLMytDjy!3N|+wADse7```YB|EB*cK!^K70r3y4&t`VwsZ1& zaByjchAtOQ=$pc9K7qGlib>kCYr=(JB^Xx2y4+LyrgHv$$X-sCsJn)>V}Lz0#Mgc# z&en`KRzA7b7&%}Z1&N|$;v)xSnlUogy}aVV3{mY7}9PyJ*_|G zsY1P?7z^;{d=pg_zSw8z3N)&pksxub6e_XzZp^5mDF)hphG7n;arB1FjmCK8RBxDc zJ~w4A_@*-pKhJyj)ikTGNFj&J!bowMFez&G(5e?i{_BtB+@s zosUw^kIXyP)59J!1ddz$L}G1~^~O{(QUSMr#|wK4c~v{vxtCu@RHq@PDl0~H+aD+M zJw|2zsb&1DAp7hSJ`^v(Td+Q&qZ%Jv-RsU(e+Wl?a#)RRcS z-Q)gKclJ@B1*lcolKYamg8}5Jk3;v5dzRRNIQ?U-xtac?0tpL=tl(*ku-T^7{vZig zRr3FFK1}>839>ia`Mg+gvp1Ns9_I+!#}pbKnO~ z%$9>S8pEOF;3YnTCyq=xr~bzGKZUpy@GPK<;X|IhNWlIA|?a`R#MN{<>0Y zj}H8PP4ZW>)>WeG8;JaIW;C<-Bs>j z>R+0RnvQ{B2EMEy>@psD+I#ph#?e+?HV^Nz!A`Rcy;iVV;6ebsI;Y6kIHwDe2G3{v z=W*9xozH!H3DS-wDEzU^ZY#!eVfY#Rl&(5aa$BIbuj)ktpn)%@sU|yH;y6afVaSA| z%Z-FW5KpuOhS@->VK|KXJ}ju&7zFo(Vo7*Sw<$9d^-0eUS1$_DVQNW#p5CLOAWEC} zY3!xy`_z+k&X`VE#!=Q&6Syd~Jiy6I3!1y*LZdAW43*=gzb)$aP?e{jq3gpqhZ+oL zv*2|8^Ndp)L77M*FwWhhXNk8(iB!!|ElbGcToe=pFmVN)BZ-^vV1ARDJEx2stRx%; zL}jR-3ad@s`b{Vx7d;ALK;+ogJH2=7S5c_bH>nwJREEHl;CDHm^R004>@zh>q7%Pb zc}-HrTv<{xKZ^2kFdcn)OgC%(jppdR=a~J-P~{@6S(ApYomm1%*0*~N?h`n1RC!*x zci58umA>5%iskGIireBrxfkcO9o%)>Z=>Z*i_1sP8)3+wD8yD%(Cn7CQ?3WP}NQ9clSypE?vy^Iis<7!Dj<~Smb&&Y3AV7>w|`9EUw({kL@Ud zCX}{y*)Aa(?PnFZ|3-RZQb7FTo%<%3D_FULYkFA?TfZHFq5rh%9V}Q9Va|oXgMDo( z*SV+bg9m8o-|%UL6ApN+h3pDzQc-LDYX6~n{<4)+eJDQcfCg`g$91q#eBb~p_P7H? z8^-x1e?s9%CCrjW2H`eR$>7;!w_?mXFbi)^`Lraq(pvny z>C!(%VNpeyzuvnLdE_c-dBh|pHk@zD&UsaYCY#`E!r{4gc6uQ+vk5VJSgXl8hC_zp zVW_`SVbJ=ZE_3_q(rl_vHcLsILzKn!ohA7#}Cd<6uci=*XhD= z{NZ1DL#D$(lt7J1B74ts(89>`Ip#mwtyuc`wL2~8_Zkp84AXS@#N+N(TgpSe3uA8g z2cP#A4n6qhfagZ5SmI^I^a-=ImGE zxTfE--20zi^;2Lvtrz?`psvhhF?7e{=^*F~YT@e}p%wDG-lA3WY9 zgo%epuVe6f!Ei_eh`qZr@U%MdJ4P(9;YxO4^5JPH0*gck1^(%jm>{n!s$Y=Ty>@Z= zYR@BAo}1pnSf0slD*GnpR{(WbIEI6BnK56_YTDavtsy={_Cb$-~^$n4^N3tk*35J6MKNVE8}vR`P-b zoi^svKDguQsv3H%AyWGwqvNl}d9yw?1&Mpmq^s>0)p*G|sO%Ek5|6+t@_WM-I|zfb zH!v|d990fN(<*P*wFpP#MzMU<#)uhYQM(QH{SAZdOtO-@Ack z?|1HQJ4f5Sba9+>;xupdKFPdHUx)_?tKU-P`sm{9;!irwHhVnlJ~eXA$>#bpC-|(L zj-QdCEGx8*@JAE67A)I8krc+iu3{yVc@{l+o97U&8{Y^Nul*uB^>#F`VXGD#naxE; z?e}3&+Kp|&-PUG~=?lC(rrdtMG`@!e#9*w<6n45duUs){)b;j)Piu24BUG+9z-GvD zNiX7R*kingzW%R0mx|LjYEp8Xrnzq%a>&Ur{a4bTf7&lum-P$Z2m6w2I?KXoVEWKBGN@`0$Pw2sA z-6xAP#s`Lba~kJE|KVEIALR`l*9p-;ft^LCvqeL1*B;+6=NTz8ZN<5-GinB1{AzdH zCb0F=Z4VS2xs9|*1SR$vP(WU8g>t)Ca(M!>EbqKJ&l&o%5ShaGdTm#B&-LP;i3LEh zkXvu|H1%Gc_hNHp=VUa&hZqkFDd`hlv5~cxTZAOpr~ALvbN$$ju%Rat-J9>pa9n_Z zV6sb&(={1YEHiE!*K=rmJgA=ze5SgY==-Xr&tdLi>)Au4 z%tO|hAOmt`&i1v=!CMy&?ql477@bdYTay```}&m>jB|}nQN34(rz^yK0dm1mgl(GGt*e_rdWQ&ykH)+QLRF8v*B*gj)(t$y- z=lR};w^OgJkP>*8sF2D455QROkPc*TSahfiW9(Oe6K9V~Rd}EWKr38l+2jdm!fq+3 zp9`}r$(8tx=vh$t(Mp#cJ=kP~_<{N#vt~bZu-Zr-;A0|}aC23t0V`3e~;Un5<%YXq{QfztF;)lOHjK;*|eLD1N>K#EQ=$S}9Ag_CA=~K;A z*_^D5A7GTdVfH6$aXtk=rZ|F+J&)btT__eefvm)NGbauElZEKZm^fxNf`UI9MZ#a6 z&8C!=PrN4pck#sjdB#pM`woIa{|xHNjkdD+s_$hM>QT^v0bc7zD(ieu9PVRwAtqn(`W>BtF&ZRPuWSWKa7cWni@D}y$xd9Hejfb+ zCM1ft=CwCZ+zYW)JK+P1q*y}+U*(<^1&-@Jdn!A=|0dah9#idv>ruTln%;x^%88I18-Q=6WBy!5Nz76shTqW))8@u3sBIE2iIbj zX`i<<|2LT*YuB?Y!_X&^v~?wb=j!Sg-AhR~ocA7ovM>X*)Z+$MXbC~T?_wn1(SsRv z2K~=da?p-C9Nu_KG9`y&2zj;i(mFqDdi>|%B!XGs#Q3xJc43TiTOpgP9_Q#Mq7kT? zd`DO?@NM~|Di}`)h%r_$XCP4T-$S_qtOFWQby>W2uze$RxfeIA81sr7>?~dw0>Qv9^lcRDTHs;*F0D^S7alP&WTAc^}zlRQYBj@65ve5L6oKbk}^A<$k+(b zu2VshZHFO<#x-S}nZN%IExlmAG~*M!+b(@b4A@0>&xBZgN&a77%ww7_-o+F;Lj*PnlOIET?#6h$CbI|Aarcfdi&+V(rLkzHoCLW8miyMEVg* zl8UO+VQX!s{^)#y5=1xck8L9D2H?}EK`}IDv7Kizu`O&)=K=?*(>UM#pDH)Hs=km0 zZ;?3!-uyNCdpAhK%M{eRJX*d^CR)}Z`jwsocP*nuc=gm@iKaU(_eY!TMzY(>;Vw(9 zPoxDy<*Nd@cCX2~j!R$hqZ>D~Xs4CCoFf0Vne4{rh+od8r}s&wWj&9PIJii^A4DZc z6~{OyaYEcKJ}vrv>yVFkIjSLm7b%|aa=y?y-2NqA8@Gt5-zK-8vh{hu!3TXSN+Pg> zu{~mHt0iDLr*IFI8Iu~@fXSLjK?oW9pWi4L!Ia3)r%k-^;ZPy|tj4bZX5KGJ`U9T_ z#Q>Umb1_dzqK4puoN+`w6=K$7euYWFzoB&9?F$%E(mihPTAGsWr_fQzyOK>!hrF|= z2QQF0gm<&rBFyXhW!n@#M8nC`t^mJ@L7hD1J$OfQAffzYKhR~-5L~oUfUK(9njfF3 zXBfsj612$!&KD)+?oknR?uTNbfk(mLqBHzaH3UpXtqe&?4sV8HeHSKl+b!cS+(Lcv z#brs0@pDpmAq^1k!AWA{9xgNfF(O-(AV&y7Ut5p5W7kx3%X;w04tO;ydl>7+0IhKT zKY%FJyEq&|VtGyizMk`nfzE3xZelXzZ+y@Q$G=serdT;<6kK+9I(kqn2!R3GrIuUD z;D5ISr$h4MgZ@hmnJD~)AW0W*k6Sk(Xh1v@1o%h{(s?0`CYX6s$wZ&wD)Je*Fx`+* z_baKST@gWX@j=XCQZ-bKA`m1UYhA#RtA+|suNb)pYM)L8Uj8saeNo)=0U z4elt#X|fE<`Z+XO-f>X5p97m7Qm_!$ih6cOX*=lfC|WfHR}8R^l!#99ZYO9MfTC9v z5*K>oA5ykmsw7#NuQLSn!RBVM@e?k?l7+vL1@r-%=u_|QOJHb&FxI^{FA5;_UD0>e zU+14-F}f!JcxdrHx8#NH{IUO2&Z)T%?O3RLiH2qe1GI|0A8^k9^i8-&#RlF%V(h`R zWuEhx=ju}t!u~P!PA!v57IG@MnvW&epe$BH*y`=oZ%9R=iHtg3x55HGnLp%oY4hoy!ZXcAlW{>3uprlh zXixi!&qGq@>bhdty7jLJIGYSP@+O*Cr*Z7Y4(}bLHMkIrd%IStL2X z_CF4Mh`Z-S`KMHeZy?wniwHy;9en5v*oxKVol*vu)?DPW>c>?f3@!9Z9^!rKqzjqZ) zbXZ*}%92}pY@6*#D;t(O0tpx-vOUZrg)#LyE7g~{kFxzxn=CG}4C6k5^UU0Z3r3!N zbV*uo=4>tSp`v#}gC?Cuo#F_{kP^N~vOnLYNgBek&sGZj3P`{lq@aI5fp6ilE0-~~ zUuaAH7O=q_1X#=Omi|l-cck8Bd!u1*{Lq~-TGit-=e*8P4Pja_H*LwY72%%v}RXpSW~4F-|V>n*D*cewdC>mRmAL zbyfwsjhVgXFQj0ENcK=kW;+TLt;$!Daf7EpT}cORfM47 zZ^y>*-u6>~*WO&u=oo=Y4dj}~ti@kx`??&N=tf?4oc}<@DBrUd`?QQ`8u>9j)2uVK zu`DtUh3MwoNjiYB$OJ{JzVAGSnJ^-s%IG}j@4|pc^oS?o*R(KP6a6I2c&;Kap$Ik- z^S#no%#Oj+A0FsbQ6;7(6XX!T_;V@m!a-`Ih_3GXsZ&k2A5Qq7&!_x77$oS2Qfuv3 zgMa9r?DlCeYl2Q{0=l+}p7dBBT)XmneD)ls^&S1QQ+)D8^%q3}iVm26v%LsZObqV0HQ-bcBAcs;_QJy^ zD3DJr*BDhuUvRjhz%S44y~2T$$b~M;3P*leD2QH<$H`7D1`q=q*{S1LwLbEh>}#`a$X{!p6*@x%Nx zxePp99drjN@PNGR#k11>fJ(IPu!$or3{%E2dc`Q4K{Y_|HkK*o3jJDH@nH> zkiYu=nh@}D>zP>l@wHN`z^KmeOym%JMcb`#1l?s@f^lWu~j6 zpa13cC385oY*!A?$Sg0jyLIt?eeIpA1{CX^$m!SD5@m`pdtUi0?O27qc8zdP z`=KqiL;llzJwqNh?1#{9X%hTN?clWCxU5Xm@=Ssu9*$|L)?2B~_Zax&ZK0lOCX)k8 zZ@TihR`7`zTHbE@>uqAa^?FXcQDxL1@9SUSi=|W&z&I78bAPc%i2{=~gH(5ESoGEp z*^|Go4hW?mDoC2=O+o^l@jVRKkk z@Qu4~v-YR=qNB9Pg&7!W4ild8sraE*(LGud`<;@wo4}pHR#1o$8$!WG^9xp#Hi^O$ z;aC=@nc6&u1JjZm_l~C7zf_+=qSQTNvZ?oNa!JYAe;OCJr(RDx6%Xatqo_ZF#-nKP zl#wIeeoZgrmM%ozoxdN{S-sTxp%?$1ySfSkd%#>v&Ei9Y#<_JaWKk!4WTDLnLdZ8S zg^54$xo%YVCY9cO&Y)=50(w|nt(SZA>LneD>+ThR)4Fx>NfbQ8#Z`KT&}h%ZiyXCnO6syS7TjYpcZt2m2qA*Zzjb-Mfj5-l?#}A-A zxgoBCl2PBO#E+-5`rA+jB*Zi9_#HpN6-+AH6 zHey2PpP=!X2XX)MvDBAPgyEgDzHE%imq1G&YjiajSXOxrzUe-%l-_+72inKLWrHjW z(pf^))Nx*QmKZeKb;P=G@KWzrYLZ-o09$dnfwV4h5_0#OLJM4?LafK2HqCF==gBm9 z`1qjw`i1W!Qi46b>_#5M*F)1mk0t%{s#Iam!N(+Z_D{`Ze4jiXk6y;O0q5Dh9B>v$ zY#I~LCL0u{d&NfI=l%}&ai1W{pxD^Wy<-wB-UQI_`HhTaed5=t*mV|Fb!{dsl17E@WvQx6YNR!%Rk;ULQ`c>19)qZ#$`rboTk^<#{QI$A9b+*3tfoJGt-F0lLlJp>*3f*UY@q-F9c|^qP`9Nu~moPrlyT`=2ec zE%n?!NYZv$4}Zq1S8EB%f6FVIa|I9e%%(twIU#MhLjgmw4IdVmsEfI*T#M;C`FwAT zGXCL-J8-_yhdJY3^&TDmp4Jr9ot<@)J&y1=Bum=e>W4#qnu*f+9LzIUeOrLSpkegCRWORDP2su#l+#lMC3PE)3( z%M3kAfzMHba5@XgjShIreZ zs_>nM^PfU@AbHY`UPBXU9R>}*kjua-U4}HgzyOA#qy*_e zO#JbL^#^uk!*=mVnd2CG20Z2UjUrHHlS{c0mqwHxEVv=_Kr-_O+Wm3ph$gBIl`hcC zyNw)2m<8`xMOzYDGANjXLJ36wO?8_OLA03 zud;=5w!E!A+Wmr#$3lPRVl_nNWMoI~w_k!&qIjtujye?d2^Xn9p6q%lF;iSd-{;hR zu}=kfv5Bx=`g!$T_jJ<|r*7pj>k+|D1g<+aagO4)Nc>j<`u#`Dyj-;pgI^HQ&@{M* zYMq>of|bl^`~J&h)fGWcNyq5o-}QGGg8aaaWv>GR}k-r0cJ`; z5kt$JE?I+x;%FJ+c+=0OasDPB1Q5$7Ll_kkV!uqe%rp#r|1`kB`c6xJ0M`(fgKj6> zW_ShekHNR^-HA(qUIi$CQ99y{KRr9uFUErx@vV{@->{yQpJQ)}W_c&k_Tg2Yc(p80 zN90#$nkA(8tkZz+)VIZ-NcC5s%XQl9Xb1y12`}zR3*c*}M;G4@f%ueWJ`wSLDz8K= zD}B04v!Ma0Plb7o;?Vbjf58f7mSRxs4c5N){gUq@xh( zFieB;VcRA;n^u%E3NZLHblyb|&zX+n&_n@{=`^C{=Y`6tV8FYG2N-~!OOjXvNk)sW z(&r9d148MV@(eRVvb_pR^{je7 zL)NZ1t;40?h`^XhgLV8BhRLL!hI7P9d3M$5_@-@}8En{>U#ikwrs0vyWgt{63Ux~1 zya6j@|R<+ zkq;x!GymO}()Cy1Xs4z2b@U+B(n6uTzi2wn`b}rlmnsn0SDIF;tmTPEr2a_b^TuiU zpGAjK?p-ZH2aIxrVu$cICcRDXYX4o6#8T#oT4gI*SjZ!LqWX6NPqU zry=y)Z>*lldvq0#qdnyq?;Rz~K5ysjsP(}9AsF8(>V)c?wUGBvqQjf}ir{$=R#IabfPBdXHniY zr_7--j)DGvY;Y@m$GA5ErL)HJj7i*3-npBxui%56_{r&(;HQ_S#)9VOnha zftb}KZcp$#5IkhkK+k+Itp-B1>IYP4u@@^I^xTg(5DPASi_OKm-e`3)`(D1ytySW?|g=7kA0<)M+6WB(@4DyVE?BW}0L^&0rWTqp>wp?U9}Kb|gW(;2 z?HmDrM^C~@XZsmq{%7D(60TS+C+vqV)(CL&rw3s zfctApQ5{ayK@lxIVBxgWJ!%*PfND9o&{uH*D2?R&+6{#J{sP9#gWCn%H~g~3F$$XX z+66{dtg-k3|IVklQTv_e0ulfpKGd$_((LLM&2vCd{mD5?!V5gWjSFpMK1g%|N(yM7 z=0oi#$?!`7&isPf!VtAsG~lhu^&4_Sjs&;2I&}40uvlFz66(D4P4#m5G*(^z5%G#8FTZ7)Fp<&YP<^7zO z!0a*#{bgtNiSimCl+Mr?*_7~O10ZW-j`fT7GA6|gaAs%jJ_fTGe60-lnMUV41`}Yw zleHlpfi1i+(Ff2q!H5m?-O!Xo6D;09-(v!XIR@9y#jixEwn@{0M|efQCIE}|aSHgj zSFL*2No=rXQ3effX)kdqU;rUrR14l5fT(K;x<|;1aveb{8x>Zd5O4zmFk<1rtBL~P zE!?tsCxH&ul9@GoDpFmO8v zFfeXFkwcO+~u zy9%JUe_v5AhGTq914ZrwF|6DXn~6TK|ESdcq)YK1Yk;mbC~83r5F=jaYn!S`1vYlh z_`gL)B8SdNzGa#p4gcuqkx`ATdzv(wa!Fc58~geIrNU3^mf;h)$^)0nb%)vFWEB| zq^2&Njbwa~6E`Gq@o;lKrftaS?2a^Rb0^#Cx83Hx-lC5)UCA~cvd!9C=WchRNlWF< z89ws9oD?1T*U7Kp{9&2Ep=nI0-D3N!j@8;D#&7yE;{%II>~WQ8I5BtWMk&P!vxm;d z*@Trl#c^UFS#Nx%!!WrgP~lsYw_j06)JXii&DYjd`A$+dN&*Swn1I%e1ltSm)Y880 zH*EgBQ+L85GVRanh8H~y{bx#2W6idwmOgiNGloPhH@sU4f8npU%?G?K&kSR2S#5$9 zkLO&Ii#o=h$A8}5C%FFN0fBda33!!7Zs*ZZAhc20yOUPujhXf(+ZcN3|IMbjrDwf4 z0eBX>s7~(BCG5iw^zXh}eY`j#FWq=kfBl0zgLUyj4vEBCXZdR`UkO{}bg^Eaygv8q6}E=Epz;~9CAj3*&CM?C%^hrZ4Hb2 zgNl`w#K@`9I|Gb#zPB+vx@FGXshSZO5@gXxgAwHZ&f)sge?w0&845m6|2ZOr4TN1P zUfh1w`5hW8vjwdmY1Pd6Zs|QHj$C2%;=jIFohqp6PIk746;5J@4KwyollOiNo+cdB zZ1c;f010F8f7`c@FG&#Fxnw4#1bt|OzGnZUCWFZG+|yi|$wNz2?ZVwMq(!d=nfM2a z*HU4^%s=+Cm!eGP&aBlTCw@8GZx{n>mCOQ#m3kPSiFe0XJ}j~%dtN{`c2pv8^lvq+ zDXUQ`ZLilm;@v1c)hpQY&2p33kqN0F_8L)$LeBoqfM0}(_p98osKJLf7heN_lP%5{ z5zwd#TeBh;_ADi{2B_e*ynnV)kBYQ}ry>(FIp|-_`MY=O`v@=fwJAAqf^9L$O9-6R zF6WnNAsGe6tF^HfL$4~l5TdYbha$SZ@p_kx2% z5NT38WG}vAaz;B_yQ2~(<{_-nGo1>;tr5jn%vpM&|LuO@B+*I;y;Y%NmRl*z5MJad zE#_g$gmnG`ueD&&wEL7w$AL`X+$zL)?cT23o|@^I09@w8`^Q?XgP;w$G_rVw+^Fut z*PzJ?l~Zq5b=0+1zN2vB4FBc3aq~V4B3X7HtLhK^r&ojaH#K(1j7->x9)iw&I{7NM zUB|kZD8CfZNZ?&sjl-&Wz*4VOZhXTYNHAN=(l8r$6r;H7hPnUFDJeAM!&xhQ8!QO_5`2t-OG|OOtpAt-}-WOd4)v(+M->qmP-4Y;z9SxaJ zp<B&oPQepjRom|!I27@=TAHfh?F12Zs?y#e~9Cjp> zT`Yy~JQe0h#KDa;$)LiPXR9FYJf}|!V?shNfW$1CN2;w+=C`+63ItDk^>WUQoPLWh zI=Lw@R~bcIK0}VIE!=*t+IY2@R_`04Hgei$@ngxlS$6up>pp3{r%S>NbtAs zkXsLIU#~sNhEN#F9i6ktbBIz-r7mnnpQRe49jr2p~ zhKNO%>MbHZ!DB21Ug%ydJ~|w_S{JrdiyeqQgVnYK*!bH5YiU@mATAA!w&dj$aV1Wz z?^qypHYq}O!g1fZB-tdn7nwdA&;R-!ljf%N{m1XQIy(a^rShk`g{tS@jf;u0IDG8{ zXVmWrQqwOVw)28lTMtLXTVMT_JjuQuzP@8*AZWya5ZZ^l&8hd(WB`H6cVXKTjMKv| zZOPa#F^N@GFP-fOp_w2;q?v-5I<_{c(4OK&lXwyn?^*^DnGr5fi)4b-&*}LWBA0aD zl<%(<2L;Akvg`iNvXXqTn10gpx?AjRP6Z@R>B_METgwF~x2;#5g_3nMUTP5}`BChk zu8L{Iz9)B!X(Vy;HrRxW`kYFQ{aMg}UmNQW91tHnHYnlY!JCYFG#%EWC0ohilPu(3 z&s_G8?l?TS=OLn9zX1nSIrE8WM8BtZveuQdM`3#Z@y|!o2C3G&;q>9*;ltk~nkXm2kTEa zxsKka!{17&aqyPm7EkP_509o-VC1L$f-sG!xNKm9DnPH0!O+@ts?O&t-~4s2gWre- zm7Z30YG3r9t%*%^thuO&=o_OqlhOi#3=9lFAm850oi}7X=%oV?88zWMsq+};Tt{n| z(+Av!;c?0N%FE723z2NMoh*7wNZByXzRuD#%-PR8P(UoS$a^rDuhmVdr3A0tKdmv62y?P+y|#jF0?#g#}Z z(4o~GMzsN(#Q`16b3R87d6d`9@aVguvoA4lMixSd6znQDd3HAC6-w}(kk-<2Am?;GK-Kz1qqLWJ0K zDDm5z_N~|C4$|t#hLB|?`g6x_=t*L-X!D(77tPZ4wT8ZRe&O*ippKJ-o(DcMM{X%v zc+eDvlOBFB_#i^by-NvoXlScV(6T;Qqg0S`C=WPF9a(96+9OVl#ZT_gQ5=-FxnjJ% z%H(ESbR)j^VV<(-g7BKk;?u`Dw(@V@As$x}#be%DdhwJ!#`L}B_ADbm8=L#&a&G@@ z6^+Mno^t}AVNY7lj3H%XZN^^(N|-{c1uaeH?NQBDkc(gD&q6iaS69cb6c?LA`kYkio)@@?XhbucKBBh@anl z6#Imd?DY7e2H4yrC^4ZCHUN`}AsSeBRH|>hfreBD; zlG!Qdw;`c#$L&X$?^I=cQ-rrCG?$}@-3gI7b@V`xeY9^N-tU>2yk?8f+3#5(HdrU4 zwMFHUA_`Rq9s(pb=yju>X*1+>IV@a;uj>j zfi%<)=^m!rJrWnAMED1( z2pZJzfJK8>UY~5(j_YC*DPFS;J7VWZzyDPQmG=GP$Gp*;x_S>J2w36!qJPQcMkvFX6*P2aqr#>6tAG#XtV$2_;1CODEkfPG*(nWx7z*>9YI;!PzM%N)a=Gd5-# ze5aip9}n8}!({wLB-#}Pde^Wz_rvkQiV3+_`D=TbjeF2by2l?}U7;Zx$fK0%Mz3Xr z1%{Efe@%e-@Zq#~{z4l@??gBjsJ;>87J^=REf&(PK6m=0js*HW^yCTKZeuSj$Bxr$ zM<&OHwW`_4@HL4yGhP~1VA(814%@INCU1^y*{ke+D$dK&7E8p)|-#2r9Dyl-fs(BTKG_5)~dO0LCStLnMA|#K{Bu!cm zJD?sb!FiSD`9ArEASnu|fI@V2SQoMh-(ypBJs9OEU7;Rp=2rd7X`ShgRf#^hvzyjr z%a@ZKZwk&`*m+jHs{`I^ld0CUF$KO}{b?h7U}_de2o$d`c_}Rfl3qP7(NiNf>7ID1 z<$TkvtdIQo6CbE%t1iO@-ZvOACq|J3ei6~tTXac$>*`i zXe=z*b-|^0Y5N}^y23~VJH18z9n34SRV#(~e_N<5cIo8pd3)%3ej=t`1Yqu9*=`}z z{Jw{b_~zFr(P>)?7%gyeMVAGQCjMx?c3vtuV#fO4DCFv4{o=Ev%uO2h;rIBH(VKb& zk)dRXU!)HrqS=OLMH09%dD8t=c1r#O^C~Fu+-K?2>0}mC7K!2!j!`r`5>50NbNA37 zSiqRNWSR539y`nsPxc|-&HKf>hNQUS#$BG>TnYuu`UTi|;MbGicX#~%?noG?JRy8! z!HtuiKCPN}y#T{#&^U)^)$7$w)!$<+Vnts3l^mm{Lwr%nNHnBI<90-2^_z-sP(I zjDp|2T5|t8B@KOBvRoAgnl#L8^b1im5mFFx5}InxwHfg$_8vfaep&Km~#cp$zUm!<7!R>)aqctRb<;A zUt;yYn4+KT{1AYVC?yBVPBS$^WiET!HE0cC&Xx7-=64!+ORP?%THnqJfLbh&{LHOd zA=|F~J#q!q`G*L$T^A1HE1$1RJL9Z@j*~OgsQB?Xq)(|N!>5Q^nG`GRAfXIvfd|ir zc1?HcEHH6ycg#OY@J_o%N|cZT(;46Il{yd1Z8+ltTaBwk(5K;&T`kiUDWk?BtxxKk z!VsN79P$%cgWbK~+z6i^NBF>TB%16V!--LCMBpjrVEEhZ_ZzOi`rZGOe0af5(e~cn zjWB$=)m;?gr;b8TSUT|7mAu6%U>f_d32%QJqyZyL3k-dAH+(POf3?&{toj}ssMYo= z5$X<@>j*Aodl6D~()U-f*g`tcvd=g|6q>Yg z9TMS?Gz#X#m5Zhh-bzfYGGD%}TShgm#3d86RCde@CZJtCS~N)UDtou-PY1Zr z1T1AxGNW;HdH+$}Sy9Fv8ztfd3sQk80sDLr&c>mMxgLdX! z&X%W-U_AQs?iXtm#MGmbBDZd9#Gb1nDr_(2GnNeXG+e*dah@_(k7K#MqmMHB_{5** ztCr#%!lg_y;=~mtx43Ul9bhtOF2nF7HTNmrv>^!|FwE)oF*VjTe|z5GSm*Y=9=DkD zMrT919&_K8w=&PTO02p?$f2ZD?B+fk+@;I2`!JwuAHBDT=#Rv%%!D200BfacvzwoD z3h7vh!!8Ua@qd=P0`-o@n?Tmx8Ea=HYRDoyK5xpxo*>xi$4Q$~u*z4L1$xxd-Ilpe zho$kB6@gZIFlPZMkpU9T-ZT;-Iq~pbySc?J@n^d%?XvKJ@^2}NIXcG8Jl41?jn34H zmHMX)z#Fm)+{5bS3}8D7xdER}QVE_dRV7hjX$dDJaJqvRX&m~U5l-1!Ob;w`$}4TzV)k6~tGbnD96&~e7yLG>9MpBS_4bJf%v`dD{hTQX$oOXS=T11u-f zSWN3G3jM^AS(DJiShH~7J20f7L+vdoVC!t+%#q3=eDBzjcW%5G?>9+V9dq=@I}ap$ ztzbD`0&kvtX@4Hx--jD=k=d<*z!DKl|*wa(_mV<8-Us!hGZXP14+Xd)<7Z@;h6}O!JPjKl2 zv?LI>3h#}==vAyKJPj;XE8ha9!X zmFXe*73o*L`lY9QlF8^l+J9KUaCwT}Uqi>K#f1=PvmJZ~I`L8CRsgiO10}T*?+odcyXUw{P3b+ zX>We1EnvOCzs*mk=|p@ZVE8OdcTtz=!+uAipVa(Z$XD1j4`^RxZ{e!L@>0@O$lJD| z_oX3cRrq%~0lL<*rjOq0*tQ+R$j0v2(`hmwGz$4w)2&2W{q6l*vs`*+1eUdliXk9+ zmwp8t2tB1IA$csJx^FxGjY|oY*S)~A(0B8luKQp)H?oPw$hryM7;mLMa+JB2QfmQP zT75gyuk2d%{;%&=d;1E#5~>_7)wfiWySTnUTOm}=ps@M_%H8W%&X0?bO8pS z>4MKwXev}kA}j4Q4*ghFB?2ia@orTLLsNl^ras4c0Sb<;U)vPC>q22ZF<;vG1D&P z+=RSN1oberl<-OIhucgg58Drsd!b{2S0@_$@j6gDY2APTV`Sj2BE#D1-iViwv1JGAgaxQVu$mu|8-QuX&32fk{>!y`9z8IQca%J z97pboM6*}tu9!H5&HZ`6l8m_(zvjJt&NW~;XMKQ;>p&y%OXvV&K z0VTyCuBSCp;&T-zW=Z+Mket0)CcChd(Dk|vY2UJn5{fm38OB=TFghoTE(y{b9UfIe zp}hU-NV-4JRo-*pnQUEGWw>>^`z-d0uuv8LV^9DaO1){#LiTnn2!&7kGj`}HA&kp+ z4bOh{5kd2>@_)7qrXAW~9B)#GiN9SXg3*d0H?&-X`qi6^&rjCgu~&P4#_BEAtREs13jryC(Nj}rH@sJg;fAtaYx%tj zn6?BExjT;Cy%^Mf2uVgFP2o=0JDZV+^x_Z>OwW;|+K%PjF!3XK5Zzm}f`A2B*FGz!HtU=` zjc2cJ1udV~Q&7dA*F>`_IP)wB8Ok)|8!?(G1GOZ{^86vb|5aN*DglHDF$z~EgA#T> zN=goNl~9zH(F$9^|b*@+%bf;T?Be;#cTX%C1bf#{>%Pwdro zV5XB`RXF36UsaNI@DGB}_evoWS^SC!i&$~49jW3JdaWAPd(FAh+>p=pKuj&xY{?@s zSn!!{bP_2#Ibpz0z7lNT3$gZRFN3BqpjQ4va(Ze2ICeY*!~6ab%QB1g__pSAO@Gx1 zrdTt@47|;c?t&2AV~_rr6p;nFE{IB zx^pVJ`&W68a3=_!G7S{Hw*v({Cwf48F9f@gak}E9t1yoeLYug7m@xcMUA;4m_w;pE z+ERV)8k#f?sf1F1H<~_4Hqxu$A#`vQHDhrAqg%1njgOd^pIm#@Y;~i{tR@vXy!v6z z;2em|-H<&{oBz6dK1zPGT?84PN04?Wk9eZ_^m{4_K z_n26t)rSr912j*Ttrcm5QFQES1<3qI33V=NA)V@0Vv>}Ap1^V`7tSec=Y_cDpM;lx zlBdOLi-08Qto5Z@&5zpcJc&kf^bD+K)pk_OeS?EN|w1O#c4%t()J|{HfnCy(~8u zEn129fML0PV1XTx?&-AdDz77uC;Y$hb&gjy+`LdL#q z>jI5!4i#dS6i*&0I$-Q7+gjq#8LAm9P>5qe>cHL*18|Rr%(y)D*ZV}Nw(sbAR(rkJ z-O*>#dfJ#aNz5@EBKV;{+*tBUuPiJ_0$QGYjTeOzO4eAy9JKtZ_rog|VG78)FZ9xz z2_geLt;Mr9PAiPmdQ6Q`oe~k#ym97@;uzu=pRy{;AFd8NjGzy&EWO3KTf$yljH5~~ z$w5(Jv~lI-YVh0kT@tdwfLs}I_6o@@KXUAJYxT3K?+!mN^zt7Ky*_G6zgC)qEeu@s z5nRHhs*bZQiLoKzsQj;f-%UqW6=&zw z=2dddK#?D#BIS4$!y(NXa&yzPcg%@jMK$uVhv zkUPoF6~(Eg_1H)BMEk;Qu7MB>jUkp0rD*f@;ptr!$TP zEzJ`zE>{J;dSIsGJ}0U-i@PeXCmc!IeS_^>-X$aNJ4>zHv=y-?aGFQ{*Z#s?U#)ko6w>+oDi@9L%&TrJ+rwj7Xl$9&c8n$CX<2Ir?>hk8nBV?wV5{+TUHOp1f? z^8j9>xk7S2h!?0&-@1VNCYTAyrVOZlGZv7643tnK9MvBJ<-}K)c3(xWQ4CI~B90^} z^~y6S68elZKQknw>wsX(8#ZAv%yE$Rg(E z0eLTZ<$=DI#L*_ zsLUHHq0;(rMg+ZN#IF}VCe7=ZuWXQMnCZo(rYk6hy!&(fvc*ti^~jN;Wi#dj`!L6S zsI?aM_0H(S)i={$eN-6%jrGO#Ja#YJinscy!9|0}`!6oP!JPGBn40WgRr?A4xgmpb z^@K=>!r=)8z468CQ{%JhVy3rM@um6IyBO7ZEO2SWEf&}zHPfRz*FMGEM@^*mxluao zdOpZgA-R0;w2V!-t+Dv*O(?#?W3X0FbzVak!!^TjlX^zpFt#&%`QS4hl=*Sby+g}% zSV{smec8yp>cxn)OJ{6e!_>eNt2(_~qFbgHv(+g`^!~aFHYLSsyo?qTHT#GQhc*?dr%ysk+|q{--P>?kM343%3*QSp^;m#U6Q`KNl>b=fo=?JP zOH!3)1O2$Y=;$xVPiJ7$b2#IL-ZCq^xLLoFomI=ykj=ug)PREw=HU55kDVF=iRy!2 z7P_2Xi5$CxSQ+B*YNCc7jM3jht|!RQm$E^4rA}pw`^S2j)k>kAD|4K#scDVyR zg+^8;d)yD}?#Lh+Dwz+o%x9_5tDow`u^n|T+Gg@*mOh>YK6P4D>*!c2f&ODnI`6ui*WV)({WtjlT5Z$QHl(`ND;Oo zPI95nC#btzApphD_tm9|s~3G5t41oC74Yp25I>Q$9f0BD$$!&u#)$YPf#5NchCD1} zd(9BLKRwd@?|7iDA2xusx1J6lDuI!pADGJp;Wwf=Y^H2tuFof58dCh~&sXyN%AQtCd+9m8<$DB}1 zi#@rvhJdBKBac81X}0L_{Wl=;RW%bg;Z1GJL(StecO6Dp7c?_M8B^7Z^N+%#f?(?!kRa5K}w*%uCfStpPy=O-a^Sn8k zLD=~c(TfaV?(5Pst120~DKUE4E-MgSYeE_CpCV`+&E}b{um!y4RVd8M~~uQzuat}6>zotN$#gkW5St3}$7V;L2Z*bch8fwwT^qL}(< zHk~S=-J9f0!?bNPah*0K_9Ye;@NFO|`mX2Th(_rPJ2J9r++a8Vdwnx1J1wg6TtViS zR%$0dUt~3SAF*I1KtKpG7@4g?;E~~y#2v|?2%U1INF_naJ98PA17S9)z`<}ym{P&6 zzR>%~aqC~^(N>(r#cumVnz*@=uRsT0d8nck`#!RIvVn&7mYuK=AuUN0LLZN#UYg}^8*7e1fAY#~=VcHOPXm-RjI>f8`DjjVW(fxV ztrhbgm%J78o%w0|%A3H=?JsCG#pJn`{}R!sVN;+?6(~?Y6v#=DZS)8xs($@)4nr3e z2z*MklLBor`>oDriOWC#^0J}=UJb%aBP%$DVc$BZ#JY3bc`b7%zjv`=Kip|nm1hi= zK2=DYD&hVkrJ|4_4f(ujuK0H?ATt-eUt)30;aI5#78zEU09<8ubzL1nV!dw@l@8j_ zsT|eMCAU+M4BzrB`g%hT&yrQ9qKSO^e!LixNL#-R;ARad{sM}jh#a!rbqRW`+`JdU z?zTEfGKfX7ZI23L=D`4Il!gq{rOY#M`e>TkTTfj}#aak<TO-jmV)P0zTEx5NNP<@U$vRqytje_6erOVwkn*q&X7|?YO+;f-*h2xM_=e_x3`H zy&a5jbwB$zS4mD8_uXC7ymfTaV{p6GL_|l(Qe+rJYxHIxwUb28Gnbi#^?G%ibmI1| zNM3x7sIxKwnNK%sl!H;NV}#(GKIT{eveh^?ui$iYslg9*?!!{f#Le^P!WUf9_jCaf z%K<73&cnSavV#JU&KJUcr$<=NlY|FTJAS&o8^MuT0t2R)bM290D`J9l8i)bJTI<5S;Sb;@4)(k7T#!6lEf!miBOBe$CGxKL9bO1ns1VMdJ! zV`sbYr(3-e4;#^(U+mx*z1`Y~?a)!5Jo2R(EA`GKtqgeMQ6fqwjEtJ`pp0QoxN`Oo zFX=nGj}a_z+k*14#5u#eYV&P!j1P_Brbd`NIbQU8{uIMFj@4QmF-*NDoN(1AiD?u6 z=BGRbqq97<>U;x-&ZANJz4n5Yjo{-0G53+OUXpqLz+ zQW$)D5!Uh+EyMu5>*#mOPrJAjBkD&V|HuT&{eJbR&9f$=GdOnh@;_L^1{> zBDcfH?t{9~197J6tj~cQxhwQ(BoS0C08QY*dG5j~wld~FiU}{XDcDKHXV)_7M(lAW zG!d_Dh3Gzr+f3Zxoqqa_6Ko$AfZC%@j^*K|2AIeRso$xOhL5E1#!*?epgn+`e0V!5 zH&26aCp^YfR%w!1MH%5QB1H?R)B$izr-X6BlLROHGCb)P{|A^Q9MZSeqmMJ6S_fE$6P z&oRX;=wXhE?~SepD2SFI93A42UKN|6J`Y`JH{%&S}>48wY}t644wMN(5N^C0V|S zek1| z{=7iPB{V&ld%d^D17|tO>%VF8u|{D3r}7@$w#RfTTo#XSR$&rKt>@oe8Ae5Qd4zUX z9oj@yF*NRWqH~PBF;}m5Mpz=j;7V}Y8FPlMnDn*sIV3%X5K|mqrq&M?!2xS$hb7je zfjeCBGK(N*vxl5K7O=$Dxv2IhVSVYs;bwH{e>$=%1xig+y(>wIjc2X1t0RwpX4kUj z2uel4PdMfZ!(-O5zQFMqdOnR?YSbbX!f4^Xvd^=SaX7dl5@|b4#Cs`xd|A5;d7;u) zzhkHG)>Cn7sRjkg=}$y_-?Fc7JlXRR--o{rOhgjCZF1uOrE2ArGh|&bj=r6wHs9#g#F} zdeJuY5)6`!2`7I*<@J^bMAF3cp1AH#y=S+bXLw$`0AM%+SLl>}7Y8|-L8&H4;nk54yrXukIu|_65`hL*XGap zV|9!e&+%4)^qY~U;eHuO-|yw`XIb<)8Gz|S5ZPd$2+xtK1ld!$FDTzv62y{JI6UOr zwf0j&j7qX*E;~3q{Xq;YQ2O9=?y9~Ai%8Mcd=NH>s*3`P^T$EI?-R7x3(Yu2tLa=(74>q} zBy_$+r!{>G9=N&sk{|lx73~W~;*CVX|m}@1McB4$q0_-L>lN-H+leb--u<{S{was_-aKYDmFPukUxYOw3wdZufr< z-HB)e%SSLkZ!RR9x*u)!M~)Y=b+2{3+u1shbb{lw);r_8+R)Ngw|Peum7Qk$Sof|1 z)@O)A%P)Ri_t&yZeqy8nMl~cRNd}_?M-JoAk=;{86~XQc)zR>ra&B&w`S<_O6E}#g z@Vxf&8ktbBOAld<0sXX|)@1yuZ5H7zG&=m7zHH!f4xMghqBtnB4VU|4IjSeX3*P~D zE64HsH4Hzh@KxrYgAGkh(dev>kC^td*(^fV@qteYx7jV}Sqh9Xv@oExm^d~(U$VXM z(Vw=@^;`y2>icl6x}hS{UQ)sJJ%JjLilg8$Ccx~DR{u`gb82RJR7Gp5kT^i1Eq^jyOF)jI&gUA?)>g^=^&g4?^B*-6X^ z;s&I*=?SZspHMuc-X0pWG8U7D!oKY{hJL^TRvdod;Wt;+I(DfcBjQ9PLwutwbx_kb z*cUtLPtB!@YC-$zZPGU8m|aI z9CCdHszA5+Pv1%2p=@kc`?Z~UT36Hru?ZW)I24yEzMmU|E;auM&|V!hJjA@s6r`j9 ztdfw5H1Wi@)@`&qd-yEA9P8?1A{E@ydnW|NNOfa6RGeq2;->{MC4SgdvQQ{Pj4Mri zrO`u*{d~c2jiT$X#GmL~%(pt}_GuK{)PL7L>o?LMkEgu@2?qrWPB1UO@kpOC#)3;@jQ=y5c#?yM6`u*L zOx}<3?#6OKpR(QH*+oZ;GqSV>?_V~3B$-w}wWK3&&^OsmMDM0RMDlEvM)3tE@3B;^ zFggr=7vZ-rwX&FX@gK%fMXu2ooYVcPUul)dfbRaYm(Sm7W?JknbR?e;_2?>wJFcsQCZ;T|vc^{`aTNzeShR3xwZkT-6Ai`a-#yAUo7#<9ub zU65WHshcX|0ww@8vjA^2{RPe>>Udt>qvKj`8JXjtIFh`XW$JvJxG$yi&~C=|+;HlI zu(a6|+vSZgj7%M|+Zb5G3Yca{o-lcRmxKW#Rc+lhY!ap!c6aXQk(@Q?X|LW* z2|nm(*wo46!V9TlgN7#My{Y11|IL&^(@rZwt826bb5`S9xyd? z(xxIr{I$$8#PC6q*3TU--3`hNzDwTIuO}d;D_+MhbS!G$@sQixR{XL2+$kt?DLQ;U zvy~mEyv@M)-zU{%yqH4y#r9`9G4f;>az&v@S@Pd+(r;F$wtALH5?vVlq4%HeiE|(R6&PEDvGlAtXwmyT*{is2op=!!xE5!mJ<)Gb{T(> z@m=y6CWRW?<0kBf%XY>?efs{+A=PnheP{C5RfqQ&2!-joh3?Y=R%yu5xscGOgEflv zM(5g0fbH}9o{V?n1-p2r#w|Y9!s1(sM9#!|@D>bxiu_ejE?_>8g1*~%I&Cz=QR-P3 zsmk;BjE!LIcX_(+1jl8Ha%KV=Pqm=56Y1mRU=F9>Rv7DZc7f`<=aPLH zF!o7`(s;dtCvqfNk7~#RljVcCqN9T7ekG#K;8J@`fQ!~~=o|5$%YESS0%*EWS<2?tpqdJ7z%I1RWW|fMs2a=U#9B2(Lj;e^wFgm{urALE z2Zhb*@vNG1(r(eI4K@|12jrx?ivObSHdv-t(s%EVK0*!jPW3L0!rsiZF)LJ`99jLp zV%zp!7K7h;Q_VgE-E4cfmA$5_cri3`Gkwk69W`Mtb3YY7-0r$t8PQ%oI?%8gT3J2U zl8F8?{M=L7KyNW3Q{_9X#{&fCymurXzh;!SqP*@xJ?8LY6U0P5=-shQM0=$|Ac%{9 zYj0f7F#Z5!fEX5=a~q#cjec%2gnzC)TW=byDmh*S4@DVrA~0c=*gCZ!nDi~6>$X+2 z|9iKx0=Q7P96N=3xle1*aituVpN~^QLCltAyqSbQasYb?LW?qdtnXBv=zdz_~5KM5%3}TsU2^N zFAX3Ufz-YgQ5`@;0Vrjcf0`*=*2bfEibz7Yh(f$InQJ&on%@jeRZubDMfUkSo>y$c z+@9OG@V>m^FTX-tmR9^$M;@v9*zNP~+Wiek4$kT)JefRlf0~?V z^j+Bt0jvvU`P1?*r0mbJizda)6nk#cO`9SxOi*PaM_muoH zDz0#yagAbG$T(y#YjUP9rk+DM&QI|_dxUQUT0;YKz<;qSNh4t&E*MAZN0izbHuNl( zWH&{4J27t_w{VS61%)9jdud)qMsQ}xIb_I%%|S2+qoXEF?J=W!PbA_PG^ZX|NX?Ea-RLGrY0UN98i}zWyWCyu8ioa7-@od`jS7K-`N5Iu$Y= zaLZ%GC@8pX*cJfe|B;;0+2NXo1qCpBmyi1@yA2CH{!|T;{=T%t14;;57@W%=%6F^L zL7&*_{Rl8f5k&Nwh+~@DgI8`yNLgrgH;D$17KnVcdKYAN;v62A8KQ*Vu+s7UG%%w_SBpUvMWD8s7SJH^NgI|wp zCddbIJm^1-&iav%gq-^}q5u3%>t8Oq;?(r^O(auAJSw@4r5DcQEP30M_T#_n6l1)M z#A5*tmB||IH*DaxS&7?3u<}a0##_VAvK}HF;IR{@%eS`srC8~AEU@E-Jd*a*uGlF4 zNIq0k@TLjNjiHTHnagt5^}!?v8LF&~tyORcuOFTPj~@}s<$4xlxtOMpxO*~P@MeOj zXun%9Yvt?GJJ-*5aR5v@xvXv*|HOB!vZ~YcSuKeQBqP^93#gZ1u}H7Z-XZU<)>;ke zJ$UO7X_3NIW<;jI&#$s7D6>*s5&*@1{X!IKUkUZrd*1BH>OXT&e7qn97pT;EClb{t z?b^w@^5w7(drxV8pkJ?uFa*7=LQZIU6!4a^gPCqZ|06L*1sj(M%hjEKO#;gJXw12= zg~}p{!LI&&?T^`apUC-O*zuPBHg@JV_OUN@EkG8eDmT%%c$j(>+12G4+0WAn9Ku!N zytF-?;^@?*z1J{8c!*_TuUU(XWeU{3tke?w@>bNr$I;Z6VMo-m{Cv>8!~t1Z@58N8 zxRlO7LkjD*HqA6w=Yk+ ziUbM^xMBM=Lo63#ggMrv>2oWerC%E3O=XtIa^`*ePX9YoLdZ5nDjLE|TTbNr{ODLO zk?wY7A~FSHnB<=(F-`+zbctzG!ZiYet=h66BF>f`;zer`Xuc)C`)Xod;xX3G4?jmh z$i?-)f1kI*2=U7^_e&GIDQiCy*t?eJ&a64V(4K6XBPu9u@aBVp*b{4G$Z>(cw-n)h zPo0@dTEDskJYlAwntjtVs8g*6JE`4%c&m)rDbl}&LpYn)OBI_~o*dS#dO;8@} ziER;lEc9b7&CqBK%Y9Ma{I{o-@5h!NCe|M|!fA<_sWf7|Qt<|ru8|&|aB=5){d-0N zQVVO@=-^NCy0ouDHNy3<(D08RJb2YcHN0ko9^xe+=*4;IBo{ZJ=o0;YDc?t9-_W<( zFjmcE_Meubu4_!o@BU`j$J9*X5!+(_j7B}F8taDmBZPW0Ytg*SZ;E>8uH#>^#+7x7p_)cQd$-HdrK&oDFQVP{Eh#;>Ycy(^qMIzLk;mT5xZ|~TN}(w5T5B88~bm? zyRwNh-~cTJ-p6Jet$*~;e|DbBwSs*4Bpg&CYL7$~#W>uL%FlTI9M2)pW*oMmos_0^ zZ)QR;sp7YfX6nZ zkbdPt8EYvT#mL5C+JiFX7q8|YHEDk}q=!$z;2Y{?LAPu&T}sRUaXVE*6=+yoA7RX4 z+yRh2B1|gzOsi1|&z$qNa#$-B=}Z13cNGy5rMYGTAB=hIP>kWGmFaLS=*6e|FR`|o zW`^WI+VJrmI3X;=s{VNaJ$AX_{@FhP*iPL)jv8iR2#OD3QEu}%D5R5gQt3a#P^(%{ z(seloHq)+dd!JhcP(esb+eFeJ8xxxITrtWb)}$O5IuvA#;dYm z0{F4vap-qA?v6!;;Q1TS?>U0|i)C9leUbWbR)d|rNBCBGaPJsD;PUEPGb!*#Es(_= zGrNQhk(<6l24f_uV13`_WI3wwv{PCs)l)nZI8(2sr2g8M3`}oV?|of+5rh0B<`bMX zYxK(gL7(TPbn^SxSmqXdXq0w`(W%Ze+|1!7hcC5hJ!TS?-bqx?pK)joTByYejtFU@sOgbquaOzD8ZoQQMuJ$4-dP;s_IJ{QQVbl zmG|i0ei-@lhyxn`dov@GpDURmm>M9Hh&i43qN>#DCt9??{wZ%#naNQ%_=j>Z zO459d#cJo<3igTsuGRSaepMk^NHt=rP8h3X(UkKv#d|H=bYf!7MB<%Nlzr8M0e7 z=zc{jDAe~@IqneZ+oRT$9EIcYa+C~#3US}m(w8wI$X&kpst-(}gLVhxkcw{ks|cJ{qY`-pp-y~vsIWs5 zdFj1$OXb-QI!^$d=Sr8xQyJPXfW^*xA$Js)8W=dM{b2D#b5RqLODgQbok?gJynwws zlu(zbF6?bUFO6w5PRjRAYr3B>>ax$0dnDWFE3QphS;oH@27sj+Vyhp58xPS|A}P}E zBZd|Dy_Np_=KdpT10!m3_g3NiPtS3u8Cz}WL5o3N%*It|V8Cq$+G$exc_A%gh{y<` zHov5fzKQ-ew-1oYKNTHIxtf@W;*C|VsPgwD;;e=~C~{{}aZ0>1RZd2y8UW7)C*fS+ zMeJ?B6v<6(L3t=VW}(wCtC8Pt=<`@%ev9w3#Yhj_ln)n0QC{%IsO~YQzE53U74b1V zQ+TS79tfrZNz*Qvi37^0o-tdPt_gV;$yO%JH}5MLlzp2OILLI1&5D)d)98F=`^&o* z6xZ!xcOZDvAbqzs@q}3eMlWojrYI+Dv>4}SK^5pu^ik>V00!qjnpbm#@&$p37Mf4) zxXf4_q25TWmu@Kg%$jV`4^=@Qa>)U)D?Xqd{ds*Nb9K-Q}{jEi; zpF0lYn(axcqG%{9mcAMT5rW3W(doF1WAL zMN0*Yb2fkWl<|5HH?#b9=Yj=O;5Fu7(Q&Wx$GfABQ+C!2RA?rD^Jv|N_}4s}$KEO0 z`ZgT*PwZ;$tTFaITD_V{Xiz{SVQR#*@MrcSJ=%okAcwA#it(5dMaB=FC#Cg<1H!QKUpL5ZQG0@;;KI=y(@TKM%54 z3k(kP{%A0BV9=!ao^1%%{DE!2M+Yg{Whd&r>ViOurP;xSg21uRPFY!^GC*bP37c0( zR$@z;WXL;x!|S>|h)NG$P3r)&w&+`uqQtkYpH|>`TZpGL|e8*|)cS-?v1m!7#RoLZXa)Nr*vqh9UdN zmSsppVhAI8+4r($|4yHO;d_6$=iGbGeLSA``F!5xoO|z++}vaWFD5s=SnC^N9Rp`E z+$ZWaeP(bXE8i$7sdG3m32nHXLo1E#x(#UzTcv%<+&LnLX%=)OMvuwqywNF(p|XtS z_aIV9Pu^*4X_cJa_5*7bB}OB@^4@V z%CPeg(I1YPs*_ z*W#szk7-Upn$*Vi{vQKN(W7N-=XotlPQw zAXU>@wa`tAG_CO|kQP^r!oDG~LDUJNV_s|suRFXp9Bo(nGLos>c0J4a7a!GiRf%am zHyY^&f(p<|i^RzCtosqkcgumye(eyT2lEDrp9~mK=PK=GYi+#AktCAXS=lD0hvAfE zLWx^AJh;ckV&0KE>XaanZHjtZkOm@wG|1ee*3Y4JOXmgGP%^ZZCW%sCR%ooNndt7R z9&-!kXhr#KaZ`cE)rq#GQPN!4bbrT4$qb#7g1ZH|EaK3j0^lpKu0C|c$58FD2{4`* zYEv=JDx^f*hGP8gq=jReMN%X_xL|d;k!GlcVs?2Y(J2~K>o!<;-t1Osa~>P2v=p3@ zA24=U4~#N{oOGnCS&|E&_5lW(a0!q{RaW?On}ioAS`MJP=C8 zz5C)dfCu~z2*q6HJ!8oIRF1(AG+HpRnK7I#szggi@cMX9m~07sFVyG)%DG&&T}<0S zf(E4--X19EX{&>&Ukm1B=x#;FX>yqmA$pr-C$HBSHYuU-IQTIFBI1SrlP$l?cU=4O z@(cQRAsq33s~HakffrC@SW^sq?6>^M@cGE6$4ml7-9=C)ZhaHGW&Qd=1B21eJf>kC z#`*XA)Nk0vM#}!lb`|>9Yv%ly%n==RRJdpW@kr_V_UJ>JfxOQz-ZMJ$)-|dO!*QsX-5ZtJ;-L<2hzT6GC^0<(;POF4C$S|T)3%{heZh@{v-}nCEt?$4Dg-X=*yBs` zy)&LU`#BMzMhSy?@TNaF!a4Iz5RYt)eq(}dYftb2LkE{9ehl8w@E8Vsf)1kd$H=$C zbq{pJtWX!O_y}x9Q*L|()v>4eN9NctuzB!wnPBj4i+VlcIm1X=6Najsa3MfzX@1}R z0Yn&rlE2QMphvX*Cgkv}E0X@*^GfrJ;9o9gbExBNS4j#!>@!k%H`gSWWX3&dKx7ax zMYRl9Gq#&y;7|xHvQrE4mox5W$Rd0{=W}W4ouh^NhyQvF9<2+`Pgr{|_&1jkE<&IQ z&Doc9MJjwDw0K$dh3|N!^qt88RFtcQpUr+9<=-`hY8DD3{1|62U&gVN!$1F3UnsV& z1eB@yH{~|WCOSMwvq*M3W|lnQgeH+xnj3Z~Y*iv)N#@C+ zexBzllYK3<9Dbij0JE>Xv-YY|!3yN4J2Hq&V`F#ku#LX!)tdE_)F;{wUE*}Go)#KMLXE({ zUu=T$H7zcY{e$pANPRklqm~>O$KK)}AYGXu#fZ8BVI{DCnW4o$Wt}o6hTyND9Dd~W zP0YiPnt^65m;T6+>*uA6ApGRJqGokE-qFStN`sORG)d+C`FzAz?|tra_!FaEC62{i zs18@*+tr`e-^WG=k)>*pjqlR#M6@j_urJ{a{ZnG}4~0ib+1eLOJY(PND`~FKtXh(9 zT>yBB#G~WP0Y9HUXhleiINDztwBjdeKs=1Le_L~I9V-6PhN!E^wDo;Gcn=D@sB01+ zSc%KDsC=OP^V+C_p)!XKd#^~hi-m*VEi)C5HH%aZ=VUrQ*4+Gv z`+e8V%}CXKg@7yVV|xXUi}6_Pw0A)6(Ssa$zoBI0mB0G3bh|TDo$Hr;m%MTuUe(MSNDFLhj#leTM2N4XisJ|ssP*+ zF7D@)YLh?6b@%C;oGw3_UsAIB97H?!BtYexhq*yXlI!t>+z1pDW_z4^U{wJ~&Rn

rb;s^{yn@mqbQ3iwp0aIdJPeI|P(zQ9bM>05g3@$z>3UGKm~8Q$>Fb zqZUn*Y-eRl;ltk-o1vn~8Biwe)Bb@;#A(;?i1oT#9QzA6HirJ{nU&3ly8S&i)CcgQ z*{!v)2NwXO=)=hDnY^0EL0P9?!U`JZ`4&HzW+W&BK2X@>DHE62>KKjqi&`K{7AE-$ zHfD_phO3k@u$*#)7ValGML3{f+nBC0_*(F9;gy7X;xHd{y`d$6w98EBcEzx*1M~Y`+%bPI#dHFNn{!ij%m6CwBUNMC6MhO^A6svm``s%!9^GH*@B)Z8gMt-fQ zpnX{MXMcb~F3fX)ni00&mW>ZV=P_f61@+=*^%I)tfV#CmbfpZ8q02P9XDlS0Fh)ug zx3P_%!MHr)g#P%Hn%eM+KhnwmN7^g?ETyAzE$O5E9zTN5`Q2SVdD~}R$Zk$c*#1yXM1{?e?}XP{dA%0kyw!>C+Mc zpkdvi<;?fNAHYwTuj;EKY-woM#BX1F`SxnPPS!VW-n~1ne_o*#_$wWn8*|>FNC^A{ zp>Ef<#ACzBc)V$Av4r05>oDR;djfj}NS0NerecVz26-};-7Y_t+wB?_@VDUSI8&o{ zXkMa#zSbDA{-=ns$!O+RqPFXP=MkaaBj~X6m3G0fXQhrtUs#5`S>?oI2b(ik5pPXH zoxfL})OgEDopAM5m-4=DzIG0`|8eHmrpiY zHwTpw+=gPirP?-gD3x_kTHO3bFY9q1K?Tak4@F(qitBfr?tw*?UD84?o)ToYCTfu zfiLf@ypyIcOUt8qYlLM@oc-+_)VZac@6O0KdrnU;5yu`!(O?(DV`H2)QbmZ?t0VD% zC>HGUH9HX!KX5ts&h|&Uw~}*%tXw5lqj3Z4*am8q2h&b5BPD7s+n?7~_(I8b3Gw|#dC zK2-sOY(T1&>~1HAzV>Ht>_^4hfL%h2Sasf~xZ}yC>l0dJ(_?cM$&=&vBXd}3^}tKk z#QKV3@y7S`9+PQtN!iok$L2E@INvEhrP-^Nc=*Jj?fvemmbbD%q+~Mn7QofU^6FU;HyU{=%5=9Hg5vFCl8*F9cD=<#plhI z(Y#@Zb|XV5p0l*!B>1a>O?O_)o2?|JKP$vKJknmN-kJCU)1uI1_W~lIsu>tJRCiSn zyHTd988KQt7ZtnfQ+{44!oj5biPYnMl^Rt$qPkszw-pRgNI7PH&p%o+(DWLw%a6_&nAJ&p+s_z>B`Suk`H8)P=RuY1> zar?F2rLsU&1M(sr`d_{hh{6kG!ucj(oAVfje@!>yE>N0;8O+1onhA7%4FBUXqyQo) z^fLJvRh%O^#sx}|3Ej!Ar~1(mODP~(-Ix9ei0;gHreFc;+UDrJ0^t+Na!IRHhd|Vse1Lt z-+^Bxb-wuM+P%ydRQX%;Jqg^|83?88-XmfxZ`{|~BB{PhTBw8~SC27@CE--e`b~DN z%AxBvtxhT?oa}5#KB{J2q)D6BP`Bqw0HR=<;llqXy)Zkxq3@I8P+5Q3=pT-*mXT(q I`u&&x2kwG4j{pDw literal 0 HcmV?d00001 diff --git a/public/postgresql_logo.png b/public/postgresql_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d3da8341842c9bc2d03d1587f16ced529281ffee GIT binary patch literal 21390 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 E0eA0%XaE2J literal 0 HcmV?d00001 diff --git a/public/sqlite_logo.png b/public/sqlite_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5743d827e947cad91649ff19a80a1ff06add1e08 GIT binary patch literal 13988 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 - - - - - - + + + + + + {appRoutes} + + + + + - - - - - ); } diff --git a/src/components/checkbox/database-checkbox.tsx b/src/components/checkbox/database-checkbox.tsx new file mode 100644 index 0000000..a577941 --- /dev/null +++ b/src/components/checkbox/database-checkbox.tsx @@ -0,0 +1,37 @@ +import { DatabaseType } from "@/lib/database" +import { Checkbox, Image } from "@heroui/react"; + + + + +interface DatabaseCheckboxProps { + database: DatabaseType, +} + +const DatabaseCheckbox: React.FC = ({ database }) => { + + return ( + +

+ +
+ + ) +} + + +export default DatabaseCheckbox; diff --git a/src/components/menu/menu-dropdown.tsx b/src/components/menu/menu-dropdown.tsx index 2d31d1a..99da743 100644 --- a/src/components/menu/menu-dropdown.tsx +++ b/src/components/menu/menu-dropdown.tsx @@ -60,7 +60,7 @@ const DropdownMenu: React.FC = ({ title, children, clickHand {child.title} { child.divide && -
+
} : @@ -68,7 +68,7 @@ const DropdownMenu: React.FC = ({ title, children, clickHand { child.divide && -
+
} diff --git a/src/components/menu/menu.tsx b/src/components/menu/menu.tsx index 81d6c40..9fa486f 100644 --- a/src/components/menu/menu.tsx +++ b/src/components/menu/menu.tsx @@ -1,8 +1,10 @@ import { useTheme } from "next-themes"; import DropdownMenu, { MenuDropdownProps } from "./menu-dropdown"; -import React, { useMemo } from "react"; +import React, { useMemo } from "react"; import { useTranslation } from "react-i18next"; +import { useModal } from "@/providers/modal-provider/modal-provider"; +import { Modals } from "@/providers/modal-provider/modal-contxet"; @@ -20,12 +22,23 @@ const Menu: React.FC = ({ }) => { const { setTheme } = useTheme() const { t } = useTranslation(); + const { open } = useModal(); const menu: MenuDropdownProps[] = useMemo(() => [ { title: t("menu.file"), children: [ - { title: t("menu.new") }, - { title: t("menu.open"), shortcut: "Ctnl + O" }, + { + title: t("menu.new"), + clickHandler: () => { + open(Modals.CREATE_DATABASE) + } + + }, + { + title: t("menu.open"), shortcut: "Ctnl + O", clickHandler: () => { + open(Modals.OPEN_DATABASE) + } + }, { title: t("menu.save"), shortcut: "Ctnl + S", divide: true }, { title: t("menu.import"), diff --git a/src/components/modal/modal.tsx b/src/components/modal/modal.tsx index de226ae..d02f177 100644 --- a/src/components/modal/modal.tsx +++ b/src/components/modal/modal.tsx @@ -1,5 +1,5 @@ -import React, { ReactNode } from "react"; +import React, { ReactNode, useState } from "react"; import { Modal as HeroUiModal, ModalContent, @@ -20,20 +20,24 @@ export interface ModalProps { title: string, children: ReactNode, actionName?: string, - actionHandler?: () => void , - isDisabled? : boolean + actionHandler?: () => void, + isDisabled?: boolean, + header?: string } -const Modal: React.FC = ({ isOpen, onOpenChange, className, backdrop = "opaque", title, children, actionName = "Action" , actionHandler , isDisabled}) => { +const Modal: React.FC = ({ isOpen, onOpenChange, className, backdrop = "opaque", title, children, actionName = "Action", header, actionHandler, isDisabled }) => { const targetRef = React.useRef(null); const { moveProps } = useDraggable({ targetRef, canOverflow: true, isDisabled: !isOpen }); + const [isLoading , setIsLoading] = useState( false ) ; - const {t} = useTranslation() ; + const { t } = useTranslation(); - const handleAction = (onClose: () => void) => { - actionHandler && actionHandler() ; + const handleAction = async (onClose: () => void) => { + setIsLoading( true) + actionHandler && await actionHandler(); + setIsLoading( false) ; onClose() } return ( @@ -44,13 +48,20 @@ const Modal: React.FC = ({ isOpen, onOpenChange, className, backdrop className={className} backdrop={backdrop} radius="sm" - + > {(onClose) => ( <> - + + {title} + { + header && +

+ {header} +

+ }
{children} @@ -58,9 +69,9 @@ const Modal: React.FC = ({ isOpen, onOpenChange, className, backdrop
-
diff --git a/src/hooks/use-table-to-node.tsx b/src/hooks/use-table-to-node.tsx index 98f19e5..32c1287 100644 --- a/src/hooks/use-table-to-node.tsx +++ b/src/hooks/use-table-to-node.tsx @@ -3,10 +3,11 @@ import { TableType } from "@/lib/schemas/table-schema"; import { Node, useReactFlow } from "@xyflow/react"; import { useEffect } from "react"; import { getDefaultTableOverlapping } from "@/utils/tables"; -import hash from "object-hash"; -import { compare } from "fast-json-patch"; +import hash from "object-hash"; + + export const useTableToNode = (tables: TableType[]): void => { - const { setNodes } = useReactFlow(); + const { setNodes } = useReactFlow(); useEffect(() => { @@ -38,19 +39,10 @@ export const useTableToNode = (tables: TableType[]): void => { if (!node) return tableNode; else { - // const diff = compare(node.data.table as TableType, tableNode.data.table as TableType); - - - //console.log (diff) - + const hashNode: string = hash(node.data.table as TableType); const hashTableNode: string = hash(tableNode.data.table as TableType); - - - return hashNode == hashTableNode ? node : tableNode; - - } }) diff --git a/src/i18/index.ts b/src/i18/index.ts index 12a3e09..0e78df9 100644 --- a/src/i18/index.ts +++ b/src/i18/index.ts @@ -28,7 +28,7 @@ i18n.use(LanguageDetector) .use(initReactI18next) .init({ resources , - lng: 'en', + lng: 'fr', interpolation: { escapeValue: false, diff --git a/src/i18/languages/en.ts b/src/i18/languages/en.ts index 75fa55f..7fe057a 100644 --- a/src/i18/languages/en.ts +++ b/src/i18/languages/en.ts @@ -8,10 +8,7 @@ export const en = { tables: "Tables", relationships: "Relationships" }, - modal: { - close: "Close", - create: "Create" - }, + color_picker: { default_color: "Default color" }, @@ -117,8 +114,23 @@ export const en = { dark: "Dark", help: "Help", show_docs: "Show Docs", - join_discord: "Join Discord" + join_discord: "Join Discord", + + }, + + modals: { + close: "Close", + create: "Create" , + pick_database: "Pick your Database.", + create_database_header: "Every database offers distinct features and functionalities." , + db_name : "Database name" , + db_name_error : "Please provide a Database name" , + continue : "Continue" , + open : "Open" , + open_database : "Open Database" , + open_database_header : "Open a database by selecting one from the list." } + } } diff --git a/src/lib/database.ts b/src/lib/database.ts new file mode 100644 index 0000000..fcaed01 --- /dev/null +++ b/src/lib/database.ts @@ -0,0 +1,32 @@ + + + + +export interface DatabaseType { + name: string, + dialect: string; + logo: string +} + +export const DBTypes: DatabaseType[] = [ + { + name: "PostgreSql", + dialect: "postgres", + logo: "/postgresql_logo.png" + }, { + name: "Mysql", + dialect: "mysql", + logo: "/mysql_logo.png" + }, + { + name: "Sqlite", + dialect: "sqlite", + logo: "/sqlite_logo.png" + }, +] + + +export const getDatabaseByDialect = (dialect: string): DatabaseType => { + const dbType : DatabaseType | undefined = DBTypes.find((dbType : DatabaseType) => dbType.dialect == dialect) ; + return dbType ? dbType : DBTypes[0] ; +} \ No newline at end of file diff --git a/src/lib/schemas/database-schema.ts b/src/lib/schemas/database-schema.ts index 2d4f0f8..3ad9238 100644 --- a/src/lib/schemas/database-schema.ts +++ b/src/lib/schemas/database-schema.ts @@ -1,5 +1,5 @@ import { InferInsertModel, InferSelectModel, relations } from 'drizzle-orm'; -import { sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'; import { tables, TableType } from './table-schema'; import { relationships, RelationshipType } from './relationship-schema'; @@ -10,6 +10,13 @@ export const databases = sqliteTable('databases', { .notNull() .unique(), name: text('name').notNull(), + dialect: text("dialect", { + enum: ["postgres", "mysql", "sqlite"], + }).notNull().default("postgres"), + + numOfTables: integer("numOfTables") + .notNull() + .default(0), createdAt: text('createdAt'), }); @@ -20,7 +27,7 @@ export const databaseRelations = relations(databases, ({ many }) => ({ export interface DatabaseType extends InferSelectModel { - tables : TableType[] , - relationships : RelationshipType[] - }; + tables: TableType[], + relationships: RelationshipType[] +}; export interface DatabaseInsertType extends InferInsertModel { }; \ No newline at end of file diff --git a/src/pages/database/database-page.tsx b/src/pages/database/database-page.tsx index ffd6dd4..297d498 100644 --- a/src/pages/database/database-page.tsx +++ b/src/pages/database/database-page.tsx @@ -40,6 +40,7 @@ import { useTheme } from "next-themes"; const DatabasePage: React.FC = () => { + const { t } = useTranslation(); const { resolvedTheme } = useTheme(); // Extract database state and operations @@ -102,7 +103,7 @@ const DatabasePage: React.FC = () => { ) as NodePositionChange[]; const nodeRemoveChanges: NodeRemoveChange[] = changes.filter((change: NodeChange) => change.type == "remove"); - + // Save new positions to the database if (nodePositionChanges.length > 0) await updateTablePositions(nodePositionChanges.map((change: NodePositionChange) => ({ @@ -112,9 +113,10 @@ const DatabasePage: React.FC = () => { } as TableInsertType))); // Delete tables if removed - if (nodeRemoveChanges.length > 0) + if (nodeRemoveChanges.length > 0) { + deleteMultiTables(nodeRemoveChanges.map((change: NodeRemoveChange) => change.id)); - + } return onNodesChange(changes); }, [onNodesChange]); @@ -142,9 +144,9 @@ const DatabasePage: React.FC = () => { // Automatically reposition tables to avoid overlap and fit the view const adjustPositions = useCallback(async () => { - const adjustedTables = await adjustTablesPositions(nodes, relationships); - console.log (adjustedTables ) - updateTablePositions(adjustedTables) + const adjustedTables = await adjustTablesPositions(nodes, relationships); + + updateTablePositions(adjustedTables) setTimeout(() => { fitView({ duration: 500 @@ -152,86 +154,89 @@ const DatabasePage: React.FC = () => { }, 300); }, [relationships, nodes]); + // Convert tables and relationships into flow elements useTableToNode(tables); useRelationshipToEdge(relationships); useHighlightedEdges(nodes, relationships, edges); const { isOverlapping, puls } = useOverlappingTables(tables); - - - + + return (
-
- + { - + - - + - - -
- { - isOverlapping && - - - - - - - - {t("table.overlapping_tables")} - - - } + + + + + +
+ { + isOverlapping && + + + + + + + + {t("table.overlapping_tables")} + + + } +
-
+ } diff --git a/src/pages/database/db-controller/relationship-controller/relationship-controller.tsx b/src/pages/database/db-controller/relationship-controller/relationship-controller.tsx index 1edde47..ed1929e 100644 --- a/src/pages/database/db-controller/relationship-controller/relationship-controller.tsx +++ b/src/pages/database/db-controller/relationship-controller/relationship-controller.tsx @@ -5,28 +5,27 @@ import { Ref, useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import RelationshipAccordionHeader from "./relationship-accordion-item/relationship-accordion-header"; import RelationshipAccordionBody from "./relationship-accordion-item/relationship-accordion-body"; -import Modal from "@/components/modal/modal"; -import CreateRelationshipForm from "./create-relationship-form/create-relationship-form"; + +import CreateRelationshipForm from "../../modals/create-relationship-modal"; import { RelationshipInsertType, RelationshipType } from "@/lib/schemas/relationship-schema"; import { useDatabase, useDatabaseOperations } from "@/providers/database-provider/database-provider"; -import { v4 } from "uuid"; + import { useDiagram } from "@/providers/diagram-provider/diagram-provider"; import { getDefaultRelationshipName } from "@/hooks/use-relationship-name"; +import { useModal } from "@/providers/modal-provider/modal-provider"; +import { Modals } from "@/providers/modal-provider/modal-contxet"; -interface Props { - -} + -const RelationshipController: React.FC = ({ }) => { - const [relationship, setRelationship] = useState(undefined); - const [isValid, setIsValid] = useState(false); - const { isOpen, onOpen, onOpenChange } = useDisclosure(); +const RelationshipController: React.FC = ({ }) => { + + + const { open } = useModal(); const { database } = useDatabase(); - const { createRelationship } = useDatabaseOperations(); const { relationships: allRelationships } = database; const [relationships, setRelationships] = useState(allRelationships); @@ -37,18 +36,6 @@ const RelationshipController: React.FC = ({ }) => { useEffect(() => setRelationships(allRelationships), [allRelationships]); - const addRelationship = useCallback(() => { - - const newRelationshipId: string = v4(); - - createRelationship({ - id: newRelationshipId, - ...relationship, - } as RelationshipInsertType); - - setSelectedRelationship(new Set([newRelationshipId]) as any); - }, [relationship]); - useEffect(() => { if (focusedRelationshipId) { @@ -57,6 +44,11 @@ const RelationshipController: React.FC = ({ }) => { }, [focusedRelationshipId]); + const onOpen = useCallback(() => { + open(Modals.CREATE_RELATIONSHIP, { + onRlationshipCreated: (id: string) => setSelectedRelationship(new Set([id]) as any) + }) + }, []) const searchRelationships = useCallback(() => { const keyword: string | undefined = nameRef.current?.value; @@ -164,21 +156,7 @@ const RelationshipController: React.FC = ({ }) => { ))}
- - - ) } diff --git a/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-item.tsx b/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-item.tsx index 9932344..443c693 100644 --- a/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-item.tsx +++ b/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-item.tsx @@ -147,12 +147,12 @@ const FieldItem: React.FC = ({ field }) => { - +

{t("db_controller.field_setting")}

-
+
{t("db_controller.unique")} @@ -174,11 +174,11 @@ const FieldItem: React.FC = ({ field }) => { onValueChange={setNote} onBlur={updateFieldNote} classNames={{ - inputWrapper: "bg-default border-divider dark:bg-background-100", + inputWrapper: "bg-default border-divider ", base: "max-w-xs", input: "resize-y min-h-[60px] max-h-[180px]", }} /> -
+
+ +
+ +
+ + + ) +} + + + + diff --git a/src/pages/database/db-controller/relationship-controller/create-relationship-form/create-relationship-form.tsx b/src/pages/database/modals/create-relationship-modal.tsx similarity index 66% rename from src/pages/database/db-controller/relationship-controller/create-relationship-form/create-relationship-form.tsx rename to src/pages/database/modals/create-relationship-modal.tsx index 1cb2146..0df9f6a 100644 --- a/src/pages/database/db-controller/relationship-controller/create-relationship-form/create-relationship-form.tsx +++ b/src/pages/database/modals/create-relationship-modal.tsx @@ -1,23 +1,27 @@ import Autocomplete from "@/components/auto-complete/auto-complete"; +import Modal, { ModalProps } from "@/components/modal/modal"; import { FieldType } from "@/lib/schemas/field-schema"; import { RelationshipInsertType } from "@/lib/schemas/relationship-schema"; import { TableType } from "@/lib/schemas/table-schema"; -import { useDatabase } from "@/providers/database-provider/database-provider"; -import { FileKey, FileMinus2, FileOutput, KeyRound } from "lucide-react"; -import { useEffect, useMemo, useState } from "react"; +import { useDatabase, useDatabaseOperations } from "@/providers/database-provider/database-provider"; +import { FileKey, FileMinus2, FileOutput, KeyRound } from "lucide-react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; +import { v4 } from "uuid"; -interface CreateRelationshipFormProps { - onRelationshipChanges: (relationship: RelationshipInsertType) => void, - onValidationChanges?: (isValid: boolean) => void +interface CreateRelationshipModalProps extends ModalProps { + onRelationshipChanges?: (relationship: RelationshipInsertType) => void, + onRlationshipCreated?: (id: string) => void } -const CreateRelationshipForm: React.FC = ({ onRelationshipChanges, onValidationChanges }) => { +const CreateRelationshipModal: React.FC = ({ onRelationshipChanges, isOpen, onOpenChange, onRlationshipCreated }) => { + const [isValid, setIsValid] = useState(false); + const { createRelationship } = useDatabaseOperations(); const [relationship, setRelationship] = useState({ sourceTableId: "", @@ -62,15 +66,39 @@ const CreateRelationshipForm: React.FC = ({ onRelat }, [relationship, sourceFields, targetFields]); useEffect(() => { - onValidationChanges && onValidationChanges((relationship.sourceFieldId && relationship.targetFieldId && fieldTypesMatches) as boolean) - }, [relationship, fieldTypesMatches]) + setIsValid((relationship.sourceFieldId && relationship.targetFieldId && fieldTypesMatches) as boolean) + }, [relationship, fieldTypesMatches]); + + + const addRelationship = useCallback(() => { + + const id: string = v4(); + + createRelationship({ + ...relationship, + id, + } as RelationshipInsertType); + onRlationshipCreated && onRlationshipCreated(id); + + }, [relationship]); + return ( - <> + +
-
-
-
-
} - + + ) } -export default CreateRelationshipForm; \ No newline at end of file +export default CreateRelationshipModal; \ No newline at end of file diff --git a/src/pages/database/modals/open-database-modal.tsx b/src/pages/database/modals/open-database-modal.tsx new file mode 100644 index 0000000..4aa4216 --- /dev/null +++ b/src/pages/database/modals/open-database-modal.tsx @@ -0,0 +1,84 @@ +import Modal, { ModalProps } from "@/components/modal/modal"; +import { getDatabaseByDialect } from "@/lib/database"; +import { DatabaseType } from "@/lib/schemas/database-schema"; + +import { useDatabase, useDatabaseOperations } from "@/providers/database-provider/database-provider"; + +import { Image, Selection, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from "@heroui/react"; +import { Key, useState } from "react"; +import { useTranslation } from "react-i18next"; + + + + + +const OpenDatabaseModal: React.FC = ({ isOpen, onOpenChange }) => { + + const { t } = useTranslation(); + const { databases } = useDatabase(); + const { setCurrentDatabaseId } = useDatabaseOperations(); + const [selectedDatabase, setSelectedDatabase] = useState(undefined); + const openDatabase = () => { + setCurrentDatabaseId( selectedDatabase.currentKey) + } + + + + return ( + + + + Dialect + Name + Created at + Tables + + + { + databases.map((database: DatabaseType) => ( + + + + + {database.name} + { + new Date(database.createdAt as string).toLocaleString("en-US") + } + {database.numOfTables} + + )) + } + + + +
+ +
+ ) +} + + +export default OpenDatabaseModal; \ No newline at end of file diff --git a/src/providers/database-history/database-history-provider.tsx b/src/providers/database-history/database-history-provider.tsx index 2bc2bb9..ade19b3 100644 --- a/src/providers/database-history/database-history-provider.tsx +++ b/src/providers/database-history/database-history-provider.tsx @@ -42,10 +42,13 @@ const DatabaseHistoryProvider: React.FC = ({ children }) => { }, [redoChanges, udpateDbFlag, isProcessing]); useEffect(() => { + if (!udpateDbFlag.current) { udpateDbFlag.current = true; return; } + + const normalizedDatabase = normalizeDatabase(database); const normalizedPresent = normalizeDatabase(datatbaseState.present); diff --git a/src/providers/database-provider/database-context.ts b/src/providers/database-provider/database-context.ts index e59941c..ed1fa3c 100644 --- a/src/providers/database-provider/database-context.ts +++ b/src/providers/database-provider/database-context.ts @@ -1,6 +1,6 @@ import { DataType } from "@/lib/schemas/data-type-schema"; -import { DatabaseType } from "@/lib/schemas/database-schema"; +import { DatabaseInsertType, DatabaseType } from "@/lib/schemas/database-schema"; import { FieldInsertType, FieldType } from "@/lib/schemas/field-schema"; import { IndexInsertType } from "@/lib/schemas/index-schema"; import { RelationshipInsertType } from "@/lib/schemas/relationship-schema"; @@ -15,6 +15,7 @@ import { createContext } from "react"; interface DatabaseDataContextType { database: DatabaseType, + databases : DatabaseType[] , isLoading: boolean, getField: (tableId: string, id: string) => FieldType | undefined, @@ -23,6 +24,11 @@ interface DatabaseDataContextType { interface DatabaseOperationsContextType { data_types: DataType[], + // database operations + createDatabase: (database: DatabaseInsertType) => Promise, + editDatabase: (database: DatabaseInsertType) => Promise, + deleteDatabase : ( id : string) => Promise , + setCurrentDatabaseId : ( id : string) => void , createTable: (table: TableInsertType) => Promise, editTable: (table: TableInsertType) => Promise, @@ -36,10 +42,10 @@ interface DatabaseOperationsContextType { orderTableFields: (fields: FieldType[]) => Promise, // index operations - createIndex : ( index : IndexInsertType) => Promise , - editIndex : ( index : IndexInsertType) => Promise , - deleteIndex : ( id : string) => Promise , - editFieldIndices:( indexId : string , fieldIds : string[])=> Promise , + createIndex: (index: IndexInsertType) => Promise, + editIndex: (index: IndexInsertType) => Promise, + deleteIndex: (id: string) => Promise, + editFieldIndices: (indexId: string, fieldIds: string[]) => Promise, // relationship operations createRelationship: (relationship: RelationshipInsertType) => Promise, editRelationship: (relationship: RelationshipInsertType) => Promise, diff --git a/src/providers/database-provider/database-provider.tsx b/src/providers/database-provider/database-provider.tsx index abeac83..7f9055e 100644 --- a/src/providers/database-provider/database-provider.tsx +++ b/src/providers/database-provider/database-provider.tsx @@ -10,7 +10,7 @@ import { FieldInsertType, fields, FieldType } from "@/lib/schemas/field-schema"; import { RelationshipInsertType, relationships } from "@/lib/schemas/relationship-schema"; import DatabaseHistoryProvider from "../database-history/database-history-provider"; import { DBDiffOperation } from "@/utils/database"; -import { DatabaseType } from "@/lib/schemas/database-schema"; +import { DatabaseInsertType, DatabaseType, databases as databaseModel } from "@/lib/schemas/database-schema"; import { getTimestamp } from "@/utils/utils"; import { IndexInsertType, indices } from "@/lib/schemas/index-schema"; import { field_indices } from "@/lib/schemas/field_index-schema"; @@ -23,19 +23,19 @@ const DatabaseProvider: React.FC = ({ children }) => { const [currentDatabaseId, setCurrentDatabaseId] = useState(undefined); // Fetch all databases - const { data: databases, isLoading: loadingDatabases } = useQuery(toCompilableQuery( + const { data: databases, isLoading: loadingDatabases , isFetching : fetchingDatabases} = useQuery(toCompilableQuery( db.query.databases.findMany() )); // Fetch all data types - const { data: data_types, isLoading: loadingDataTypes } = useQuery(toCompilableQuery( + const { data: data_types, isLoading: loadingDataTypes , isFetching : fetchingDataTypes} = useQuery(toCompilableQuery( db.query.data_types.findMany() )); // Fetch the current database with nested tables, fields, and relationships - let { data: database, isLoading: loadingCurrentDatabase } = useQuery( + let { data: database, isLoading: loadingCurrentDatabase , isFetching : fetchingCurrentDatabase} = useQuery( toCompilableQuery( - db.query.databases.findFirst({ + db.query.databases.findMany({ where: (databases, { eq }) => eq(databases.id, currentDatabaseId as string), with: { tables: { @@ -68,9 +68,9 @@ const DatabaseProvider: React.FC = ({ children }) => { }) ) ); - - - + + + // Normalize result to single object if (database.length == 1) database = database[0] as any; @@ -82,7 +82,22 @@ const DatabaseProvider: React.FC = ({ children }) => { } }, [currentDatabaseId, databases]) - const isLoading: boolean = loadingDataTypes || loadingDatabases || loadingCurrentDatabase; + const isLoading: boolean = loadingDataTypes || loadingDatabases || loadingCurrentDatabase ; + // CRUD for database + const createDatabase = useCallback(async (database: DatabaseInsertType): Promise => { + return await db.insert(databaseModel).values({ + ...database, + createdAt: getTimestamp() + }); + }, [db]); + + const editDatabase = useCallback(async (database: DatabaseInsertType): Promise => { + return await db.update(databaseModel).set(database).where(eq(databaseModel.id, database.id)) + }, [db]); + + const deleteDatabase = useCallback(async (id: string): Promise => { + await db.delete(databaseModel).where(eq(databaseModel.id, id)); + }, [db]); // CRUD operations for Tables const createTable = useCallback(async (table: TableInsertType): Promise => { @@ -219,6 +234,7 @@ const DatabaseProvider: React.FC = ({ children }) => { // Apply a list of diff operations to sync database const executeDbDiffOps = useCallback(async (operations: DBDiffOperation[]) => { try { + console.log ("excuted diff operations") await db.transaction(async (tx) => { for (const operation of operations) { if (operation.type == "CREATE_TABLE") { @@ -253,9 +269,9 @@ const DatabaseProvider: React.FC = ({ children }) => { } else if (operation.type == "CREATE_INDEX") { await tx.insert(indices).values(operation.index); - if (operation.index.fieldIndices && Object.values(operation.index.fieldIndices).length > 0) - await tx.insert(field_indices).values(Object.values(operation.index.fieldIndices)); - + if (operation.index.fieldIndices && Object.values(operation.index.fieldIndices).length > 0) + await tx.insert(field_indices).values(Object.values(operation.index.fieldIndices)); + } else if (operation.type == "DELETE_INDEX") { await tx.delete(indices).where(eq(indices.id, operation.indexId)); @@ -279,6 +295,11 @@ const DatabaseProvider: React.FC = ({ children }) => { }, [db]); const databaseOpsValue = useMemo(() => ({ + + createDatabase, + editDatabase, + deleteDatabase, + setCurrentDatabaseId, createTable, editTable, deleteTable, @@ -299,6 +320,9 @@ const DatabaseProvider: React.FC = ({ children }) => { editFieldIndices, data_types }), [ + createDatabase, + editDatabase, + deleteDatabase, createTable, editTable, deleteTable, @@ -324,6 +348,7 @@ const DatabaseProvider: React.FC = ({ children }) => { diff --git a/src/providers/modal-provider/modal-contxet.tsx b/src/providers/modal-provider/modal-contxet.tsx index eb6f717..7604c7f 100644 --- a/src/providers/modal-provider/modal-contxet.tsx +++ b/src/providers/modal-provider/modal-contxet.tsx @@ -1,18 +1,14 @@ -import { createContext } from "react"; +import { createContext } from "react"; export enum Modals { - CREATE_RELATIONSHIP = "CREATE_RELATIONSHIP" + CREATE_RELATIONSHIP = "CREATE_RELATIONSHIP", + CREATE_DATABASE = "CREATE_DATABASE" , + OPEN_DATABASE = "OPEN_DATABASE" } - - - - - interface ModalContextType { - focusedTableId: string | undefined; - focusedRelationshipId: string | undefined; + open: (modal: Modals, props?: any) => void } export const ModalContext = createContext({} as ModalContextType); diff --git a/src/providers/modal-provider/modal-provider.tsx b/src/providers/modal-provider/modal-provider.tsx index 4e0211c..136ea2c 100644 --- a/src/providers/modal-provider/modal-provider.tsx +++ b/src/providers/modal-provider/modal-provider.tsx @@ -1,35 +1,56 @@ -/* + import { useCallback, useContext, useState } from "react" import { ModalContext, Modals } from "./modal-contxet" -import Modal, { ModalProps } from "@/components/modal/modal"; import { useDisclosure } from "@heroui/react"; +import CreateRelationshipModal from "@/pages/database/modals/create-relationship-modal"; +import { CreateDatabaseModal } from "@/pages/database/modals/create-database-modal"; +import OpenDatabaseModal from "@/pages/database/modals/open-database-modal"; + interface Props { children: React.ReactNode } interface CurrentModalProps { modal: Modals, - modalProps: ModalProps, - content?: any + props?: any, + } -const ModalProvider: React.FC = ({ children }) => { - const { isOpen, onOpen } = useDisclosure(); +export const ModalProvider: React.FC = ({ children }) => { + const [currentModal, setCurrentModal] = useState(undefined); + const { isOpen, onOpen, onClose } = useDisclosure(); + const open = useCallback((modal: Modals, props?: any) => { - const onOpenChange = useCallback(() => { + setCurrentModal({ + modal, + props + }); onOpen(); - setCurrentModal(undefined); - }, [onOpen]); + }, []); - + const onOpenChange = (isOpen: boolean) => { + if (!isOpen) { + onClose(); + setCurrentModal(undefined); + } + } return ( - + { - currentModal && - - - + currentModal ? ( + currentModal.modal == Modals.CREATE_RELATIONSHIP && + + || + currentModal.modal == Modals.CREATE_DATABASE && + + || + currentModal.modal == Modals.OPEN_DATABASE && + + ) : undefined } {children} @@ -37,6 +58,5 @@ const ModalProvider: React.FC = ({ children }) => { } -export const useModal = () => useContext(ModalContext); +export const useModal = () => useContext(ModalContext); -*/ \ No newline at end of file diff --git a/src/styles/globals.css b/src/styles/globals.css index 0a60d5b..b4493d9 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -129,4 +129,9 @@ svg.text-icon:hover { .dark .bg-background-50 { background-color: red !important; +} + + +.database-checkbox[data-selected="true"]{ + border : 1.5px solid hsl(var(--heroui-primary-300)); } \ No newline at end of file diff --git a/src/utils/utils.ts b/src/utils/utils.ts index e062430..1840d3c 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -58,5 +58,6 @@ function excludeFields( export { areArraysEqual, getTimestamp, - excludeFields + excludeFields , + } \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index c1f68b5..5df8e31 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -22,7 +22,7 @@ export default { }, themes: { - + dark: { colors: { @@ -39,8 +39,9 @@ export default { DEFAULT: "#cecfd2" }, content1: { - DEFAULT: "#282d34" + DEFAULT: "#1c2026" }, + default: { 50: '#f2f3f5', 100: '#d6d7db',