From 9a401293dd22d2702babaaebd6fcf833e72d716e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Volf?= Date: Wed, 28 Jan 2026 19:36:00 +0100 Subject: [PATCH] feat: UI overhaul --- assets/favicon.ico | Bin 110218 -> 11885 bytes assets/images/icon.png | Bin 22434 -> 11897 bytes assets/images/icon.svg | 203 ------------- assets/manifest.json | 4 +- assets/styles/input_range.css | 53 +--- assets/styles/select_arrow.css | 9 + src/components/app.rs | 5 +- src/components/bottom_panel.rs | 72 +---- src/components/button_primary.rs | 29 ++ src/components/button_secondary.rs | 29 ++ src/components/category_calendar_task_list.rs | 5 +- src/components/category_input.rs | 119 ++------ src/components/category_today_task_list.rs | 32 +- src/components/create_button.rs | 31 ++ src/components/error_boundary_message.rs | 1 + src/components/form_open_button.rs | 39 --- src/components/input.rs | 47 +++ src/components/input_label.rs | 21 ++ src/components/mod.rs | 9 +- src/components/navigation.rs | 35 ++- src/components/navigation_item.rs | 18 +- src/components/project_form.rs | 119 +++++--- src/components/project_list.rs | 18 +- src/components/project_select.rs | 4 +- src/components/reoccurrence_input.rs | 86 ------ src/components/reoccurrence_interval_input.rs | 44 +++ src/components/select_button.rs | 28 ++ src/components/subtasks_form.rs | 224 +++++++------- src/components/task_form.rs | 283 ++++++++---------- src/components/task_list.rs | 57 ++-- src/components/task_list_item.rs | 6 +- src/layouts/main.rs | 50 ---- src/layouts/mod.rs | 4 +- src/layouts/navigation.rs | 38 +++ src/layouts/suspense.rs | 28 ++ src/route/mod.rs | 49 +-- src/views/category_page.rs | 3 +- src/views/mod.rs | 2 + src/views/project_form_page.rs | 12 + src/views/task_form_page.rs | 12 + tailwind.css | 4 + 41 files changed, 838 insertions(+), 994 deletions(-) delete mode 100644 assets/images/icon.svg create mode 100644 assets/styles/select_arrow.css create mode 100644 src/components/button_primary.rs create mode 100644 src/components/button_secondary.rs create mode 100644 src/components/create_button.rs delete mode 100644 src/components/form_open_button.rs create mode 100644 src/components/input.rs create mode 100644 src/components/input_label.rs delete mode 100644 src/components/reoccurrence_input.rs create mode 100644 src/components/reoccurrence_interval_input.rs create mode 100644 src/components/select_button.rs delete mode 100644 src/layouts/main.rs create mode 100644 src/layouts/navigation.rs create mode 100644 src/layouts/suspense.rs create mode 100644 src/views/project_form_page.rs create mode 100644 src/views/task_form_page.rs diff --git a/assets/favicon.ico b/assets/favicon.ico index 070d1dae2042471cfdd81aea7665dea21ecbfa78..adc9155379f199ae0c8724827b8fb944b6dd82bf 100644 GIT binary patch literal 11885 zcmZvC1yqz#*X|58ATXdvH>jvIhz#AKv~+`X57Iq=gfxhBDG1UX4lU9!ppY~XE>Q3bmKj3SD=y$iqJWs=Sl^Xy6YTZ*Qaz(mP+1ceWsV z?b7X-Z|`6^n@l;TUGOMniL0ul)D`4^RU~;x`~Yv{WUM0D<~3GO=UW>e zyOjQCcQFEAq|}M!Cfh9HV?~2T+#A-DZ32NN?udKpnT1{(h6Hb0P3EIFsbods!-`xE zaq$goTdVIVfA_UX&0-Jy zE<%x~k6A79w?_k!;k?#tfIcp+vMyC*$?l(+4d>(2wZqM=kwLRR5;@S(5J5HmY077a zrfR4&h!8`s>tvUkPdG_ER^MzBpLfs+=;SyWk9=jdL7EZ_19ga>Yad4yUYRR5UktpZ z_2YNQiNxM~nOP{rLo8QuM!d|AmFVZTmonABT>ur!))vd|k6HAQn2iTli9cGxB_m$~ zk==FIZXI2szp^vBelynuetston@8gL&QE5YXW^JSSTM7z8ENI_v)^woc7wO;&c+b- zad)c{C?pPvA#q%Kja{Q{aAlE0W0KA4_pi^?0!!j3tE58ny%br$gCAg7WwhF;D_<{* z&oxN!vBuHzB%X=$0D>Uv%JJx!!h&1a$D8AuCF;R)`rsg>M9Xbw6Vmx(PFX;r_5?~F zmaMHkq@*LpvYkB>Q2Xb6>S}1I**9!&YN#Tslh^Cy^#p{HNx}SSbKr$!RBH@8|JYGi ztW0^=xJj$kdON9IuPb8lX6-uT!qidP?|2Gv;c6Uvu|7n*85}k}=5tzGq%NfWrGD3- z$kNF{jV0eni={5v#BbHKSpQ{ zRGjfL?9Jv!Lz2y-`u9$YHu2n&dfW8#behIA6sqJ|>l%Br^F^zU#z>Q2Za71$|G8JH zuDHFY#BuCAvg1owo0!fZz97d9d5!`jf5ER>0qyHYo5{Qa$AgO!ZX1Oh$0!lj(e0R| z?*uJWp8OX7Zd+GgCb&v=mFK}1+9i;!xc+I%)0qVI>hecsSMH~CwuT$t0Y2tEwq8P` z(;nsFmKk&N1$+cPf>*i{IrfS1Rj(}jvPQYUsmwkwu2RS#fisA^vWTUi&XH1kEoR)f zotr8ME|x{5ncX~Gth}d3IqTVsBcv(CDn@!Z^E785F;h@A`2h{MDh#uMtCVQ)jx#9D zWP6e?PQCSZu;F4}{aAC;n3H__@f8t^+-N6@(AadVq0tx@rfKaG?{wt;jmnx6?`NwG zSY(w^peAHfGV=?yTS>vd0*GTldI{r#7?K0NUt+bBGUa#Ma4|6{NPTnObm8wPof+U8 z?Y#xf7B}L{omgL*h_c-~e8TGX32hGJBX`@dGuonIV3BalaNPUFPFw{i!q3#|h5ZYP zZ}$Rp*jxDAEvY!Z=$D)2ke;f@?0p-W=hs~0fyJ~HF}r<69n(;?;mx$g!vfcdA;jRA z12M-Zzdk4&bNe%`h-K5MXN$}iD_@uKR1}cZ6kEDhPwV&XrnWj=Sl+h!w5Ufi;}+q& z^_Qtq0X}7>bcIiB{<-nZP@t(kPou=FO_lvl`RnLDsHLdO3{eprWi4OGjM6!TYllw zbUFLZ^tgTZWGZ@~Kr`#$%C#j|$dCNQu+`)2!^1Kc$>%+|y4S|h@1)^49y~G>fcF72 zw7(Tt)hoEbeG`BG=)B+0JL@koX%d&C4oPuJ7P8#C`Ru0b8ZRug;U3hpj^S9HL{I@m193#Qt~B9*ba< zSBFm@LIf~cJ*;cb=2F8q7WSA~%|o{xM`4DzVelt59XG^~1#lqq;eege-ovUvjfpUy z@kEagvt6Y&XUAOYOi9vL`odv%C+>Q#1aHTgmR4iI@e1zUFL?_Ck!NW7)pBf0n7;57 z(5Ko9qY7cMUZoJ%nGl^>AR`bciKOGhg^}#rXI2f0bx?mNaP~xb#4}cvjN!EvmN5O* zpks%6Gm;h^;I5PD#o!si`77Lab&h%U@KPYkn-Gjah@|rEpIf@z=3-DV*DEGL=)<0u zT18_ygC26pns>a@8$Zg;na|X*Do>r@u1eim@w%++`V~*3xH8}I8vEF{eA)}ER1FuC zN2ab825ty3dk0|?rxBN{+!{(!uigH=Tyu5QC8jxmII@%IAmDgaFUVj^KcTa=q|qcr zIe2m2+tm`_{jsH|ZafP_(@{%+9YG8kR-0*i@3&@4dn7dLPm5V-`)8zGmL9wI!TI-_ zGWCf#teoK|UBnP8B8Y7iUK?LV`go(P_$n=ER>D0ix-pPtL_@kL7{gy}^{^v~bNAHJ zS9Ch%Nqso;s~~W-W zr7xMXyJpQQ{D)ANe}nn&a(&sRfTK6Z^-8RxN7ULyt@hTMKJ3EHElMI@3hCZUN$$s1 z_yKA5_5PeUSpPRp@NNG64BD6>scE&KPTnf09T|Bx8@av6;5fxKQ*R}#z*P+e4GrDZ za8}u7G|o)a6zXt#>hE#-R!Eh?a{se4cj>-bay$L6DuZTo%5~$HvN7DZ9{#o7CZD8s z`rL}bUu+@QL)}W9l z`QGo@7%7zs_AiBu~{`dDZ+C*az1d|5ax3Z1&&BK((2HDuD#Kd zWiOu~apl7+BGYzCX?<4Wv(~5c9v}$(t_LZj;BeOB3_?*5BrMAEqf6k+d%i6;{Y>LA z@}E-z>>rg0+NPVW2ySG1*KNEuiuST}a{Zks7fZG5nr$kKm2_|-DqlBH9Hy1st)2Q- zgi;%G{5U9Cthx4qRtBr@GuEyHt6#F!J{xdVydZl^nN8_Ds$_lAlaHA#J`W^{^l>q| zwyV9hmuNt|hh5_3V6IDQ`G8YhL5g5!8n+Cx;AmEzMk96@KRqP;$Dl}E^RKB}zkVJc zf*a}Hbq~EzS$@AaI|!-`Y7M_6ZuehZM$fdmd)_CcFVjpp6mZ$L{%17zqzv#sM5oHK zGl|&fJ7~=&0l_i&onbQMRf6m(0uV&Od;>SRT`JuTi9FEZqRRr^6g`3m} z(f)-STIGdD%KjiELfO2MJWc>2&T+Q5cB~(epZ=>AHP@x!(t1^~AzV-#&|rQxq}%oF zS6Wiy0*x75Pq%yhz5SvcwuQ_8o_xXD%b0}qoY_oL(@;I zQSa%ik-aqu6upA|I^li%-gfk)%H;^bQdR+sf2bBlRe|XoM-Yq=JcBO>CtaHXyE5B( zfm5A9kC*m!l>K~Ujk#exLCxD8U1xzfd$Wz@qq6}PM|Xr&yI`pswTp?AE!kJJa^i-C zyl!$hJs7D2inVY1Q0&VUSzv?qRF0u_9Ki(Ha|k0ZG^duefNq|){MhQ?-Q{phQNNqt z^KI9zi*j9dI>UvqBfC+D4v`bs*Y3?n1!Mcoa2oJG2*5yMP6Bh#u~=3+-GgIWIeUZG zagnd}lp{k1V2H9nZa33MvReThPV_b_gp$~Ge?(u1J?Nx)f_-j%AfLxrX_@1ZDWZ%D z0$@5C#*35nU`=jk#}b}VoF%sM?qg=OXL8K{)hHSnq6hV%dA_ zbdA}4BHE=ADU003PuH3PbLDrZ@LNHf$9O;^RosN;2(~!9Dhb)1JE^qkhm-Pr=2v@3 zA{p(58I)kxhpg>wfKH@qK!9>9A4A$eZ{6;=7`z#IDjrq%Ix(AJqFU?p+w9L$rL%xVWe>QV{QNpFG)R zM$VQ-%`1z|C1tZU6gb+bd&IgI9t75&B&O6{v|F<)Q8S?tK-|ZA7Gjwj(C&h2JvpK* zhc|zvl0v>2Gl$jV)bOIcm74s{W;MAt<_9+WQn6C9qnVPdMzg%`81MHgoTdI)Eo=;b zwJ=r*8+GDgUDHArUnK{)KEm%nj0%C?Ow9i(m>|5Wtr@@lQ}$u(18Pjf4S9`KB_C*E z!(O~O^?7Upheyi2hgf&_)CGf)7ZYU#W6R>nTLTTJYB_@jV^M(_7n_F7WNoN(F&>RF z#gPwz`2wdYt)n|~YV@onRpeb<`3@w@vC(y}3_!X}>gHQYj9Jr0Sz(J4yuk{yd>HGB zRya^{rDn`uNKLVefNM!3KaH1H@9J3Q6V8+9Wh`*yi00LbqH^Yi!?`8BY0QF-&47wGYeZi@)$6Nc zOh7>$Nrky@&L|1)QisregaT}g7AHr@CKfXfxFVrGw{cl(1;uBBi|xY>P0irei8?m* zSQWCyYnf$!?Ia3{mq3Etd73nuuiFo!dD6RofT1&R*+ zHnEpwS?Q{KGs~MdDiD?P%Jxp)t?AJnW|5*+?XIgxFj_pFc(OwxaYGjtqHpLY9;P@U z@B)~+aIHQk$eQK0NyK*VxzJXxkle>r16|7~iCd)e=ZhWddJ)5iU9Fzt<96)mkO$t4 zxjNIxCN%TGoj6o~Hjlxz$JOt>bP;V^WwLAct=I9&O(4%o;rfjVdG~d>qmg4lNkRpa zx?d!bdCc}Ah-2Hc&9&{B!#f24sL^JkXTkR(ZyX$_c5<%oZO$v(wEtal_qIgk6P^pI z+)3Y3E_d?M@dHIGH-it%#G6?83QFYcf?JPt2Dv^Sq|7LE1JU}KZ5BP6v}an(*xlNd zMUp7SBz9d(Uro+@a}}wE!Vvf{fmaa_4Fd4X){fF$S;lo@q&YI{1!FBVhpUhr{=rCw<98$RBvkS|A2HNI z7uwXlr4ZEQN1G#lvli^3KA3%&`pLyUE}8C35w6$NL}*DY?Kx~oXfm8&LJ%1U3~7uW z64n?XJp|^$++Tw#s6BN7!S1Bni$Qx6%L0}$92aoQ=Kkd4O&?^CrJK!OgNGlExGCHL zcGMWPE`YG3Um1fC)swF!6gY9{+oA5%%tyH^jJD!|K$(w4w8{4?lV+{(3VwwD4nHQjnyio!Hu^H^>1Or54JeF}sI$3do z^$BqD1_H(8SWVs><;pt-M%*5X3g5;xGD7W;%0bo$R#>o*lMz~AhJ(hC$FLwTP7ea# zX`1^+L>7_J| z|JLy1qW1+~6pQwYt$9~ObtBvEui|!R5U|_uQc{MN#JOV-x_>X7c8OMd`dPJyUwWnq zQ)$2v7_|^{-*k^EkTp#N58LeGCZ3{&Tnq(9F)5zeEF!;uI z@B$3g0?OY?K<|pH4@i55t}Ve!#>-~pj?d@RnwvrV1^|B=;No!na0mUww3eJ{C?Use zu#CpH@?g{C9qHV?&HDCqY)r(yi7sdg6b84+hAGB`bsq4|QnXpT13#m!N%qq#{ig46 zzcC`LKyTY7Zye`KSkCh{MuTrpX1-P4upwZG{EiPqe-1&gmlv6h$;Fc!58adFK?xgz zxSN;_zCV`kJ1**+{(=MRp+&E!O=Zv45<~B$v!yoo>j5b5y(q*|BidDApHl|WFT}3A_#CB&-j{!R_~meF*{wf|$v2u)w6m{j| zf1Umi7|2d?8tWI+^_C~`-9Ev}_{L7NwTt_fBb6iI??tb-iYDtl$wE{!pS=~mU29IK(z~%1GLeJzErZPV{Vts$ta6{>$M{q&Q0|!Ja`Ckg)_J8;x z$N3oVp!A;4n{BdnAv2pOP>vKYkTWIv?HBM#fMkDbB?#%tRIbjdW!!tc~0FA&g7AS(5((DtAGs$n>xc=8<`YEX z&vq#4XT{W~kPv~F zD?;-BcJoH;!t~#zmPw&^{&CiRzvzA^?_zBzm>l1BS2Go=h`(@8+07M{DdCaf!lXFC zh~D3UBxq0$es}yZvI9{*&;F%X-<)E28zI#&x^puv*O`nWNtDic^p+P3@tuf%OrHYF zUqGzzMD$U6nm3^?~g_gFIN)S`D#hf?g9m$Jy219SG_N*n?!FZ54ko(ntZz z$pVfeMWak`ci2#a4F59MNy@i8;rf#aTWAM?>BzzYC z4E{>w!6nu+!i761S1(!q;k)%Hw5etOHCZ%5-D=1*a7-VjW#5dvzOM8Juf>v%$}7JH zM?+yj#jsK_WMIqy{X>*Md=mOD|8vxIgXK@@yOl_3+cykZYqfDPUcEToji(Hhh0t`q zxRhZTj{chPqtOilc-`{9*XGpPnHdr`AkbYrKlQ1_->QOU778^+Z z?KX*tvkVi~mklIR5dt%u{X4_)k1b2_!yGER1bB0et(u0dIpq(uIh2bgwU2n%e<2Vg z4S~!%zJ?8@FUG72i>^2zY@8~A;6|0 z1l~M!tT>EUo&}sr!qWRs>RTWrGHfh;I*E)N`|Bqps%f|Jv}{zAWzLbkWOt>z8oSo4 zap2CgI|~@fW!>u+xtQzU@T?YN`@uOve=$MKUrbQyqS4%B(RoE|GCo%7@)~@5`Np_} zY&mUg(09*7f-tsMwe1;@Y%S|tzrb{Dqr4ny$iK;$|M9WzK0Rr&9;7XPI98<$u=oLQ z8t{)f5C2xlYhS&+Z4g8{&Di&iq&x9*Tyr9b^Ctk7{|NlL@n#nO2YXf`?5D4WFJS+_ z1jaSVuu2#v53iE5b|XFuqq4w6{Wzbx5ZlWo#~z^5bws^090LBqIC89buh1PtJr4b1 zrq2GMomTr(x0rvm!F6k{ROS8fkVn}~nyNy337&1QF;kf@VUd2IjE-jh!#x~iFf_Hy zER6jJR&kg!4&-IFiu`VZ*h zp2cxjwNUY{Ll&q|*MHhYuETaqUOIz_0eRx4$q!p>C!qYNW}sc8w!+3`B5ZpWlUi9T zDCuY@;SyE?MwLvLwprbk@&tX^Y-vD)}u1X%~q+leV(Qb~(i|hpCD&^y}&SgX&%CIj};kgpmv5cU|*2SC#|fydlGm(Tg`i zt;niI{^orbqq`}j4R)y)SESbuJB(P`V44`gwV0GsW;ayP^va9lHrH0df(s4|!>K2U zv9}VNv>?NV4Y%ga{n4N2SPYz#yq|foR+qs*6~$>jik_$Gg6na2PN`d zCa!fh*}c1wOtVS=0pw+A#9vk*?W9FAjB{g>6M`h8=hY$rzcGO6u(`(T zy@uy0@#)T_ZC**=RN$sKI>hZ%I&=nkz}M$i3V?*79H67ChK&p}6%O(|bdd7fjCSaM znjpIRO)peGxyZ#wt6TZr8}Xv2=bLPL@tSYj#nEZ;PNls}`XF7j6U8|YT6>TCHYSHv z*ZNw*lBR8H&dvRGchu0g*qkSf7&7*E{SFG1-I%*@m`J5C?AZFNG{?*O^O^AfhVkDG z$JTHR+9_weCW0Eio@@dMQ$W)a=oJnR(qC-+Gt|cgqsgw#ZwZ`n&&SJ2yQDP#X&4r^ z&+;Aj^7x(3h-ptoeDix&A^{->`a=lT)7}>U8I)t|*QHx@*5YJajGt#2w7*V*X*Y&X zt&P+RdH7)@xNLmB^sypirIoKp2FPUV=}Gx?qZQNpMnZL~%@~3u`(;w|EBJ54)AKjF zi@%cx=7Sw`n00NgCGu&+txm(y$;;-AdLv;iW`BCZ&-)`P02^yYow zg-IX4Lo%O_C3T+;;qB`)K3{gm!AcJ2nh=y(-@?7o6^=`M?=S*?CVaORyAC3NIA6&zI%xh5q9sN8m;3vl5SJ&;N{g{7k9YZjF zwfQy(NXQ|=tl}ZPbaF3wF^rb8)5|odoEUm%57&e3eMV*e9(^l}d#(dSn)Oy@sm(E&8M)ZcDgwF~nn7EVu z#5UY!C6APqcg-y0k3Zo*Ss5D%$WRSvurio!VJ;XiADZ+!QS7Ka)lsf+ks$N^pB3A` z#nwk-&8O(9%8B<4NXjF2Z)ePZ8UvQuGPz0vyj*NC*Sve6{j=HGj+45f%X&7>c?$E! zfcpaXUQS0=?iq*_+;Q{9#hTF(3t0JIE7UwQ#Wax>G9#MBbov%^I(p)@RHpFbaHswOj6EMuP57nM6>(XT{P=Wut_ z1CA(8W*yd=<`NA8(mOEJR8TfoIOgNPU4}^$=}=vYS9C698xSCz0mrpU85wHpc!#k?7bU_U9U1AzBfG6ZKd05$GW`w>qS zUE7XeJ$V|YEumcVX`0?WBbf-m?Th!6C*((4F_u8W;u2`Ur)5`iR?zG22H&dM=Wfmo z*VN-}UC_)qR3^`C?`=#fB%(LWZSfv_gR{pU%bQb@m|B%xGvXes@6)Q8GWOe0X&mK( zR$Iloj3*`mR7!)#QQTwGyd~}gr$tud^_siPIRiw^Pacb=7NN8x9vqBPo5q=NKd+!+O~U@aUdUiqW)>*}glI3xXkV z;rS3XHoauOq)&Iv1?dvF*M&52H}Evl@8Czq*m3HXjV%->P8hD{>_K|_I6)Mv#6a%x zuZWzopdd&(kCPKQt{wqP??RN zVY4HO%eRj$V%UhXGgD~1m|OtN4+pdS+?fFAT}m)z6T|s9q(g(8i0Ids%}B4W&Go*j z_!RY#My8LC(`*%g{t{95bGF0}GGFZ>*Jb`I&Dcdxv;=w$lCk4l!KmHAm~=+Ye$rUIK3p^q;Nx%GUKz{9l@ELHG(i zJk!FX<#ZW3W^x~mJvy;5^u?kp0}@AnF?77C|KyLSK)V0n*ShS|HG$yaDTeuyFlH(f zIu{);P?g*%QY%3t9?j4n75&gjS14z<*R6=9g!@VHkWPT8S*&)l28F80C)E#%UsHFy zp^Qbj!`$0x{m)l!jcz$^@9BriDX?DrNuYE zpPsUW1OHk0dzVxW`LqQBJbC%AehuA@Un%OVIA)!Sxt;bM$1QYPx^k=eTUIZ;T77DQ zA|+Vq3a;jNPVLR#{-bU6yh2_b150_^mZYwzaQEZt%vYaMXV$Hs?~JOsS1RM?5S*X3 zMQL8gsbP2po_jVfFh?Z&H zlTSVjvW|XqP*}qZ(TpyPIMC9U&)b19`SQQ4Z`mBqxxXNVE_MB-9fY>LB ze!4PFtG#ddcH`=!5QESf+QHrK@%TVi6Q6)!`e4(S%Bb_o@@$kA&7lcGQ`00Ze{Qni zJA)!|KaIOuOnz{Gvx7jI<*nmp)g@*-a{h3{Fz&9igAxgMk^d1WTE@Lp{#9MvzPswg ziF5je)y*Gieg@P98K@f@Iq%-cv@7lQyP8&EVCdStJOC$VY3&8RgZjjzS8w(@iSdXD z@4MiP@sAPkplo@uO>bjQV)}M|6k@u4R342Me5Jjqh^E@U^c z!S0AQw*bvMRAoUwZCke7zPW%76L?UFbPL2MOrzbFQFr^B&GQ&uPn4~UaZMMB27Vs8 zOk78*KfO#>pvt+na5}L3*-->t?hoNnd)~KBA%X_MXr3b8E+5WQ8U!q`3#9Al@Yx95 z%gN@(uCr{8sxeE}uUe9iGo^{HY_4$rO#8yhS4o35SoqcE6B_novY2V9@J(gYxi$q^ z{{ikluROA>j-Yd$LjMh={-^ZqkkCHCwa@4842p)-^X42IOgEzDjxEt)vvy1+cmAUU z)LGgf_b=M8ol5KIjnL|FoZ!#;F$Z*0bqfz>7M#Lf;-Jus!9xNxVet9hf??Bp4|SY> z@7?yjTqkRQjwxv(AC)?}QI|U^#Ndq@pUCUGr^MnCGoH*qefLC2&f6YyR$%J0zm;3J z?hCn4+EAZtNaM@@4*dQH7eTLcotiEXDxLvwVjbU7wW0~Jv;SDInzRTQIFKPT-27?@ zgcan%X$A;;;YCVfdH)=-;lLXbO z`Fr_0#Woxx(s)?M08QF`Px# literal 110218 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 F{|AnE;-CNk diff --git a/assets/images/icon.png b/assets/images/icon.png index 1c33866a44a4d17d522c4266f3fe4d76e29158e4..8ea72efc816e809bbf06e99fb874cbbd1473b4ad 100644 GIT binary patch literal 11897 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 diff --git a/assets/images/icon.svg b/assets/images/icon.svg deleted file mode 100644 index 30bb299..0000000 --- a/assets/images/icon.svg +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/manifest.json b/assets/manifest.json index ee671f6..a0062c6 100644 --- a/assets/manifest.json +++ b/assets/manifest.json @@ -3,8 +3,8 @@ "short_name": "Todo Baggins", "start_url": "/", "display": "standalone", - "background_color": "#27272a", - "theme_color": "#27272a", + "background_color": "#101828", + "theme_color": "#b89a2e", "icons": [ { "src": "/assets/images/icon.png", diff --git a/assets/styles/input_range.css b/assets/styles/input_range.css index 555e054..1de33c2 100644 --- a/assets/styles/input_range.css +++ b/assets/styles/input_range.css @@ -8,56 +8,33 @@ input[type="range"] { background: transparent; } -input[type="range"]::-moz-range-thumb { +input[type="range"]::-moz-range-thumb, input[type="range"]::-webkit-slider-thumb { width: 1.25rem; height: 1.25rem; - background: rgba(228 228 231); + background: var(--color-gray-400); + filter: drop-shadow(0 var(--spacing) 0 var(--color-gray-500)); border: 0; border-radius: 0.5rem; + cursor: pointer; } -input[type="range"]::-moz-range-progress { - background: #525259; +input[type="range"]::-webkit-slider-thumb { + position: relative; + top: -9px; +} + +input[type="range"]::-moz-range-track, input[type="range"]::-webkit-slider-runnable-track { + background: var(--color-gray-800-muted); height: 0.5rem; + filter: drop-shadow(0 calc(0px - var(--spacing)) 0 var(--color-gray-900-muted)); border-radius: 0.25rem; } input[type="range"]::-moz-range-track { - background: rgba(39 39 42 / 50%); - height: 0.5rem; - border-radius: 0.25rem; -} - -input[type="range"].input-range-reverse::-moz-range-progress { - background: #2d2d31; - height: 0.5rem; - border-radius: 0.25rem; -} - -input[type="range"].input-range-reverse::-moz-range-track { - background: rgba(113 113 122 / 50%); - height: 0.5rem; - border-radius: 0.25rem; -} - -input[type="range"]::-webkit-slider-thumb { - width: 1.25rem; - height: 1.25rem; - background: rgba(228 228 231); - border: 0; - border-radius: 0.5rem; - position: relative; - top: -0.4rem; + transform: translateY(3px); } input[type="range"]::-webkit-slider-runnable-track { - background: rgba(39 39 42 / 50%); - height: 0.5rem; - border-radius: 0.25rem; -} - -input[type="range"].input-range-reverse::-webkit-slider-runnable-track { - background: rgba(39 39 42 / 50%); - height: 0.5rem; - border-radius: 0.25rem; + position: relative; + top: 3px; } diff --git a/assets/styles/select_arrow.css b/assets/styles/select_arrow.css new file mode 100644 index 0000000..cbb88f0 --- /dev/null +++ b/assets/styles/select_arrow.css @@ -0,0 +1,9 @@ +select { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 640'%3E%3Cpath fill='%239ca3af' d='M300.3 440.8C312.9 451 331.4 450.3 343.1 438.6L471.1 310.6C480.3 301.4 483 287.7 478 275.7C473 263.7 461.4 256 448.5 256L192.5 256C179.6 256 167.9 263.8 162.9 275.8C157.9 287.8 160.7 301.5 169.9 310.6L297.9 438.6L300.3 440.8z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-size: 2rem; + background-position: right .5rem center; +} diff --git a/src/components/app.rs b/src/components/app.rs index 553ee51..6e4204c 100644 --- a/src/components/app.rs +++ b/src/components/app.rs @@ -1,4 +1,5 @@ use crate::internationalization::get_language_identifier; + use crate::route::Route; use dioxus::core_macro::rsx; use dioxus::dioxus_core::Element; @@ -15,6 +16,7 @@ static FONTS_DIRECTORY: Asset = asset!( const TAILWIND_CSS: Asset = asset!("/assets/tailwind.css"); const INPUT_NUMBER_ARROWS_CSS: Asset = asset!("/assets/styles/input_number_arrows.css"); const INPUT_RANGE_CSS: Asset = asset!("/assets/styles/input_range.css"); +const SELECT_ARROW_CSS: Asset = asset!("/assets/styles/select_arrow.css"); const MANIFEST: Asset = asset!("/assets/manifest.json"); #[component] @@ -36,10 +38,11 @@ pub(crate) fn App() -> Element { document::Stylesheet { href: TAILWIND_CSS } document::Stylesheet { href: INPUT_NUMBER_ARROWS_CSS } document::Stylesheet { href: INPUT_RANGE_CSS } + document::Stylesheet { href: SELECT_ARROW_CSS } document::Link { rel: "manifest", href: MANIFEST, crossorigin: "use-credentials" } div { - class: "min-h-screen pt-4 pb-36 flex flex-col text-zinc-200 bg-zinc-800", + class: "min-h-screen py-4 flex flex-col text-gray-300 bg-gray-900", Router:: {} } } diff --git a/src/components/bottom_panel.rs b/src/components/bottom_panel.rs index 7738415..f230871 100644 --- a/src/components/bottom_panel.rs +++ b/src/components/bottom_panel.rs @@ -1,78 +1,22 @@ -use crate::components::error_boundary_message::ErrorBoundaryMessage; use crate::components::navigation::Navigation; -use crate::components::project_form::ProjectForm; -use crate::components::task_form::TaskForm; -use crate::models::project::Project; -use crate::models::task::Task; -use crate::route::Route; use dioxus::prelude::*; #[component] -pub(crate) fn BottomPanel(display_form: Signal) -> Element { - // A signal for delaying the application of styles. - #[allow(clippy::redundant_closure)] - let mut expanded = use_signal(|| display_form()); +pub(crate) fn BottomPanel() -> Element { let navigation_expanded = use_signal(|| false); - let current_route = use_route(); - - let mut project_being_edited = use_context::>>(); - let mut task_being_edited = use_context::>>(); - - use_effect(use_reactive(&display_form, move |display_form| { - if display_form() { - expanded.set(true); - } else { - spawn(async move { - // Necessary for a smooth – not instant – height transition. - #[cfg(not(feature = "server"))] - async_std::task::sleep(std::time::Duration::from_millis(500)).await; - /* The check is necessary for the situation when the user expands the panel while - it is being closed. */ - if !display_form() { - expanded.set(false); - } - }); - } - })); rsx! { div { class: format!( - "flex flex-col pointer-events-auto bg-zinc-700/50 rounded-t-xl border-t-zinc-600 border-t backdrop-blur drop-shadow-[0_-5px_10px_rgba(0,0,0,0.2)] transition-[height] duration-[500ms] ease-[cubic-bezier(0.79,0.14,0.15,0.86)] overflow-y-scroll {}", - match (display_form(), current_route, navigation_expanded()) { - (false, _, false) => "h-[64px]", - (false, _, true) => "h-[130px]", - (true, Route::ProjectsPage, _) => "h-[130px]", - (true, _, _) => "h-[506px]", + "flex flex-col pointer-events-auto bg-gray-800 transition-[height] duration-[500ms] ease-[cubic-bezier(0.79,0.14,0.15,0.86)] overflow-y-scroll {}", + if navigation_expanded() { + "h-[130px]" + } else { + "h-[66px]" } ), - if expanded() { - ErrorBoundaryMessage { - match current_route { - Route::ProjectsPage => rsx! { - ProjectForm { - project: project_being_edited(), - on_successful_submit: move |_| { - display_form.set(false); - project_being_edited.set(None); - } - } - }, - _ => rsx! { - TaskForm { - task: task_being_edited(), - on_successful_submit: move |_| { - display_form.set(false); - task_being_edited.set(None); - } - } - } - } - } - } else { - Navigation { - expanded: navigation_expanded, - } + Navigation { + is_expanded: navigation_expanded, } } } diff --git a/src/components/button_primary.rs b/src/components/button_primary.rs new file mode 100644 index 0000000..7fe01c2 --- /dev/null +++ b/src/components/button_primary.rs @@ -0,0 +1,29 @@ +use dioxus::prelude::*; + +#[component] +pub(crate) fn ButtonPrimary( + class: Option, + children: Element, + #[props(extends = GlobalAttributes, extends = button)] attributes: Vec, + // TODO: Remove this once https://github.com/DioxusLabs/dioxus/issues/4019 gets resolved. + onclick: Option>>, +) -> Element { + rsx! { + button { + class: format!( + "cursor-pointer pb-[6px] hover:pb-[7px] active:pb-[2px] mt-[1px] hover:mt-0 active:mt-[5px] hover:*:drop-shadow-[0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted)] active:*:drop-shadow-[0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted)] transition-all duration-150 {}", + class.unwrap_or("".to_owned()) + ), + onclick: move |event| { + if let Some(onclick) = onclick { + onclick.call(event); + } + }, + ..attributes, + div { + class: "py-3.5 px-4 flex flex-row justify-center items-center bg-amber-300-muted drop-shadow-[0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted)] text-amber-700-muted rounded-xl transition-all duration-150", + {children} + } + } + } +} diff --git a/src/components/button_secondary.rs b/src/components/button_secondary.rs new file mode 100644 index 0000000..4be48a0 --- /dev/null +++ b/src/components/button_secondary.rs @@ -0,0 +1,29 @@ +use dioxus::prelude::*; + +#[component] +pub(crate) fn ButtonSecondary( + class: Option, + children: Element, + #[props(extends = GlobalAttributes, extends = button)] attributes: Vec, + // TODO: Remove this once https://github.com/DioxusLabs/dioxus/issues/4019 gets resolved. + onclick: Option>>, +) -> Element { + rsx! { + button { + class: format!( + "cursor-pointer pb-[6px] hover:pb-[7px] active:pb-[2px] mt-[1px] hover:mt-0 active:mt-[5px] hover:*:drop-shadow-[0_7px_0_var(--color-gray-800)] active:*:drop-shadow-[0_2px_0_var(--color-gray-800)] transition-all duration-150 {}", + class.unwrap_or("".to_owned()) + ), + onclick: move |event| { + if let Some(onclick) = onclick { + onclick.call(event); + } + }, + ..attributes, + div { + class: "py-3.5 px-4 flex flex-row justify-center items-center bg-gray-600 drop-shadow-[0_6px_0_var(--color-gray-800)] rounded-xl transition-all duration-150", + {children} + } + } + } +} diff --git a/src/components/category_calendar_task_list.rs b/src/components/category_calendar_task_list.rs index 5a61335..16df019 100644 --- a/src/components/category_calendar_task_list.rs +++ b/src/components/category_calendar_task_list.rs @@ -28,11 +28,12 @@ pub(crate) fn CategoryCalendarTaskList() -> Element { div { class: "flex flex-col gap-4", div { - class: "px-7 flex flex-row items-center gap-2 font-bold", + class: "px-7 flex flex-row items-center gap-2 text-gray-500 font-bold", div { class: "pt-1", { - date_current.format_localized(t!( + date_current.format_localized( + t!( if date_current.year() == Local::now().year() { "date-weekday-format" } else { diff --git a/src/components/category_input.rs b/src/components/category_input.rs index b86f805..9420271 100644 --- a/src/components/category_input.rs +++ b/src/components/category_input.rs @@ -1,10 +1,11 @@ +use crate::components::select_button::SelectButton; use crate::models::category::Category; use dioxus::core_macro::rsx; use dioxus::dioxus_core::Element; use dioxus::prelude::*; -use dioxus_free_icons::Icon; +use dioxus_free_icons::icons::fa_regular_icons::FaLightbulb; use dioxus_free_icons::icons::fa_solid_icons::{ - FaCalendarDays, FaForward, FaHourglassHalf, FaInbox, FaQuestion, FaWater, + FaCalendarDays, FaHourglassHalf, FaInbox, FaSignsPost, FaWater, }; #[component] @@ -14,105 +15,51 @@ pub(crate) fn CategoryInput( ) -> Element { rsx! { div { - class: format!("flex flex-row gap-2 {}", class.unwrap_or("")), - button { - r#type: "button", - class: format!( - "py-3 flex flex-row justify-center items-center rounded-lg grow basis-0 {} cursor-pointer", - if selected_category() == Category::SomedayMaybe { "bg-zinc-500/50" } - else { "bg-zinc-800/50" } - ), - onclick: move |_| { + class: format!("grid grid-cols-3 gap-3 {}", class.unwrap_or("")), + SelectButton { + icon: FaLightbulb, + is_selected: matches!(selected_category(), Category::SomedayMaybe), + on_select: move |_| { selected_category.set(Category::SomedayMaybe); - }, - Icon { - icon: FaQuestion, - height: 16, - width: 16 } - }, - button { - r#type: "button", - class: format!( - "py-3 flex flex-row justify-center items-center rounded-lg grow basis-0 {} cursor-pointer", - if selected_category() == Category::LongTerm { "bg-zinc-500/50" } - else { "bg-zinc-800/50" } - ), - onclick: move |_| { + } + SelectButton { + icon: FaWater, + is_selected: matches!(selected_category(), Category::LongTerm), + on_select: move |_| { selected_category.set(Category::LongTerm); - }, - Icon { - icon: FaWater, - height: 16, - width: 16 } - }, - button { - r#type: "button", - class: format!( - "py-3 flex flex-row justify-center items-center rounded-lg grow basis-0 {} cursor-pointer", - if let Category::WaitingFor(_) = selected_category() { "bg-zinc-500/50" } - else { "bg-zinc-800/50" } - ), - onclick: move |_| { + } + SelectButton { + icon: FaHourglassHalf, + is_selected: matches!(selected_category(), Category::WaitingFor(_)), + on_select: move |_| { selected_category.set(Category::WaitingFor(String::new())); - }, - Icon { - icon: FaHourglassHalf, - height: 16, - width: 16 } - }, - button { - r#type: "button", - class: format!( - "py-3 flex flex-row justify-center items-center rounded-lg grow basis-0 {} cursor-pointer", - if selected_category() == Category::NextSteps { "bg-zinc-500/50" } - else { "bg-zinc-800/50" } - ), - onclick: move |_| { + } + SelectButton { + icon: FaSignsPost, + is_selected: matches!(selected_category(), Category::NextSteps), + on_select: move |_| { selected_category.set(Category::NextSteps); - }, - Icon { - icon: FaForward, - height: 16, - width: 16 } - }, - button { - r#type: "button", - class: format!( - "py-3 flex flex-row justify-center items-center rounded-lg grow basis-0 {} cursor-pointer", - if let Category::Calendar { .. } = selected_category() { "bg-zinc-500/50" } - else { "bg-zinc-800/50" } - ), - onclick: move |_| { + } + SelectButton { + icon: FaCalendarDays, + is_selected: matches!(selected_category(), Category::Calendar { .. }), + on_select: move |_| { selected_category.set(Category::Calendar { date: chrono::Local::now().date_naive(), reoccurrence: None, time: None, }); - }, - Icon { - icon: FaCalendarDays, - height: 16, - width: 16 } - }, - button { - r#type: "button", - class: format!( - "py-3 flex flex-row justify-center items-center rounded-lg grow basis-0 {} cursor-pointer", - if selected_category() == Category::Inbox { "bg-zinc-500/50" } - else { "bg-zinc-800/50" } - ), - onclick: move |_| { + } + SelectButton { + icon: FaInbox, + is_selected: matches!(selected_category(), Category::Inbox), + on_select: move |_| { selected_category.set(Category::Inbox); - }, - Icon { - icon: FaInbox, - height: 16, - width: 16 } } } diff --git a/src/components/category_today_task_list.rs b/src/components/category_today_task_list.rs index 0cb6e0e..772847c 100644 --- a/src/components/category_today_task_list.rs +++ b/src/components/category_today_task_list.rs @@ -46,29 +46,31 @@ pub(crate) fn CategoryTodayTaskList() -> Element { rsx! { div { class: "pt-4 flex flex-col gap-8", - div { - class: "flex flex-col gap-4", + if !long_term_tasks.is_empty() { div { - class: "px-7 flex flex-row items-center gap-2 font-bold", - Icon { - class: "mx-1", - icon: FaWater - } + class: "flex flex-col gap-4", div { - {t!("long-term")._upper_first()} + class: "px-7 flex flex-row items-center gap-2 text-gray-500 font-bold", + Icon { + class: "mx-1.5", + icon: FaWater + } + div { + {t!("long-term")._upper_first()} + } + } + TaskList { + tasks: long_term_tasks } - } - TaskList { - tasks: long_term_tasks } } if !overdue_tasks.is_empty() { div { class: "flex flex-col gap-4", div { - class: "px-7 flex flex-row items-center gap-2 font-bold", + class: "px-7 flex flex-row items-center gap-2 text-gray-500 font-bold", Icon { - class: "mx-1", + class: "mx-1.25", height: 22, width: 22, icon: FaCalendarXmark @@ -86,9 +88,9 @@ pub(crate) fn CategoryTodayTaskList() -> Element { div { class: "flex flex-col gap-4", div { - class: "px-7 flex flex-row items-center gap-2 font-bold", + class: "px-7 flex flex-row items-center gap-2 text-gray-500 font-bold", Icon { - class: "mx-1", + class: "mx-1.25", height: 22, width: 22, icon: FaCalendarCheck diff --git a/src/components/create_button.rs b/src/components/create_button.rs new file mode 100644 index 0000000..a67576d --- /dev/null +++ b/src/components/create_button.rs @@ -0,0 +1,31 @@ +use crate::components::project_form::PROJECT_BEING_EDITED; +use crate::components::{button_primary::ButtonPrimary, task_form::TASK_BEING_EDITED}; +use crate::route::Route; +use dioxus::prelude::*; +use dioxus_free_icons::{Icon, icons::fa_solid_icons::FaGavel}; + +#[component] +pub(crate) fn CreateButton() -> Element { + let navigator = use_navigator(); + let current_route = use_route(); + rsx! { + ButtonPrimary { + class: "pointer-events-auto m-4 self-end *:rounded-full! *:p-4", + onclick: move |_| { + *TASK_BEING_EDITED.write() = None; + *PROJECT_BEING_EDITED.write() = None; + navigator.push( + match current_route { + Route::ProjectsPage => Route::ProjectFormPage, + _ => Route::TaskFormPage, + } + ); + }, + Icon { + icon: FaGavel, + height: 24, + width: 24 + } + } + } +} diff --git a/src/components/error_boundary_message.rs b/src/components/error_boundary_message.rs index 4beb4d4..6beb3ac 100644 --- a/src/components/error_boundary_message.rs +++ b/src/components/error_boundary_message.rs @@ -14,6 +14,7 @@ pub(crate) fn ErrorBoundaryMessage(children: Element, class: Option) -> class: "grow flex flex-col justify-center items-center", div { Icon { + class: "text-gray-500", icon: FaTriangleExclamation, height: 32, width: 32 diff --git a/src/components/form_open_button.rs b/src/components/form_open_button.rs deleted file mode 100644 index 253e1c4..0000000 --- a/src/components/form_open_button.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::models::project::Project; -use crate::models::task::Task; -use dioxus::prelude::*; -use dioxus_free_icons::{ - Icon, - icons::fa_solid_icons::{FaPlus, FaXmark}, -}; - -#[component] -pub(crate) fn FormOpenButton(opened: Signal) -> Element { - let mut project_being_edited = use_context::>>(); - let mut task_being_edited = use_context::>>(); - - rsx! { - button { - class: "pointer-events-auto m-4 py-4 px-5 self-end text-center bg-zinc-300/50 rounded-xl border-t-zinc-200 border-t backdrop-blur drop-shadow-[0_-5px_10px_rgba(0,0,0,0.2)] text-2xl text-zinc-200 cursor-pointer", - onclick: move |_| { - if opened() { - project_being_edited.set(None); - task_being_edited.set(None); - } - opened.set(!opened()); - }, - if opened() { - Icon { - icon: FaXmark, - height: 24, - width: 24 - } - } else { - Icon { - icon: FaPlus, - height: 24, - width: 24 - } - } - } - } -} diff --git a/src/components/input.rs b/src/components/input.rs new file mode 100644 index 0000000..8a7d4d3 --- /dev/null +++ b/src/components/input.rs @@ -0,0 +1,47 @@ +use dioxus::prelude::*; + +#[component] +pub(crate) fn Input( + class: Option, + name: String, + r#type: String, + id: Option, + #[props(extends = GlobalAttributes, extends = input)] attributes: Vec, + // TODO: Remove this once https://github.com/DioxusLabs/dioxus/issues/5271 gets resolved. + autofocus: Option, + // TODO: Remove this once https://github.com/DioxusLabs/dioxus/issues/4019 gets resolved. + oninput: Option>>, + onchange: Option>>, +) -> Element { + rsx! { + input { + class: format!( + "pt-3 pb-2.25 {} bg-gray-800-muted enabled:hover:bg-gray-800 enabled:focus:bg-gray-800 drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-900-muted)] rounded-xl outline-0 {} transition-all duration-150 {}", + match r#type.as_str() { + "date" => "ps-3.25 pe-3", + _ => "px-4" + }, + match r#type.as_str() { + "text" | "number" => "", + _ => "enabled:cursor-pointer" + }, + class.unwrap_or("".to_owned()) + ), + name: name.clone(), + r#type, + id: id.unwrap_or(format!("input_{}", name)), + autofocus, + oninput: move |event| { + if let Some(oninput) = oninput { + oninput.call(event); + } + }, + onchange: move |event| { + if let Some(onchange) = oninput { + onchange.call(event); + } + }, + ..attributes + } + } +} diff --git a/src/components/input_label.rs b/src/components/input_label.rs new file mode 100644 index 0000000..b1b859d --- /dev/null +++ b/src/components/input_label.rs @@ -0,0 +1,21 @@ +use dioxus::prelude::*; +use dioxus_free_icons::{Icon, IconShape}; + +#[component] +pub(crate) fn InputLabel( + icon: I, + r#for: Option, +) -> Element { + rsx! { + label { + r#for, + class: "mt-0.5 min-w-7 flex flex-row justify-center items-center", + Icon { + class: "text-gray-600", + icon, + height: 16, + width: 16 + } + } + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs index 81d8ae0..4ca3745 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,16 +1,21 @@ pub(crate) mod app; pub(crate) mod bottom_panel; +pub(crate) mod button_primary; +pub(crate) mod button_secondary; pub(crate) mod category_calendar_task_list; pub(crate) mod category_input; pub(crate) mod category_today_task_list; +pub(crate) mod create_button; pub(crate) mod error_boundary_message; -pub(crate) mod form_open_button; +pub(crate) mod input; +pub(crate) mod input_label; pub(crate) mod navigation; pub(crate) mod navigation_item; pub(crate) mod project_form; pub(crate) mod project_list; pub(crate) mod project_select; -pub(crate) mod reoccurrence_input; +pub(crate) mod reoccurrence_interval_input; +pub(crate) mod select_button; pub(crate) mod sticky_bottom; pub(crate) mod subtasks_form; pub(crate) mod task_form; diff --git a/src/components/navigation.rs b/src/components/navigation.rs index 4c01216..0ced100 100644 --- a/src/components/navigation.rs +++ b/src/components/navigation.rs @@ -2,32 +2,37 @@ use crate::components::navigation_item::NavigationItem; use crate::route::Route; use dioxus::prelude::*; use dioxus_free_icons::Icon; +use dioxus_free_icons::icons::fa_regular_icons::FaLightbulb; use dioxus_free_icons::icons::fa_solid_icons::{ - FaBars, FaCalendarDay, FaCalendarDays, FaCheck, FaForward, FaHourglassHalf, FaInbox, FaList, - FaQuestion, FaTrashCan, + FaBars, FaCalendarDay, FaCalendarDays, FaHourglassHalf, FaInbox, FaList, FaSignsPost, + FaTrashCan, FaVolcano, }; #[component] -pub(crate) fn Navigation(expanded: Signal) -> Element { +pub(crate) fn Navigation(is_expanded: Signal) -> Element { rsx! { div { class: "grid grid-cols-5 justify-stretch", button { class: format!( - "py-5 flex flex-row justify-center items-center {} cursor-pointer", - if expanded() { "text-zinc-200" } - else { "text-zinc-500" } + "py-2 flex flex-row justify-center items-center cursor-pointer", ), - onclick: move |_| expanded.set(!expanded()), - Icon { - icon: FaBars, - height: 24, - width: 24 + onclick: move |_| is_expanded.set(!is_expanded()), + div { + class: format!("pt-2.5 px-4 {} transition-all duration-150", + if is_expanded() { "pb-2 mt-1 bg-gray-900 text-gray-400 rounded-xl drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-950)]" } + else { "pb-3 bg-gray-800 rounded-xl drop-shadow-[0_0_0_var(--color-gray-950)] text-gray-600" } + ), + Icon { + icon: FaBars, + height: 24, + width: 24 + } } }, NavigationItem { route: Route::CategoryNextStepsPage, - icon: FaForward + icon: FaSignsPost }, NavigationItem { route: Route::CategoryCalendarPage, @@ -41,7 +46,7 @@ pub(crate) fn Navigation(expanded: Signal) -> Element { route: Route::CategoryInboxPage, icon: FaInbox }, - {if expanded() { + {if is_expanded() { rsx! { NavigationItem { route: Route::ProjectsPage, @@ -53,11 +58,11 @@ pub(crate) fn Navigation(expanded: Signal) -> Element { }, NavigationItem { route: Route::CategoryDonePage, - icon: FaCheck + icon: FaVolcano }, NavigationItem { route: Route::CategorySomedayMaybePage, - icon: FaQuestion + icon: FaLightbulb }, NavigationItem { route: Route::CategoryWaitingForPage, diff --git a/src/components/navigation_item.rs b/src/components/navigation_item.rs index 39c139e..720c535 100644 --- a/src/components/navigation_item.rs +++ b/src/components/navigation_item.rs @@ -13,14 +13,18 @@ pub(crate) fn NavigationItem( Link { to: route.clone(), class: format!( - "py-5 flex flex-row justify-center items-center {}", - if current_route == route { "text-zinc-200" } - else { "text-zinc-500" } + "py-2.5 flex flex-row justify-center items-center hover:*:bg-gray-900 active:*:text-gray-400", ), - Icon { - icon, - height: 24, - width: 24 + div { + class: format!("pt-2.5 px-4 {} transition-all duration-150", + if current_route == route { "pb-2 mt-1 bg-gray-900 text-gray-400 rounded-xl drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-950)]" } + else { "pb-3 bg-gray-800 rounded-xl drop-shadow-[0_0_0_var(--color-gray-950)] text-gray-600" } + ), + Icon { + icon, + height: 24, + width: 24 + } } } } diff --git a/src/components/project_form.rs b/src/components/project_form.rs index 07a2206..a3a3cfa 100644 --- a/src/components/project_form.rs +++ b/src/components/project_form.rs @@ -1,82 +1,113 @@ +use crate::components::button_primary::ButtonPrimary; +use crate::components::button_secondary::ButtonSecondary; +use crate::components::input::Input; +use crate::components::input_label::InputLabel; use crate::models::project::Project; use crate::server::projects::{create_project, delete_project, edit_project}; use dioxus::core_macro::{component, rsx}; use dioxus::dioxus_core::Element; use dioxus::prelude::*; use dioxus_free_icons::Icon; -use dioxus_free_icons::icons::fa_solid_icons::{FaFloppyDisk, FaPenClip, FaTrashCan}; +use dioxus_free_icons::icons::fa_solid_icons::{FaFeatherPointed, FaStamp, FaTrashCan, FaXmark}; + +pub(crate) static PROJECT_BEING_EDITED: GlobalSignal> = Signal::global(|| None); #[component] -pub(crate) fn ProjectForm( - project: Option, - on_successful_submit: EventHandler<()>, -) -> Element { +pub(crate) fn ProjectForm() -> Element { + let navigator = use_navigator(); + let project = PROJECT_BEING_EDITED(); let project_for_submit = project.clone(); rsx! { form { + class: "px-4 flex flex-col gap-4", onsubmit: move |event| { event.prevent_default(); let project = project_for_submit.clone(); async move { let new_project = event.parsed_values().unwrap(); - if let Some(project) = project { - let _ = edit_project(project.id, new_project).await; + let result = if let Some(project) = project { + edit_project(project.id, new_project).await } else { - let _ = create_project(new_project).await; + create_project(new_project).await + }; + if result.is_ok() { + navigator.go_back(); } - on_successful_submit.call(()); } }, - class: "p-4 flex flex-col gap-4", + id: "form_project", div { class: "flex flex-row items-center gap-3", - label { - r#for: "input_title", - class: "flex flex-row justify-center items-center min-w-6", - Icon { - class: "text-zinc-400/50", - icon: FaPenClip, - height: 16, - width: 16 - } + InputLabel { + icon: FaFeatherPointed, + r#for: "input_title" } - input { + Input { + class: "grow", name: "title", required: true, - initial_value: project.as_ref().map(|project| project.title.to_owned()), r#type: "text", - class: "py-2 px-3 grow bg-zinc-800/50 rounded-lg", - id: "input_title" + initial_value: project.as_ref().map(|project| project.title.to_owned()), } } - div { - class: "flex flex-row justify-between mt-auto", - button { - r#type: "button", - class: "py-3 px-4 bg-zinc-300/50 rounded-lg cursor-pointer", - onclick: move |_| { + } + div { + class: "px-4 grid grid-cols-3 gap-3 mt-auto", + ButtonSecondary { + r#type: "button", + class: "grow", + onclick: { + let project = project.clone(); + move |_| { let project = project.clone(); async move { if let Some(project) = project { - let _ = delete_project(project.id).await; + let result = delete_project(project.id).await; + if result.is_ok() { + /* TODO: Might not work on mobile due to + https://dioxuslabs.com/learn/0.7/essentials/router/navigation#history-buttons. + */ + navigator.go_back(); + } + } else { + navigator.go_back(); + } + } + } + }, + Icon { + icon: FaTrashCan, + height: 16, + width: 16 + } + } + if project.is_some() { + div { + class: "grow flex flex-col items-stretch", + GoBackButton { + ButtonSecondary { + /* TODO: Replace w-full` with proper flexbox styling once + https://github.com/DioxusLabs/dioxus/issues/5269 is solved. */ + class: "w-full", + r#type: "button", + Icon { + icon: FaXmark, + height: 16, + width: 16 } - on_successful_submit.call(()); } - }, - Icon { - icon: FaTrashCan, - height: 16, - width: 16 } } - button { - r#type: "submit", - class: "py-3 px-4 bg-zinc-300/50 rounded-lg cursor-pointer", - Icon { - icon: FaFloppyDisk, - height: 16, - width: 16 - } + } else { + div {} + } + ButtonPrimary { + form: "form_project", + r#type: "submit", + Icon { + icon: FaStamp, + height: 16, + width: 16 } } } diff --git a/src/components/project_list.rs b/src/components/project_list.rs index 28b5ae1..aa8001f 100644 --- a/src/components/project_list.rs +++ b/src/components/project_list.rs @@ -1,24 +1,22 @@ -use crate::{hooks::use_projects, models::project::Project}; +use crate::route::Route; +use crate::{components::project_form::PROJECT_BEING_EDITED, hooks::use_projects}; use dioxus::prelude::*; #[component] pub(crate) fn ProjectList() -> Element { + let navigator = use_navigator(); let projects = use_projects()?; - let mut project_being_edited = use_context::>>(); - rsx! { div { class: "flex flex-col", for project in projects { div { + class: "px-7 py-4 hover:bg-gray-800 font-medium text-pretty wrap-anywhere select-none transition-all duration-150 cursor-pointer", key: "{project.id}", - class: format!( - "px-7 py-4 select-none {} text-pretty wrap-anywhere", - if project_being_edited().is_some_and(|p| p.id == project.id) { - "bg-zinc-700" - } else { "" } - ), - onclick: move |_| project_being_edited.set(Some(project.clone())), + onclick: move |_| { + *PROJECT_BEING_EDITED.write() = Some(project.clone()); + navigator.push(Route::ProjectFormPage); + }, {project.title.clone()} } } diff --git a/src/components/project_select.rs b/src/components/project_select.rs index fc3021e..a4d05aa 100644 --- a/src/components/project_select.rs +++ b/src/components/project_select.rs @@ -10,8 +10,8 @@ pub(crate) fn ProjectSelect(initial_selected_id: Option) -> Element { rsx! { select { name: "project_id", - class: "px-3.5 py-2.5 bg-zinc-800/50 rounded-lg grow cursor-pointer", - id: "input_project", + class: "px-4 pt-3 pb-2.25 bg-gray-800-muted enabled:hover:bg-gray-800 enabled:active:bg-gray-800 drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-900-muted)] rounded-xl grow cursor-pointer", + id: "input_project_id", option { value: 0, {t!("none")} diff --git a/src/components/reoccurrence_input.rs b/src/components/reoccurrence_input.rs deleted file mode 100644 index 9f84b86..0000000 --- a/src/components/reoccurrence_input.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::models::category::ReoccurrenceInterval; -use dioxus::core_macro::rsx; -use dioxus::dioxus_core::Element; -use dioxus::prelude::*; -use dioxus_free_icons::Icon; -use dioxus_free_icons::icons::fa_solid_icons::{FaBan, FaEarthEurope, FaMoon, FaSun}; - -#[component] -pub(crate) fn ReoccurrenceIntervalInput( - reoccurrence_interval: Signal>, - class_buttons: Option<&'static str>, -) -> Element { - rsx! { - button { - r#type: "button", - class: format!( - "py-2 flex flex-row justify-center items-center rounded-lg {} {} cursor-pointer", - class_buttons.unwrap_or(""), - if reoccurrence_interval().is_none() { "bg-zinc-500/50" } - else { "bg-zinc-800/50" } - ), - onclick: move |_| { - reoccurrence_interval.set(None); - }, - Icon { - icon: FaBan, - height: 16, - width: 16 - } - }, - button { - r#type: "button", - class: format!( - "py-2 flex flex-row justify-center items-center rounded-lg {} {} cursor-pointer", - class_buttons.unwrap_or(""), - if let Some(ReoccurrenceInterval::Day) = reoccurrence_interval() - { "bg-zinc-500/50" } - else { "bg-zinc-800/50" } - ), - onclick: move |_| { - reoccurrence_interval.set(Some(ReoccurrenceInterval::Day)) - }, - Icon { - icon: FaSun, - height: 16, - width: 16 - } - }, - button { - r#type: "button", - class: format!( - "py-2 flex flex-row justify-center items-center rounded-lg {} {} cursor-pointer", - class_buttons.unwrap_or(""), - if let Some(ReoccurrenceInterval::Month) = reoccurrence_interval() - { "bg-zinc-500/50" } - else { "bg-zinc-800/50" } - ), - onclick: move |_| { - reoccurrence_interval.set(Some(ReoccurrenceInterval::Month)) - }, - Icon { - icon: FaMoon, - height: 16, - width: 16 - } - }, - button { - r#type: "button", - class: format!( - "py-2 flex flex-row justify-center items-center rounded-lg {} {} cursor-pointer", - class_buttons.unwrap_or(""), - if let Some(ReoccurrenceInterval::Year) = reoccurrence_interval() - { "bg-zinc-500/50" } - else { "bg-zinc-800/50" } - ), - onclick: move |_| { - reoccurrence_interval.set(Some(ReoccurrenceInterval::Year)) - }, - Icon { - icon: FaEarthEurope, - height: 16, - width: 16 - } - } - } -} diff --git a/src/components/reoccurrence_interval_input.rs b/src/components/reoccurrence_interval_input.rs new file mode 100644 index 0000000..a4b00dd --- /dev/null +++ b/src/components/reoccurrence_interval_input.rs @@ -0,0 +1,44 @@ +use crate::components::select_button::SelectButton; +use crate::models::category::ReoccurrenceInterval; +use dioxus::core_macro::rsx; +use dioxus::dioxus_core::Element; +use dioxus::prelude::*; +use dioxus_free_icons::icons::fa_solid_icons::{FaBan, FaEarthEurope, FaMoon, FaSun}; + +#[component] +pub(crate) fn ReoccurrenceIntervalInput( + reoccurrence_interval: Signal>, + class_buttons: Option<&'static str>, +) -> Element { + rsx! { + // TODO: Abstract into SelectButton. Make it sank into the surface by default, like other inputs (abstract those too haha), and rise it up on selection (rationale: it will influence what is on the surface). + SelectButton { + icon: FaBan, + is_selected: reoccurrence_interval().is_none(), + on_select: move |_| { + reoccurrence_interval.set(None); + } + } + SelectButton { + icon: FaSun, + is_selected: matches!(reoccurrence_interval(), Some(ReoccurrenceInterval::Day)), + on_select: move |_| { + reoccurrence_interval.set(Some(ReoccurrenceInterval::Day)) + } + } + SelectButton { + icon: FaMoon, + is_selected: matches!(reoccurrence_interval(), Some(ReoccurrenceInterval::Month)), + on_select: move |_| { + reoccurrence_interval.set(Some(ReoccurrenceInterval::Month)); + } + } + SelectButton { + icon: FaEarthEurope, + is_selected: matches!(reoccurrence_interval(), Some(ReoccurrenceInterval::Year)), + on_select: move |_| { + reoccurrence_interval.set(Some(ReoccurrenceInterval::Year)); + } + } + } +} diff --git a/src/components/select_button.rs b/src/components/select_button.rs new file mode 100644 index 0000000..50eba12 --- /dev/null +++ b/src/components/select_button.rs @@ -0,0 +1,28 @@ +use dioxus::prelude::*; +use dioxus_free_icons::{Icon, IconShape}; + +#[component] +pub(crate) fn SelectButton( + icon: I, + is_selected: bool, + on_select: Callback, +) -> Element { + rsx! { + button { + r#type: "button", + class: format!( + "pt-4.5 flex flex-row justify-center items-center {} rounded-xl transition-all duration-150", + if is_selected { "pb-3.75 bg-gray-900 drop-shadow-[0_0_0_var(--color-gray-900-muted)]" } + else { "pb-2.75 mt-1 bg-gray-800-muted hover:bg-gray-800 drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-900-muted)] text-gray-400 cursor-pointer" } + ), + onclick: move |_| { + on_select.call(()); + }, + Icon { + icon, + height: 16, + width: 16 + } + }, + } +} diff --git a/src/components/subtasks_form.rs b/src/components/subtasks_form.rs index fb4948d..1c7f34b 100644 --- a/src/components/subtasks_form.rs +++ b/src/components/subtasks_form.rs @@ -1,3 +1,6 @@ +use crate::components::button_secondary::ButtonSecondary; +use crate::components::input::Input; +use crate::components::input_label::InputLabel; use crate::hooks::use_subtasks_of_task; use crate::models::subtask::NewSubtask; use crate::models::task::Task; @@ -6,146 +9,131 @@ use dioxus::core_macro::{component, rsx}; use dioxus::dioxus_core::Element; use dioxus::prelude::*; use dioxus_free_icons::Icon; -use dioxus_free_icons::icons::fa_regular_icons::FaSquare; -use dioxus_free_icons::icons::fa_solid_icons::{FaListCheck, FaPlus, FaSquareCheck, FaTrashCan}; +use dioxus_free_icons::icons::fa_solid_icons::{FaGavel, FaListCheck, FaTrashCan}; #[component] pub(crate) fn SubtasksForm(task: Task) -> Element { let subtasks = use_subtasks_of_task(task.id)?; let mut new_title = use_signal(String::new); rsx! { - form { - class: "flex flex-row items-center gap-3", - onsubmit: move |event| { - event.prevent_default(); - let task = task.clone(); - async move { - let new_subtask = NewSubtask { - task_id: task.id, - title: event.get("title").first().cloned().and_then(|value| match value { - FormValue::Text(value) => Some(value), - FormValue::File(_) => None - }).unwrap(), - is_completed: false - }; - let _ = create_subtask(new_subtask).await; - new_title.set(String::new()); - } - }, - label { - r#for: "input_new_title", - class: "min-w-6 flex flex-row justify-center items-center", - Icon { - class: "text-zinc-400/50", - icon: FaListCheck, - height: 16, - width: 16 - } - } - div { - class: "grow grid grid-cols-6 gap-2", - input { - name: "title", - required: true, - value: new_title, - r#type: "text", - class: "grow py-2 px-3 col-span-5 bg-zinc-800/50 rounded-lg", - id: "input_new_title", - onchange: move |event| new_title.set(event.value()) - } - button { - r#type: "submit", - class: "py-2 col-span-1 flex flex-row justify-center items-center bg-zinc-800/50 rounded-lg cursor-pointer", - Icon { - icon: FaPlus, - height: 16, - width: 16 - } - } - } - } - for subtask in subtasks { - div { - key: "{subtask.id}", + div { + class: "flex flex-col gap-3", + form { class: "flex flex-row items-center gap-3", - button { - class: "min-w-6 flex flex-row justify-center items-center text-zinc-400/50 cursor-pointer", - onclick: { - let subtask = subtask.clone(); - move |_| { - let subtask = subtask.clone(); - async move { - let new_subtask = NewSubtask { - task_id: subtask.task_id, - title: subtask.title.clone(), - is_completed: !subtask.is_completed - }; - let _ = edit_subtask( - subtask.id, - new_subtask - ).await; - } - } - }, - if subtask.is_completed { - Icon { - icon: FaSquareCheck, - height: 24, - width: 24 - } - } else { - Icon { - icon: FaSquare, - height: 24, - width: 24 - } + onsubmit: move |event| { + event.prevent_default(); + let task = task.clone(); + async move { + let new_subtask = NewSubtask { + task_id: task.id, + title: event.get("new_title").first().cloned().and_then(|value| match value { + FormValue::Text(value) => Some(value), + FormValue::File(_) => None + }).unwrap(), + is_completed: false + }; + let _ = create_subtask(new_subtask).await; + new_title.set(String::new()); } + }, + InputLabel { + icon: FaListCheck, + r#for: "input_new_title" } div { - class: "grow grid grid-cols-6 gap-2", - input { + class: "grow flex flex-row items-end gap-3", + Input { + class: "grow", + name: "new_title", r#type: "text", - class: "grow py-2 px-3 col-span-5 bg-zinc-800/50 rounded-lg", - id: "input_title_{subtask.id}", - initial_value: subtask.title.clone(), - onchange: { - let subtask = subtask.clone(); - move |event: Event| { - let subtask = subtask.clone(); - async move { - let new_subtask = NewSubtask { - task_id: subtask.task_id, - title: event.value(), - is_completed: subtask.is_completed - }; - if new_subtask.title.is_empty() { - let _ = delete_subtask(subtask.id).await; - } else { - let _ = edit_subtask( - subtask.id, - new_subtask - ).await; - } - } - } + required: true, + value: new_title, + onchange: move |event: Event| new_title.set(event.value()) + } + ButtonSecondary { + r#type: "submit", + Icon { + icon: FaGavel, + height: 16, + width: 16 } } + } + } + for subtask in subtasks { + div { + key: "{subtask.id}", + class: "flex flex-row items-center gap-3", button { - r#type: "button", - class: "py-2 flex flex-row justify-center items-center col-span-1 bg-zinc-800/50 rounded-lg cursor-pointer", + class: "mt-1.5 hover:mt-1 hover:pb-0.5 min-w-7 cursor-pointer transition-all duration-150", onclick: { let subtask = subtask.clone(); move |_| { let subtask = subtask.clone(); async move { - let _ = delete_subtask(subtask.id).await; + let new_subtask = NewSubtask { + task_id: subtask.task_id, + title: subtask.title.clone(), + is_completed: !subtask.is_completed + }; + let _ = edit_subtask( + subtask.id, + new_subtask + ).await; } } }, - Icon { - icon: FaTrashCan, - height: 16, - width: 16 + div { + class: format!("grow h-7 w-7 mb-[4px] drop-shadow-[0_1px_0_var(--color-gray-800),0_1px_0_var(--color-gray-800),0_1px_0_var(--color-gray-800),0_1px_0_var(--color-gray-800)] rounded-full {}", + if subtask.is_completed {"bg-gray-600"} else {"border-3 border-gray-600"} + ) + } + } + div { + class: "grow flex flex-row items-end gap-3", + Input { + class: "grow", + name: "title_edit_{subtask.id}", + r#type: "text", + initial_value: subtask.title.clone(), + onchange: { + let subtask = subtask.clone(); + move |event: Event| { + let subtask = subtask.clone(); + async move { + let new_subtask = NewSubtask { + task_id: subtask.task_id, + title: event.value(), + is_completed: subtask.is_completed + }; + if new_subtask.title.is_empty() { + let _ = delete_subtask(subtask.id).await; + } else { + let _ = edit_subtask( + subtask.id, + new_subtask + ).await; + } + } + } + } + } + ButtonSecondary { + r#type: "button", + onclick: { + let subtask = subtask.clone(); + move |_| { + let subtask = subtask.clone(); + async move { + let _ = delete_subtask(subtask.id).await; + } + } + }, + Icon { + icon: FaTrashCan, + height: 16, + width: 16 + } } } } diff --git a/src/components/task_form.rs b/src/components/task_form.rs index 7d85bd3..3f83e63 100644 --- a/src/components/task_form.rs +++ b/src/components/task_form.rs @@ -1,11 +1,14 @@ +use crate::components::button_primary::ButtonPrimary; +use crate::components::button_secondary::ButtonSecondary; use crate::components::category_input::CategoryInput; +use crate::components::input::Input; +use crate::components::input_label::InputLabel; use crate::components::project_select::ProjectSelect; -use crate::components::reoccurrence_input::ReoccurrenceIntervalInput; +use crate::components::reoccurrence_interval_input::ReoccurrenceIntervalInput; use crate::components::subtasks_form::SubtasksForm; use crate::models::category::{CalendarTime, Category, Reoccurrence}; use crate::models::task::NewTask; use crate::models::task::Task; -use crate::route::Route; use crate::server::tasks::{create_task, delete_task, edit_task}; use chrono::Duration; use dioxus::core_macro::{component, rsx}; @@ -13,8 +16,8 @@ use dioxus::dioxus_core::Element; use dioxus::prelude::*; use dioxus_free_icons::Icon; use dioxus_free_icons::icons::fa_solid_icons::{ - FaBell, FaBomb, FaClock, FaFloppyDisk, FaHourglassEnd, FaLayerGroup, FaList, FaPenClip, - FaRepeat, FaTrashCan, + FaBell, FaBomb, FaClock, FaFeatherPointed, FaHourglassEnd, FaList, FaRepeat, FaScroll, FaStamp, + FaTrashCan, FaXmark, }; use dioxus_i18n::t; use serde::{Deserialize, Serialize}; @@ -51,24 +54,19 @@ struct InputData { project_id: Option, } +pub(crate) static TASK_BEING_EDITED: GlobalSignal> = Signal::global(|| None); +pub(crate) static LATEST_VISITED_CATEGORY: GlobalSignal = + Signal::global(|| Category::Inbox); + #[component] -pub(crate) fn TaskForm(task: Option, on_successful_submit: EventHandler<()>) -> Element { - let route = use_route::(); +pub(crate) fn TaskForm() -> Element { + let navigator = use_navigator(); + let task = TASK_BEING_EDITED(); let selected_category = use_signal(|| { if let Some(task) = &task { task.category.clone() } else { - match route { - Route::CategorySomedayMaybePage => Category::SomedayMaybe, - Route::CategoryWaitingForPage => Category::WaitingFor(String::new()), - Route::CategoryNextStepsPage => Category::NextSteps, - Route::CategoryCalendarPage | Route::CategoryTodayPage => Category::Calendar { - date: chrono::Local::now().date_naive(), - reoccurrence: None, - time: None, - }, - _ => Category::Inbox, - } + LATEST_VISITED_CATEGORY() } }); let category_calendar_reoccurrence_interval = use_signal(|| { @@ -108,9 +106,9 @@ pub(crate) fn TaskForm(task: Option, on_successful_submit: EventHandler<() rsx! { div { - class: "p-4 flex flex-col gap-4", + class: "grow px-4 flex flex-col gap-6.5", form { - class: "flex flex-col gap-4", + class: "flex flex-col gap-8", id: "form_task", onsubmit: move |event| { event.prevent_default(); @@ -152,56 +150,45 @@ pub(crate) fn TaskForm(task: Option, on_successful_submit: EventHandler<() project_id: input_data.project_id .and_then(|deadline| deadline.parse().ok()).filter(|&id| id > 0), }; - if let Some(task) = task { - let _ = edit_task(task.id, new_task).await; + let result = if let Some(task) = task { + edit_task(task.id, new_task).await } else { - let _ = create_task(new_task).await; + create_task(new_task).await + }; + if result.is_ok() { + navigator.go_back(); } - on_successful_submit.call(()); } }, div { class: "flex flex-row items-center gap-3", - label { + InputLabel { r#for: "input_title", - class: "min-w-6 flex flex-row justify-center items-center", - Icon { - class: "text-zinc-400/50", - icon: FaPenClip, - height: 16, - width: 16 - } + icon: FaFeatherPointed }, - input { + Input { + class: "grow", name: "title", required: true, initial_value: task.as_ref().map(|task| task.title.clone()), r#type: "text", - class: "py-2 px-3 grow bg-zinc-800/50 rounded-lg", - id: "input_title" - }, + autofocus: true + } }, div { class: "flex flex-row items-center gap-3", - label { - r#for: "input_project", - class: "min-w-6 flex flex-row justify-center items-center", - Icon { - class: "text-zinc-400/50", - icon: FaList, - height: 16, - width: 16 - } + InputLabel { + r#for: "input_project_id", + icon: FaList }, SuspenseBoundary { fallback: |_| { rsx ! { select { - class: "px-3.5 py-2.5 bg-zinc-800/50 rounded-lg grow cursor-pointer", + class: "px-4 pt-3 pb-2.25 bg-gray-800-muted drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-900-muted)] rounded-xl grow cursor-pointer", option { - value: 0, - {t!("none")} - }, + value: 0 + } } } }, @@ -212,98 +199,69 @@ pub(crate) fn TaskForm(task: Option, on_successful_submit: EventHandler<() }, div { class: "flex flex-row items-center gap-3", - label { - r#for: "input_deadline", - class: "min-w-6 flex flex-row justify-center items-center", - Icon { - class: "text-zinc-400/50", - icon: FaBomb, - height: 16, - width: 16 - } + InputLabel { + icon: FaBomb, + r#for: "input_deadline" }, - input { + Input { name: "deadline", initial_value: task.as_ref().and_then(|task| task.deadline) .map(|deadline| deadline.format("%Y-%m-%d").to_string()), r#type: "date", - class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow basis-0 cursor-pointer", - id: "input_deadline" + class: "grow basis-0" } }, div { class: "flex flex-row items-center gap-3", - label { - class: "min-w-6 flex flex-row justify-center items-center", - Icon { - class: "text-zinc-400/50", - icon: FaLayerGroup, - height: 16, - width: 16 - } + InputLabel { + icon: FaScroll }, CategoryInput { - selected_category: selected_category, - class: "grow" + class: "grow", + selected_category: selected_category } } match selected_category() { Category::WaitingFor(waiting_for) => rsx! { div { class: "flex flex-row items-center gap-3", - label { - r#for: "input_waiting_for", - class: "min-w-6 flex flex-row justify-center items-center", - Icon { - class: "text-zinc-400/50", - icon: FaHourglassEnd, - height: 16, - width: 16 - } + InputLabel { + icon: FaHourglassEnd, + r#for: "input_category_waiting_for", }, - input { + Input { + class: "grow", name: "category_waiting_for", required: true, initial_value: waiting_for, r#type: "text", - class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow", - id: "input_category_waiting_for" - }, + } } }, Category::Calendar { date, reoccurrence, time } => rsx! { div { class: "flex flex-row items-center gap-3", - label { - r#for: "input_category_calendar_date", - class: "min-w-6 flex flex-row justify-center items-center", - Icon { - class: "text-zinc-400/50", - icon: FaClock, - height: 16, - width: 16 - } + InputLabel { + icon: FaClock, + r#for: "input_category_calendar_date" }, div { - class: "grow flex flex-row gap-2", - input { - r#type: "date", + class: "grow flex flex-row gap-3", + Input { + class: "grow", name: "category_calendar_date", + r#type: "date", required: true, initial_value: date.format("%Y-%m-%d").to_string(), - class: - "py-2 px-3 bg-zinc-800/50 rounded-lg grow cursor-pointer", - id: "input_category_calendar_date" }, - input { - r#type: "time", + Input { + class: "grow", name: "category_calendar_time", + r#type: "time", initial_value: time.map(|calendar_time| calendar_time.time.format("%H:%M").to_string() ), - class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow cursor-pointer", - id: "input_category_calendar_time", - oninput: move |event| { + oninput: move |event: Event| { category_calendar_has_time.set(!event.value().is_empty()); } } @@ -311,60 +269,46 @@ pub(crate) fn TaskForm(task: Option, on_successful_submit: EventHandler<() }, div { class: "flex flex-row items-center gap-3", - label { - r#for: "category_calendar_reoccurrence_length", - class: "min-w-6 flex flex-row justify-center items-center", - Icon { - class: "text-zinc-400/50", - icon: FaRepeat, - height: 16, - width: 16 - } + InputLabel { + icon: FaRepeat, + r#for: "category_calendar_reoccurrence_length" }, div { - class: "grow grid grid-cols-6 gap-2", + class: "grow grid grid-cols-5 items-end gap-3", ReoccurrenceIntervalInput { reoccurrence_interval: category_calendar_reoccurrence_interval }, - input { + Input { + class: "text-right", r#type: "number", inputmode: "numeric", name: "category_calendar_reoccurrence_length", disabled: category_calendar_reoccurrence_interval().is_none(), required: true, - min: 1, + min: "1", initial_value: category_calendar_reoccurrence_interval().map_or( String::new(), |_| reoccurrence.map_or(1, |reoccurrence| reoccurrence.length).to_string() - ), - class: "py-2 px-3 bg-zinc-800/50 rounded-lg col-span-2 text-right", - id: "category_calendar_reoccurrence_length" + ) } } }, if category_calendar_has_time() { div { class: "flex flex-row items-center gap-3", - label { - r#for: "category_calendar_reminder_offset_index", - class: "min-w-6 flex flex-row justify-center items-center", - Icon { - class: "text-zinc-400/50", - icon: FaBell, - height: 16, - width: 16 - } + InputLabel { + r#for: "input_category_calendar_reminder_offset_index", + icon: FaBell }, input { - r#type: "range", + class: "grow", name: "category_calendar_reminder_offset_index", + r#type: "range", min: 0, max: REMINDER_OFFSETS.len() as i64 - 1, initial_value: category_calendar_reminder_offset_index() .to_string(), - class: "grow input-range-reverse cursor-pointer", - id: "category_calendar_has_reminder", oninput: move |event| { category_calendar_reminder_offset_index.set( event.value().parse().unwrap() @@ -372,8 +316,8 @@ pub(crate) fn TaskForm(task: Option, on_successful_submit: EventHandler<() } }, label { - r#for: "category_calendar_reminder_offset_index", class: "pr-3 min-w-16 text-right", + r#for: "category_calendar_reminder_offset_index", {REMINDER_OFFSETS[category_calendar_reminder_offset_index()] .map( |offset| if offset.num_hours() < 1 { @@ -399,17 +343,20 @@ pub(crate) fn TaskForm(task: Option, on_successful_submit: EventHandler<() } } } - div { - class: "flex flex-row justify-between mt-auto", - button { - r#type: "button", - class: "py-3 px-4 bg-zinc-300/50 rounded-lg cursor-pointer", - onclick: move |_| { + } + div { + class: "px-4 grid grid-cols-3 gap-3 mt-auto", + ButtonSecondary { + r#type: "button", + class: "grow", + onclick: { + let task = task.clone(); + move |_| { let task = task.clone(); async move { if let Some(task) = task { - if let Category::Trash = task.category { - let _ = delete_task(task.id).await; + let result = if let Category::Trash = task.category { + delete_task(task.id).await } else { let new_task = NewTask { title: task.title.to_owned(), @@ -417,27 +364,53 @@ pub(crate) fn TaskForm(task: Option, on_successful_submit: EventHandler<() category: Category::Trash, project_id: task.project_id }; - let _ = edit_task(task.id, new_task).await; + edit_task(task.id, new_task).await.map(|_| ()) + }; + if result.is_ok() { + /* TODO: Might not work on mobile due to + https://dioxuslabs.com/learn/0.7/essentials/router/navigation#history-buttons. + */ + navigator.go_back(); } + } else { + navigator.go_back(); + } + } + } + }, + Icon { + icon: FaTrashCan, + height: 16, + width: 16 + } + } + if task.is_some() { + div { + class: "grow flex flex-col items-stretch", + GoBackButton { + ButtonSecondary { + /* TODO: Replace w-full` with proper flexbox styling once + https://github.com/DioxusLabs/dioxus/issues/5269 is solved. */ + class: "w-full", + r#type: "button", + Icon { + icon: FaXmark, + height: 16, + width: 16 } - on_successful_submit.call(()); } - }, - Icon { - icon: FaTrashCan, - height: 16, - width: 16 } } - button { - form: "form_task", - r#type: "submit", - class: "py-3 px-4 flex flex-row justify-center items-center bg-zinc-300/50 rounded-lg cursor-pointer", - Icon { - icon: FaFloppyDisk, - height: 16, - width: 16 - } + } else { + div {} + } + ButtonPrimary { + form: "form_task", + r#type: "submit", + Icon { + icon: FaStamp, + height: 16, + width: 16 } } } diff --git a/src/components/task_list.rs b/src/components/task_list.rs index 98a4931..1d1bc8d 100644 --- a/src/components/task_list.rs +++ b/src/components/task_list.rs @@ -1,17 +1,16 @@ +use crate::components::task_form::TASK_BEING_EDITED; use crate::components::task_list_item::TaskListItem; use crate::models::category::Category; -use crate::models::task::{Task, TaskWithSubtasks}; +use crate::models::task::TaskWithSubtasks; +use crate::route::Route; use crate::server::tasks::complete_task; use dioxus::core_macro::rsx; use dioxus::dioxus_core::Element; use dioxus::prelude::*; -use dioxus_free_icons::Icon; -use dioxus_free_icons::icons::fa_regular_icons::FaSquare; -use dioxus_free_icons::icons::fa_solid_icons::FaSquareCheck; #[component] pub(crate) fn TaskList(tasks: Vec, class: Option<&'static str>) -> Element { - let mut task_being_edited = use_context::>>(); + let navigator = use_navigator(); rsx! { div { class: format!("flex flex-col {}", class.unwrap_or("")), @@ -19,28 +18,32 @@ pub(crate) fn TaskList(tasks: Vec, class: Option<&'static str> div { key: "{task.task.id}", class: format!( - "px-7 pt-4.25 {} flex flex-row items-start gap-4 select-none {}", + "px-7 pt-3.75 {} flex flex-row items-start gap-4 hover:bg-gray-800 cursor-pointer select-none transition-all duration-150", if task.task.deadline.is_some() || !task.subtasks.is_empty() { "pb-0.25" } else if let Category::Calendar { time, .. } = &task.task.category { if time.is_some() { "pb-0.25" } else { - "pb-4.25" + "pb-3.75" } } else { - "pb-4.25" + "pb-3.75" }, - if task_being_edited().is_some_and(|t| t.id == task.task.id) { - "bg-zinc-700" - } else { "" } ), onclick: { let task = task.clone(); - move |_| task_being_edited.set(Some(task.task.clone())) + move |_| { + *TASK_BEING_EDITED.write() = Some(task.task.clone()); + navigator.push(Route::TaskFormPage); + } }, button { - class: "text-zinc-500", + class: format!( + "mt-0.5 hover:mt-0 hover:pb-0.5 transition-all duration-150 cursor-pointer {}", + if let Category::Done = task.task.category { "pointer-events-none" } + else { "" } + ), onclick: { move |event: Event| { // To prevent editing the task. @@ -50,23 +53,21 @@ pub(crate) fn TaskList(tasks: Vec, class: Option<&'static str> } } }, - if let Category::Done = task.task.category { - Icon { - icon: FaSquareCheck, - height: 30, - width: 30 - } - } else { - Icon { - class: "cursor-pointer", - icon: FaSquare, - height: 30, - width: 30 - } + div { + class: format!("h-8 w-8 rounded-full {}", + if let Category::Done = task.task.category { + "mt-[3px] mb-[2px] bg-amber-300-muted" + } else { + "mb-[5px] border-3 border-amber-300-muted drop-shadow-[0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted)]" + } + ) } }, - TaskListItem { - task: task.clone() + div { + class: "mt-1.5", + TaskListItem { + task: task.clone() + } } } } diff --git a/src/components/task_list_item.rs b/src/components/task_list_item.rs index 26b3b47..dfd205b 100644 --- a/src/components/task_list_item.rs +++ b/src/components/task_list_item.rs @@ -25,7 +25,7 @@ pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element { class: "flex flex-row gap-4", if let Some(deadline) = task.task.deadline { div { - class: "flex flex-row items-center gap-1 text-sm text-zinc-400", + class: "flex flex-row items-center gap-1 text-sm text-gray-500", Icon { icon: FaBomb, height: 14, @@ -76,7 +76,7 @@ pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element { if let Category::Calendar { time, .. } = task.task.category { if let Some(calendar_time) = time { div { - class: "flex flex-row items-center gap-1 text-sm text-zinc-400", + class: "flex flex-row items-center gap-1 text-sm text-gray-500", Icon { icon: FaClock, height: 14, @@ -91,7 +91,7 @@ pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element { } if !task.subtasks.is_empty() { div { - class: "flex flex-row items-center gap-1 text-sm text-zinc-400", + class: "flex flex-row items-center gap-1 text-sm text-gray-500", Icon { icon: FaListCheck, height: 14, diff --git a/src/layouts/main.rs b/src/layouts/main.rs deleted file mode 100644 index f0800ad..0000000 --- a/src/layouts/main.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::components::bottom_panel::BottomPanel; -use crate::components::form_open_button::FormOpenButton; -use crate::components::sticky_bottom::StickyBottom; -use crate::models::project::Project; -use crate::models::task::Task; -use crate::route::Route; -use dioxus::core_macro::rsx; -use dioxus::dioxus_core::Element; -use dioxus::prelude::*; -use dioxus_free_icons::Icon; -use dioxus_free_icons::icons::fa_solid_icons::FaCog; - -#[component] -pub(crate) fn Main() -> Element { - let mut display_form = use_signal(|| false); - let project_being_edited = - use_context_provider::>>(|| Signal::new(None)); - let task_being_edited = use_context_provider::>>(|| Signal::new(None)); - - use_effect(move || { - display_form.set(project_being_edited().is_some() || task_being_edited().is_some()); - }); - - rsx! { - SuspenseBoundary { - fallback: |_| { - rsx! { - div { - class: "grow flex flex-col justify-center items-center", - Icon { - class: "animate-[spin_2000ms_linear_infinite]", - icon: FaCog, - height: 32, - width: 32 - } - } - } - }, - Outlet:: {} - } - StickyBottom { - FormOpenButton { - opened: display_form, - } - BottomPanel { - display_form: display_form, - } - } - } -} diff --git a/src/layouts/mod.rs b/src/layouts/mod.rs index 052501d..896ab04 100644 --- a/src/layouts/mod.rs +++ b/src/layouts/mod.rs @@ -1,2 +1,2 @@ -mod main; -pub(crate) use main::Main; +pub(crate) mod navigation; +pub(crate) mod suspense; diff --git a/src/layouts/navigation.rs b/src/layouts/navigation.rs new file mode 100644 index 0000000..9dc2b5f --- /dev/null +++ b/src/layouts/navigation.rs @@ -0,0 +1,38 @@ +use crate::components::bottom_panel::BottomPanel; +use crate::components::create_button::CreateButton; +use crate::components::sticky_bottom::StickyBottom; +use crate::components::task_form::LATEST_VISITED_CATEGORY; +use crate::models::category::Category; +use crate::route::Route; +use dioxus::core_macro::rsx; +use dioxus::dioxus_core::Element; +use dioxus::prelude::*; + +#[component] +pub(crate) fn Navigation() -> Element { + let current_route = use_route(); + use_effect(use_reactive(¤t_route, move |current_route| { + *LATEST_VISITED_CATEGORY.write() = match current_route { + Route::CategorySomedayMaybePage => Category::SomedayMaybe, + Route::CategoryWaitingForPage => Category::WaitingFor(String::new()), + Route::CategoryNextStepsPage => Category::NextSteps, + Route::CategoryCalendarPage | Route::CategoryTodayPage => Category::Calendar { + date: chrono::Local::now().date_naive(), + reoccurrence: None, + time: None, + }, + _ => Category::Inbox, + }; + })); + + rsx! { + div { + class: "grow flex flex-col pb-36", + Outlet:: {} + } + StickyBottom { + CreateButton {}, + BottomPanel {} + } + } +} diff --git a/src/layouts/suspense.rs b/src/layouts/suspense.rs new file mode 100644 index 0000000..1e7f0e8 --- /dev/null +++ b/src/layouts/suspense.rs @@ -0,0 +1,28 @@ +use crate::route::Route; +use dioxus::core_macro::rsx; +use dioxus::dioxus_core::Element; +use dioxus::prelude::*; +use dioxus_free_icons::Icon; +use dioxus_free_icons::icons::fa_solid_icons::FaCog; + +#[component] +pub(crate) fn Suspense() -> Element { + rsx! { + SuspenseBoundary { + fallback: |_| { + rsx! { + div { + class: "grow flex flex-col justify-center items-center", + Icon { + class: "text-gray-500 animate-[spin_3000ms_linear_infinite]", + icon: FaCog, + height: 32, + width: 32 + } + } + } + }, + Outlet:: {} + } + } +} diff --git a/src/route/mod.rs b/src/route/mod.rs index a25b856..c573cb4 100644 --- a/src/route/mod.rs +++ b/src/route/mod.rs @@ -8,7 +8,9 @@ use crate::views::category_today_page::CategoryTodayPage; use crate::views::category_trash_page::CategoryTrashPage; use crate::views::category_waiting_for_page::CategoryWaitingForPage; use crate::views::not_found_page::NotFoundPage; +use crate::views::project_form_page::ProjectFormPage; use crate::views::projects_page::ProjectsPage; +use crate::views::task_form_page::TaskFormPage; use dioxus::prelude::*; // All variants have the same postfix because they have to match the component names. @@ -16,26 +18,33 @@ use dioxus::prelude::*; #[derive(Clone, Routable, Debug, PartialEq)] #[rustfmt::skip] pub(crate) enum Route { - #[layout(layouts::Main)] - #[redirect("/", || Route::CategoryTodayPage {})] - #[route("/today")] - CategoryTodayPage, - #[route("/inbox")] - CategoryInboxPage, - #[route("/someday-maybe")] - CategorySomedayMaybePage, - #[route("/waiting-for")] - CategoryWaitingForPage, - #[route("/next-steps")] - CategoryNextStepsPage, - #[route("/calendar")] - CategoryCalendarPage, - #[route("/done")] - CategoryDonePage, - #[route("/trash")] - CategoryTrashPage, - #[route("/projects")] - ProjectsPage, + #[layout(layouts::navigation::Navigation)] + #[layout(layouts::suspense::Suspense)] + #[route("/today")] + CategoryTodayPage, + #[route("/inbox")] + CategoryInboxPage, + #[route("/someday-maybe")] + CategorySomedayMaybePage, + #[route("/waiting-for")] + CategoryWaitingForPage, + #[route("/next-steps")] + CategoryNextStepsPage, + #[route("/calendar")] + CategoryCalendarPage, + #[route("/done")] + CategoryDonePage, + #[route("/trash")] + CategoryTrashPage, + #[route("/projects")] + ProjectsPage, + #[end_layout] + #[end_layout] + #[layout(layouts::suspense::Suspense)] + #[route("/task")] + TaskFormPage, + #[route("/project")] + ProjectFormPage, #[end_layout] #[redirect("/", || Route::CategoryTodayPage)] #[route("/:..route")] diff --git a/src/views/category_page.rs b/src/views/category_page.rs index 64fb8e3..b069551 100644 --- a/src/views/category_page.rs +++ b/src/views/category_page.rs @@ -11,8 +11,7 @@ pub(crate) fn CategoryPage(category: Category) -> Element { rsx! { TaskList { - tasks: tasks.clone(), - class: "pb-36" + tasks: tasks.clone() } } } diff --git a/src/views/mod.rs b/src/views/mod.rs index 892573d..e19c82b 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -8,4 +8,6 @@ pub(crate) mod category_today_page; pub(crate) mod category_trash_page; pub(crate) mod category_waiting_for_page; pub(crate) mod not_found_page; +pub(crate) mod project_form_page; pub(crate) mod projects_page; +pub(crate) mod task_form_page; diff --git a/src/views/project_form_page.rs b/src/views/project_form_page.rs new file mode 100644 index 0000000..7528841 --- /dev/null +++ b/src/views/project_form_page.rs @@ -0,0 +1,12 @@ +use crate::components::{error_boundary_message::ErrorBoundaryMessage, project_form::ProjectForm}; +use dioxus::prelude::*; + +#[component] +pub(crate) fn ProjectFormPage() -> Element { + rsx! { + ErrorBoundaryMessage { + class: "grow py-4 flex flex-col gap-12", + ProjectForm {} + } + } +} diff --git a/src/views/task_form_page.rs b/src/views/task_form_page.rs new file mode 100644 index 0000000..c184611 --- /dev/null +++ b/src/views/task_form_page.rs @@ -0,0 +1,12 @@ +use crate::components::{error_boundary_message::ErrorBoundaryMessage, task_form::TaskForm}; +use dioxus::prelude::*; + +#[component] +pub(crate) fn TaskFormPage() -> Element { + rsx! { + ErrorBoundaryMessage { + class: "grow py-4 flex flex-col gap-12", + TaskForm {} + } + } +} diff --git a/tailwind.css b/tailwind.css index 664bee6..225f3b1 100644 --- a/tailwind.css +++ b/tailwind.css @@ -20,4 +20,8 @@ /* stylelint-disable-next-line */ @theme { --font-sans: "Inter", "sans"; + --color-amber-300-muted: #b89a2e; + --color-amber-700-muted: #80390b; + --color-gray-800-muted: #141d2d; + --color-gray-900-muted: #0b111f; }