From 27e9eda2b060b5082c3816dfa095e60db8f1eae6 Mon Sep 17 00:00:00 2001 From: florian <> Date: Sun, 7 Jul 2024 15:43:17 +0200 Subject: [PATCH] feat: Added support for thumb re upload --- bun.lockb | Bin 172137 -> 181555 bytes package-lock.json | 13 +++ package.json | 33 +++--- src/components/BlurImage.tsx | 29 ++++++ .../FileEventEditor/FileEventEditor.tsx | 96 ++++++++++++++---- .../FileEventEditor/usePublishing.ts | 33 +++--- src/pages/Transfer.tsx | 48 ++------- src/pages/Upload.tsx | 40 ++++---- src/utils/blur.ts | 50 +++++++++ src/utils/image.ts | 18 ++++ src/utils/transfer.ts | 48 +++++++++ 11 files changed, 297 insertions(+), 111 deletions(-) create mode 100644 src/components/BlurImage.tsx create mode 100644 src/utils/blur.ts create mode 100644 src/utils/image.ts create mode 100644 src/utils/transfer.ts diff --git a/bun.lockb b/bun.lockb index 8f256ddc7796dfca248480475f3edac78fa262e3..4e8dfcd7b4b692c2e27e6be96f5efa0208ff5a47 100755 GIT binary patch delta 39776 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 O2ETa_LUi&V{p^1+)2TQB delta 34453 zcmeIbcU%=$+b%pavV{$91qA6x5k(QCZx98s!xj`9U?L-M z6}w`uQDcd{#U3@5XzZTro}xTSe9n8`@BMz~{PE1si+kPIta`6|l|6ghYeR|IgFU8` zeQMvnzjofUb~eG!FWbxt+}P*v0*f1`;-`HaJ$LrOrDcs8I$oS7aCGgS5?@_tvTe$s zM&3=pRN}ZIe@F|+Q#{9MA=@Gv@hQ2vc?JE_IAaa&PdVYxwpB%rtAZQ-lJk1|r*d3M za@K(4T#j=#<~URE10i*g*;y%RRMxwP}fsBBpjEZretGB z6J%wskSkg$1-K%Em7HM|bZS{vcE3DoadM9yIcWnpZZJryNP)F1cSBl2&&vr6rg)jj zebc;C({oe{rXxP}Y-(DMd^DKL%%xZyR{^!7e7fPj89Jq?*a(EEso)jY*-oy)4mbm~ z&Bb1hY6VF)`JmjdkNebS6NP^Yo^+L0^;_cj3e`KUKy0e0i<6wI8Y_8FdN$SRckt9@ zcOj|aCC+lGndzBmP!Dbhbh21%bva&gZb4Q`-}JomPNJ!iv)0;`<6yd?6{4S!P2n^* zxv=MulvzP?T3$K|Y40v4zNpAXkd$g?4Y}%Rxk27(gYvje9&)<0+^qDT@H=S`ApL3Z z)I@l`bhUv_R-`m^HH8iniHD3V?1GUu-OJ0(%bB$Jvs{7bNHOBD*{K$h}(N7m`I0%%D?&@4aPvr{$(3XC$YlrDUY%lL^IovT4pk zQgf>N$|lKzq>8UWI+9BuwUF8U(xiOTbG--V!VrD@h_A{ObwYq@dLK2Sj?EVBD%L6t z4&pd?Kv5;C8l;_~bC57u(TzZkbAj9o36Ch64+&Egjevw_6!lPKQ{}#gqH7^3zb644 z=Ky&Vl6taVc5WVKIc|7;Nf(Op#IlMuh0Pku=JtoIifGP|R*;nx{&fRczXM74&p=Z5 z>{j^YkYx7hias2Y=7Zh}-vN@+MJo5}K~j1LSuZToAV3*B50fKag~U)VIs!=**#JrR z=PCL`NXozz{Y~coL|qA)o1c=CoSVi?YbK}bm!6ZHmzK+A4n_oAJ)6t%@_MJi)eE@J zl8yNS}E#!!q=~?Ohl5=y_srj!{@G-=relL!ayLSL28Cvz5zs{)YOsmeQ z83qiDLL^WfY&$X}KT)UJk*(y44D6kr(i<&E&rKUdp0ACO{pbp$)U#Nky!)mV3`Rq! zLMf2c1vaf^PkJ9KdqN>};_pEx3+JTeW*24*NaN15kzMKtB$;$OWM#EF5kEyNqt<9oC&|j1OS~XvJaB-tKD8sze$OI8ana)(t8#bP=cKx zsIONb0`=u=CE`>igC04_Dd5`uH>5! zNfpaX%juQo&vC<>!v0j?HAE!S_e{>s>yHZMq~+(5lgvw$Tb9&Go;y}S(h&YhiJzJ= zfa=fnRVs>Qy>EJIat>PMf&-AQR@BDt;LG&f~=%Vd%ps1gBMs^BD4`g~~dLE}PltIXVY8#2f zR1r9x)c#WFlyT3D`RA zr|Oo#$%%i_P4)(uDWfm$w-y(e)hcxBBeN!u_K0;|iB$$k^WzVY4v>LxK}X2h(5dtZ zkW^>aOu6(vx!#hWq6_*ZWA|VUo=P{MDkQ@pX;fW=q}q@pC1>Ooa=FNetla_988R(5 zBRwmR<2v;Fmx$acMgOa@{%Qub*{Rtnc{%A>Y2ipm!}FgTQUNUyk5sB&xDQ2EL$u_4 zx!B#X5Y@XTdV)IN0h0Qn3MBEb5RYW`Ksj9xNNQg!B#pVHNRM^0=i`by%G(W4*&d*+PfT$&Ftz@%aRu1K!=7)WZlBQkP>6ab@=)l~IfQYgN=7x{Q)-6%A=A232Wd+ePeH^ssx^3vVqSIaqGCY;@T^;CR~V z5*zVH3tMq=6+2a~$5eR_NLhGkVu1o5y%h-hM&w4tBnGTye?!{d{WZg3uY`?&k4 zorA6po_z1ZHM`0Cr)}`H>dlMhj$6#5h1w8N;!tiZRGcE(R%*KLqv`l*}ax;=MT zUU+@Yy-i%~-VN=~@96QpZ}!QVQ*4F>MVf8mRvq6vA;56=)#l>b#!JS}cdrw>FuWwE z&(5yV4-O8k*!BB{6P9HCmbw4?gnMzv_E=r0xNYA(QEy}CspdU?_W5|Dj+OQd9M$u# zPwV_3(>XUUjWYf6~wmpSavx{L#kKywU-; zU*cGtn`3}wh{vfKb2hlx-GGrC``HRt#tfY?J|Jt#7=pCovt1}x4zjcv5iw3 zbK}?{1N+r}(s)YgzJ2L#e&fU+om$$>9k9bY{bx)oO4rN8wsDN2%q>&>uy%yxJF>HmEg?f$S&r*i8#egFPtzE zOZ>vMwi=ELMwAMoMP0oRs}W88!?mJ98i^KuA>t922n!uDhHDlfL-IjxjTjeMi;C8E z#+~N4BZ#M6_1Xo{0-K3j!Xv}fGqON+F@XA;$aSPYh z!Kap3q%jiXki!^gO{M(eT=c?G6EWI7T(H)PCGO$cQJAoNaIc~ib0;*aI?_ezg%4UW zx<&avsj37rwC+P3wkhPk{VvI4zGQuO*_$W=yUs9<3X$*#}Z;w_t1~ zMtg^AJ7T7!lF<>add)Ow^~IRTFzrQzd}SW>(_-63IrEbDG_)2=e8RQGAgOtBAD)KR z3L5+swsNW_C+EZ|E_!VYG_r98s;zc3G|H@^IK^47-3l#Q$_42JosAgn8?K4Kd|p?a zSR+i^7a`e(Ftc_kG}%#*t>zBh6Jxx?w62)PsjaAAUA;CPT7sla@zZO|piyyxRF#*| zs9n$ohnQeF2!H0jX7Xq@tk#i)=7?FZ;IFeP8mMmo#BFU94jiQ3Qz{f`~`#q$vgK{`&fsIC_-R2QS`hihG3DL+gwvn0B~Y@J%nVXp*)P$f$oI zG2Q5DyW*jd!q$|c659`KYlZbtXGMOucqA->_Y}1aBD6oa$$lewitx@&ENKv~?cpvP zf}>hcBN|5VwZ*805!%ce9M=XJV%XKzYmP!|OpW0yiQ4c8ZHkB7c-a(lpvgT?E_n@_ z-1BiEdcLx#jfl{+sfo|?G!5{B#qkjl+Re~u9z$39=(V?@HG(F24qrozY80XESPPyh zaWrm6LL(=^cn{U{yF_hdgz$SUY3}g$lq)LF7Q>*?Y=Mfo>iM;zwsC~^3Al#fVE=`}UI5KBDa z8m1k95UoH`MKq<*Br%_ z=&+i4zLltL9-%z~j^bO0u&ve;CZrOrrIjZgnmm)hM%tOsXt+zOlIB-v&BYjfn6`F3 zb&9~0txbkTbu*DBliASfLo=q?QF9YoxEM2_feCV@`B9z`2SKB*K-Wd+`HiABDnk1d z93?~tVl3A2lPh8(o(|J%bD=5D0qZY?CdZ`leG^(sDIFD98#QSzo^T1%jzlOHyu7fT z@)u1F;eu&^XgVrfn}+t&XhOqW^!zIEh#^ALE(pU%JYfjatU)MI3K?UjYVB>nC1jRm_RXnY{aNG5t@#Os~0B@3)95I7qCts^gTk&rO-`; zT1uhXn2hARnFz_;CWPd8?>^s+z|KUzI~JiMc`g)a`&qE~%B) ztyj>Hm*VUVF#pm$I2$%CVw70YGhBO4A*F4kb}&X6MZuN=>(@qT)KU}#?Wv;48^B8np(+cGGLOK!Z7yIqJP~Puk{b>SMNPDNQjG5u!907s&mXq8UpS_m7c> zGR7b#;ndF>O`qRElN&|a18C?j+9hDrd10W1NV%sVL@hC*=|nRJTC}K(2oro;i_v|< zh3{I6C4IxS4_eC;3+#?o1hkQJmZmIWR2wlmFa4Gn-9Xt`j}$mDWO z&OxJ^$eLY8wWv6~UfThhTxaS4QMqR<&8%0UQ95}~S|?sMv^39ZQxy&C2DX^XskUv{=ghqA2h7Ki6gGSvYFJw=kQR%#Bk)YSQCCV`+R}+#F#iQNB zHS0kJh$pItY3?8tEXH8l=g>(u3A_f&dagpiM@ zs~aX{brwsyg=<%U2$5<*gYxOWXr5iH6-nxulg|c+y=o%?j7{O2Y6# z-l&DAUb_|=Rx284u+w#D4P>plUh9x7Yx3qHUX03)&@KQMEXBZbr}+h1bJ`MWoqDLB zK(G_mbcL29eIz=GknAB-Fhr}S$o+sdJVLLHgGPOXR$`eP4UKYEw)IL(Y01-8NmYBA z25by8vVa_OG&D*FA4Cb;m6+JUL3;y@+A4j%(Hhd^_DDMweykXk8=>6`PWERDSeomp z=0!CMhDMz(eH7LXRqny}(n9#2Vo6@O_BlAR7it1?`}E@I%M6-Fkyi#Z%o#K}q0ZZ& zk$0l4etIl>NAe@I(Y(&YxkhBF zkI>Pl$3z_REef%Xi}{^N2b;LO9-EEE$d&Cr7Rf z78NW@ouE;*rTI=X6?|#u0*oyaoM(vlEg^l^*(GlU=paHUjVmQGqhCm}% zhK1Ycg{=d`l9Ay&CmtCY!H*WT#Sy}<1I6g#aK5HEzBocVXpr2am@a+v!s$U`bV<0z zaWL)RVoJg^xt~Kv5o#@Qc0vx$8}RR&G*n6Ngu^8BSd|O z0XAH(wJDO#1)qdv;-JyMG?RR93N(LcQYUKnL6f_bJke-`oK6}{+Hh!OQF&|(h1L<8 zT#ujsMe91Ud>t1-qtPPAd;zU7G)t*lgNo%6ENQ>4%Z5g^HsbJo2^?423F=mY#gZiL z58bi5lUS|MD7g&mf->~l0%&9hS^EWA`_G!!=r1utOrTJ;rHHyi(8%n@C=jz&L%Bd< zj8c8DaHfe8E3_OV9u?~sdVnKP9cu#>fVuz=cq_PF=^_-KB&CnU19&Mz7*M)MQU#hR5>6vsBq@U^S(PN|(TZMPYQePwDgrRGbd{G> zaA$zhcR~5m)kPt&`brl`ihw?lWOqd-K~ly&0lG+1Jk0-6i!fVD*FQ-r7!$2@ktB;} zE9G;T-st-o4&zqJumF-QF;vkpf~4zzLQ+MjT*|+2q?9ekk))OsD|&fJJX}z^NKyu4 z6gf^}Ij+2<_~R9xB*p&*AbtWs7fI-qxrvg>Nsl=Kx(KHNc+$x&B9huK$W8Bk-sQNh3(AqehVxdBr~vkgoEQ5@?l(^yLm+ z(<=8*|U*qjRg0nj{6c;g1{S6@@2B9e+#FNmA*zA&I)9@c#`d&kp4i zQpWd`3`tV(z9Jte@*$D9NK)`O{GoJ@75xd3xc;4_D8KVfq<9F(p7>lzNRomt6rCg$ z@KWJlDLhF*JTcd%kh=YYa`&Th_rD^^k9o@aFOrs0Q-vo#3M<;_KHrD=D5y^{!fz9cgKAyuZI$^N1+;kq+lxk=pg$m{Qsn+mggw>lBA(g zI0yma1}is6(uf-gNePM-o+Rm`ASq#q!jmNa8$~ZKiJFK%wD8V=q)B{^A{Rm;T_Lv! z0sP~Z&>uxoa4G&!Mb;{^6q5L@^hc2t+^*<<&6ND}|w-eXfHxcK^-SlrG{P*uu@7JcD`ilI|78{27&ldYX zTWpTD-X?H{|7@{ou8_9gG;jQ8i!HB=|Jh>ypKQU&bN*l3Vz>RD-(rge>+%|HJM+=& z?$p@RPMLigzZ>T_`eJJvvr$9+S3H|_(mHl)=EkU$2Sd)RSkz+2tnsJP7v)+Gs&f5g z#a|NFk8of5xPI=Rp&Pu{pW+)8>MN&P41V?7!^{m~^&L(fwmP8e^qYNWO}_V@dXJ-r z7G>Hty_GTa)|Euh7{`T`W}Xi^G5uia?=7BeeOz_y#6>B~#fBSg3kRQ=fA!L~pq*jk zx-=K3tZ}@$u?3e{|5$OOYqw7Ha9h`+-mBenF8HK0vHxLYi*A{NEDCzvI@0|6oR-_F zRem?qy|4T@uOWY>|&a3ynB2_{nk3O zCnx#k&pUPKVfTLgjLE6amv>wc+BCX9Zceii-e-NeE-N;LiH|qhifP9UyqUP}c)Yl5 zlTNfgVc>P*n~m{e!e*V=b(0~6ob6PPT`IS~EzaPq#N-J;~Ycx!^fJvuXW>1^(F=(o_9*I&=zs zSl_JK{Uh%AO&j*zV__#;=~#Fs$#dNgm+wqoyLR{HgADE%D-EX!%cZ_PNM6!c=5va%Ho)9(R_9BG_-L$bYjqU1Me!1*d8y2?9_>Opt*~FJL1Lr(5CM& z@E+n#Xft-{#AZ7Ud@XUx&UmrqZk_lNT5U0MSG@Qd+LB!c-b;K2ZP^~3*kQMU_ZAoI zM!%KmM9m%pUr&tN6EB+V)rp&+`H6g4ytoBgTA6_l5Z6KLy$|i%Yv6;#Tuyl#iA&BJ#2IIG;)k;azLywv4(2(h6IY)z@af_kXs@AlJ#XOq zip$T#Jm+LqRxhk^!{NbtpHGe{eWOB~zMb%}uW6mWOE%Ow+GdtX z!t$0uRqV7yt1oc7R@&CM;F`6lhx_(f`o!!paTw{|)s24Ff-3d;{$@w5~S|{5RtA zo9O?W=>J;=exjIo3;ll!{SR%DsQnfF|10|cR|7vq+zM?AG^g7JUKG=BV|d)wi6@{< z7wzs~c-+y6!|oV(CLV!y7@GH813yzNxQq7P#h|`ti244rK{4A7ZBb?Tx1Rdpq0uFC zrny{=TI04WxFjpaJ?8qdt)(t{yAwsmFOD91n)rv++9&yYJ>3%W%$wJY8Q8}2)K3Xl zPK>e_o$lGT`jq4Dcm7qqvksbb3p=yYi0peyEAZ*9dpgz_*)AY$Uh~GurPX80zHc?_ z?8nHtGf%&AO&@Woj@`RZzs-HN6}RqgSI!&fh!^j{-1lJa`v!iVIQ~A&eIMqAwm=Mh z0CPWpxgQw#MdAZ!_o2l+H1JErnGa#^hcGv^rDD`?F!ygT_iqM%x%dX!YiL~`8Tggr z@<%ZDBbfWKfnO~qK8Cp;!`#sDlJygq`w7hb#K5l;w?f+j&FOaozd=m@9p?UB$Idn6 zTWIKU-zL%SX*|DK?2o^t;*qDY_)}Q?nStLb7CejRw~43mce_~oc|5;E9D%<(#f$j6 zOZ0mYk2l)Kzd++&pm8q@e3=;fM?Ak*oPxjm#0U7hUyOVi&mWjRlSji|qG2x${GsX3 zptXF3hP^TfM>ysd&UX}!a%?ckV;uXL*+NyumTk=6uJPo8>)m=iZRE(~^o$^met@lsQ#v zIpSz(*Iu?~xJ~mh2V6t^SGgY(|P@ch)+s@BgvJxAi|Ze?3xPm{{BAyWq$>^=|8{ZasZj#--X9mV^fbS&STHwbq)wim?o8N7~p zSb(_4u^bD&BWuZnIY-O`bciLG*Tjsr1oIoZgqURln4qd)9-~vLf=Q?V<_7YXZUyEUI)<1n#5A)8^8#IC4W_pS%u8ZkqI0T&v8@PZNi{HkqI-xrOiTwGFmKR7 zHed!BgVET6d512t1>EfLvb_lU4zY8PC=evrGrZ z+6at}XFZI-Bvj@rvx7!_v`~c?r+$KIs$f6+@{>WZ;8|M%F}8r19)l!Rc^1us=xvTn zR`X!2dG@n07+VW4K_*~qc(#HP9VW&}1ICVLeKcSOS%Nu1j04YdDuVH>3dW-%7$=^M zGy!vgm^+kDb)NZa!Hly4GhK^Ty0V*EGK@8dIi?`onX3~Sh6rzCr19X{V5-fGYGBSG zU%nQ5U`m#;0TE*cqBfgp2I4glABgZ`QI$X}vjwrb5(sbhhKK|^5M6a3>apcI5GM8@ zbd^E)u>vZ03lYtzA^|M1GKk&|Aa+uWAf~MX!qyQ)W)%>6wv~v(L|k=3RYQ69mP|Rw ziPyEd{_MNH=f5>;?p$U4+2Jq7T)0qtwCZ=w-{p%Buhgl!rbn*r<#7v5XC%$JQ@uum z(Os{tOm(TX<;Uy?EsEM~?>jo;z>RVvDU6M9MnRs=DCmweN^Z#fs)M*d#PsSQBG^qL z##IN=%mqXwo8kf@#0A7lBAT#BR}lA!SmFwz8GA;=3|9~x+(5Ko3*11obOWJr2NA{M z+(Eo1ViOSt#@7I`%pF8p4G=MG9T5pNKv;W#Xw8y6K$v)dI6y=kv#1GT3laG>L9}CK zMD(r+!lM?54y=DI5Vo~IoFgKhxq9NyVIoF*f=Fbii5TPwBB(Zq&TK?&5T3O`+##YX z^Q!~m0uj^efauO{5;3k0h-O|OlGzk55FuV5UJ{YQBI|;G$MIShnJ7>ENzOkoxcKx`o*zX1r5l@Zaq0SJ$VAf~ha4MEs81d-Vs1Y=v9^YQ!) zX3+v-CQBzVie6;@OjbjhfF zq4v|_qqoiWZ=18LU!!sN6NWT)=|A&@an~5@Q*$j2|M_jZ4o&o-!*a@Hyrg`_k6)y` z8B=;^QO=l$D=WlBbXYaH`)QB8yLGkaR@-`GNsF8xYi)Y=N-yL+Jk~kZ^}GD_CR<#7Z`k#47fT#A+553$cbRfGAvBzCMq32EAWf?L*v>Cxe|Y^)^1WrmSjq z+m#E8G85Nr+w$JF=Oo?qlWWV2n)G!Jy#N=yP&lG=Y92Q zyR7oBxvw1dB=zx!Q&&%Xt2tIK<8|dTUfAVUVO-Xt{X@ThTO#`RX}#^oW8Fp`|D$8Y zXBGy-+9M~6ovw4WH*E3Pb$hYdx#LG>e!u@W=bW%HuTR{V82ZzM@Xh6WW_|gLt88z3 zGwHTRpL0_m-T3}$os=qZ1Gp}$)4$KO^Qv3f>CtQbK0{{vq(?hPO#kKV*yj~a-`@4p zrhNsq655s4S{inxDZN^v?$b7~#5S<^Mz#*3kiKD1FQc-eNtb^x99aBki^9}#S9dw> z9W+Hhuw55>_dji)c%ARDp!LJ)rKy*)zgaTp{QFF8-LxYq|MuP^A3BI zD}`PPQZJ)!)^R?2o7FflL+|Sq(O{Eb>heQLZQBlP&~t3z$Kmf>dTY-c*UOx6?$8y# z?we-me3Kdk=(;u-+ax^Fynk(#m9BO&f1U&2bp2!x-IC^rHX#(cgH+kK5&T1!RRLY^4Feqy4qv&zDRnZQnkyL z@)Y}w_;P~GrtCvP=L7(4fxxMO5+r?QEuK`Bk9J_(r_+tO#& z_S@a3+%{iwpl6hm)kXWx88zd|Raku?imYK&CGzj zGCCjGvsb5|zGus~pGmZsxoOG~p;GTRZKhOz^da>-*VlDd?rz?y_PVz#?0W`>+_T!y z-6Og^@A=~eqvV_Gnsx5$do8W}6OQfWGY%8Zy7r7(TQ4x|dhV_z=Pc{^&vk3n_uc^e zZOh~8FTD5eO_fhe-HN;;(u)EP*-t)Oz1WbNu%038YD=jNVW z?Rw%!ubxe>)V0}M-kv+l*Y4iElTSZYvO2Y^Le0ii?8`3Aw6l7)VYz17Pt)dZyJOgu zZS8l|D`?DGkBTcaxt2RJE4^-f@ZPP_MivjY?D4;z>s8TF>L|{{9ERLg+P@udEUj0_gOTnHzJjZSHwiU({%p=pC*Z*&(V2be*MIOn7id$WdlAiiJMtzP1754qu@M=( zLuo=s{y49Zemal2Ch^rN-$4}9RQ)x)pRCwV34Dp1s5CAKyNgmpGFSdmf;=|A6aG_q zf~V)d+_}+#Zz>h)n8bT(m0v?EYFHYR#OwJ=@{g0@w|Fwreb}=!JQs{`*60SL=cSRm ze8T?+Fq5jt%zN-A!kmE8*d*ScXUNo(GS%W|4YSiz@z|Y>Na68;tF$baKf_Z^j5PEr ziV^FYhW0c_=T~V8TEI4P-6?|xl!4ARM*dY8H%N1ah8>gIO}|M+KS@>4hT1(+r~cH` zhPH|!CHu15X?zm3{fd;A`m;}S@#xV^DF`AYk$i$Z&B2|;c*u`_ zBwy5D6J-~bUUidnmVAb9KC2(S>fm%RtjQN zaoYf@#P=Xb(i?X30J`QX@hT%c8K8$p>dUS4&5S6$O@zRFM5M~n_?-sOwNOb+-;%7r zO=>x9`0$T@T#j3%aI{&Yv{eCm<&Rn}PlM8L8*vGg0M}9_Em?UF5xADAuNl+J)H4C9 zmHJ{Wy~nZ<8B$>@l*G0O*Qe5OtyDNWgw?lG)t7JW!KLE96(qd{fPeJMS6ojD;98?_ zg^p160#qy7G|_!0pfNxfZFq=t2I!SnDvb6j#F20Gr2sD4W{@Aa0IdMJXep%It^mfS zbgA$2x4d6fgNY`E^qLd4mN(Wf@HQtSUi`o3bPq!xbr#BlH z1M~{yx4fZiSL0CWW6fdqhFo~CzBTLW_PQOD|K_0sL5pi+pwIzci7Jm!ZJrz-nL^K(EK8 zLZ$&dfnGpwARXuf^aV12Odt!$2D$)Ufo?!spdCQ(uSY?)0_YuH8ZOI#x4=7cl=leG z2J}y0C@>5d4$uq05kMHw0HC)T=;iIZKrq5I;7uXv4e?)ro~ZCu$ZNn&fc`TO4G|g= z(defc8HBBlsdrjssFee}|1{e#BV`Ufk>UQ)RS{=X(s0(-lK0rM-^Ag`A(gs0# z%5D!h0JKeT0-S;BfD7OXxB>0}?R?FE=cv*jz)Kc-nRhSrMlb?s1kj6~qtKesz<7Y( zI;MB8D*-E!lqQc4fD!N-C<17b3BmnPfF=)`EP5dyEMwA}|Fp@bX?8p?0vHJt13iEg z9zUOxh5*eRU4d>ucc3C*4443aBBM9JTi_k=9>DWW%PV}%!chn}2ATj(fk*)BtMrnw z4wYGj^cvtXFb60BMgc>BzCZ?$1aLqB1paF!cLg{C90iU6$AQDZ7+@?wZ_^q8OhkqB zAByRBZKN5f8Dw*y1&{!b2bjK;{AhPyxsT*9AxfLJokyRpXQ_O&jC~u$I@bZ{G!i zyo^33ZUktI(O9GA(T-svK;BmXj03`e1IVd9;{E`6 zH1%aFkOK4o5@;>x$nIR@!wOBHR{*F*)PK~Ev@*p37~%8_n9>Go5x9Is+7k0aJiU zz_-9eU;_2TAOyYv3V;%T93A$NhVKx_!N5>}hVocoG%x}v0!9H8w-^|ygjJ4o3Xf6l zsXV104^Wj%gsz+zw#KpndfSfGTdla~U^WIagg_2p1j06zk2fi;RG3$6!_00)50z$So|-QsQ7 zsaw28v!5t-^A;bVD@E`ya0ocaTz};|7am10l!uD}0f0Z?2h;*;0yM9=18xAVjI=`1 zN=aEzfwY2J1D1dVKzkJ}@GI^aLDCfT8^RBPyTBdbHt+y|YZr3&5V)@p?;xK6zXOkf zN5B&${1nm)cmX^I{shQ$F99;y9{`!{EkO5(e+9e&UMu~N*18sEU&7Bseh{}VcJvJ0%Q$}NBa!gSCBt^ z-doVh?*WjReF565)CN2OGB@$wfEPgf7m6e8Um*1WWk~UZ0E$c969|wGP(i^8N8ttt z(<(u0U=~0tN+GS_eSma;3Qq-+0WuXi7p?5wfX+ZCAOVO6Is&w3H2~x%(LfYHm1qgH z02%?!0Z-)93^E*`u4)L`7-&kn#wG|v0u(s{pc|xSr2PxElxj(>r!cj206?vxMYbP6_i6E^ zg*Ok#1@eI+pa4)!I~L(Fz$kz$OI9rgDDU!?9SJ>&`hNsKnNYaAd51w)%{v6)!N6b5 zOcndznR+PV3cz5GIwx=@@o&hj`hSx&KwZIbK2VgNk z6{d<1M-3vW@+%Oo0W1gH5pOBvGJxijHIS=-S^zbUM%_waHK62CC=C}Xa6PaNpp4b7 zXofIlxCl^vpnTmT7Zz|WC5)rW#pp4X3 z(M+kfitbbE)p!Y~py29g!`?pNYgRyzRe#9)ItK*#1WWq@9TIr7Ids3~_UFS)_-=td zetMrkmidr3IQaVbO3SewqV&8NvA%U#nPq9N%3{vHX&R=$IGvPz- z(K-UMWZypKV>BT?zCQl!#$(=A6X4^=`7{1^-pU~uL9U4)^+b{So6rIu?BgH6lHT#v zSl!=Idh`>1GcTv$gFo}LO^}fv>=npP{mzdRj5Vy&YeZZBgtunBp7Pa$)w5^R-;y@s zyZNIUC{jI*X8NiNQJY_#a1?mZbOYzhu!T=~e@FFvno8%t%RA$H^*UM-fV5cdQjOVD z#1IA=vl`EM|6uimn(gmbT0{gc?uD3CiZ3Q@t>j)sz26KiGK$#L;>(?MEt`O}!eA}i z^Nimptk<#u&-wep1T&WOf^Q`RRAOsLhF4-PH3VyB@dxiDw5Y^<|KM$d)w637cR$h0 zzV_G?*`n?~dVHjYH&sTb$H$$G>H7T0P3ewQm4-St{10TfyfQnEJcO;4+1)=-fm4;4 z{Y$=%X>1i~(~je3SnNxbp&mywX=!2LDE}e%zvQGIShI8+pE9T7slE6PiuGYv^J@(;IYNrlY8|-O{G~`>zT|H`NRkq*8j3+5`zm%(#=cpda z(}|ybx%r>-`XNRT(xUEBRoVPks9D>pY}+f?WocFR1iGVq+zwZ?FyMUbyd`h+QVpdP z1}oV$v(vunT|kMM3Is{nuL>zUS`f9>bKBsHb|8OWn(%J<=XV z;`N_yzCJ-zi>IvYH6Or#n0`~R6HM%x`5RQp%AU;@1Y5zyo^2Kce>UI^Z_j^VQ{M1) z{8P5_4JD8?_WBJU>*#`yM`ZmGVa6A>1<#8`?t!v>WM{M1`1y0^o!HQ~Xr6j@&ZL$_SGdMC7#Cqq=BEjTvv!p}7Z%0NNKE?S zuI)Q1bqJgM7!%OmcYFeb!+R9=l(mL%NOP0D^;ucMp`4s&9Z@M|$bDdQC|*80{hqg@ z;q~Y}-%3tx=b#>_6E@#@i}SekK}uWDCDT~u2ju%4`C`l#9se#r?(wmacrgwm3-eY8 z7tFSQ;A0#bVWPJ}gz#ayYQN@`wnhX@LNuASXYL=7gL+Po`}vZx1&fCcLN;+fA<_zjxv`$r|R@Qg3CX9 z5A)##`ylm9Abr_4o4;#v^Q2TeY3!({2u1wx;Jv?d)=Q;f*eVvYVZ31H@RbM{st#-& zBJg$DF%pvRvX3Z@t$)W`8izM@@xy0iw(bes0E{v3+V#Y?>wglu5{11G6dfLa=(&Ooua%Y7EXcPef$BJZ980 zWn$~?U1)c7pB3&XqY0%|(g?N=>hUyAZa3ZcXx+9VMzEZ=I*ZT@nI-%uUpqUEk}b5p}mD?W^kuPC_UeXf-ig%QHd0On&X_&ca)7j3tyX4!Sp%#lVs_7{>j zYS|ED^s0K;QQWL2Pa4fX{qakLrfe-D1gWPZZSXMa>U)25)h`i7paIoUhmfa3T30q7 zd-}_r`RoHF-HQl#TvK#6(!=CslcxK>M7V)Fm=cQgwN{7tzdP0Z%N>pxOyERzEX_m+ zz*;|(L>`+|33XKtTq^Q9w-_VD&&G+qZB1+8zx~`w~MvGHI@Z@3u1!QaXLP^E`|_L0Sj(kR_c> z(+6|c3^?>9h68i;f)Cbfz#4cVqIyiz<>H>r{l2?Y_9bEmmW_x(>bXuUo2PfpiKsRE zO9b`gr_&3}o%VY^X#M5RbhZ;o9n_SYZg&gunSfXrz*QMWm#357Jq(;pdMd!_*GH$p^H0+U+(xfWZxq5 z81=xb;8w>sFPOS<@Rt~!v0*kx_o}B;prdGCiH=f_>rv0IknT|DFtbW{%e61-jE-jg z%mjM}^@t(Ah{Ya5i!JOCH9($7hcn(Cu|~QJHf*e!;DnduBFR70LypuVH;{scD&~jT z?6M`iY6ZKBc#1o*T_}a$#4L13svfYUp7x<6#T0acbw>pL2J<5Egbk~VqA!>ULHK&v zyeej~!Od8ms)D~z+>CXrDzp-&HDjxY@7$dESV3Y_bc6Js&Dq*-1cGgn@4DhvRwYd9x@ajib+Dr!*OIQd4_E>JY&a|bsJV8t+FB%S7AR_ zLx*c)SVbG5m7{uknCXLfr-=Cz-Xf;5+r1advTRTf_1v&G7HNGy2A-IS7?}R?DUMT5 z7AttR>B8lR9mAy>;sabDSJ;~EK-xB6OB=|khl?%hGa$dIr0XHl_l|Xdv?6C(2C8fg4N&AT(A%R`r57@LDulao^?mf zgL)$uGBAeR$&M_;T(EUi4<)NpujTvKovofLG0@@W3G7rA!B(0AG*}9MN?`MmLb#T| z_Sg#6MxchXGv-)2rZQs-AwZay$QoD(t%B9##=c3Nwrn%nUryfi;*0}cE zm__TCDi*=x(N&$;E~Mp$Gc!yg!mZA%rlsJI_h1t(g_xj!Y#{mp&$PP8N0p^c*xWJw z%G@W&QCZyO>B#mUlltPr|5Ja#>N{J(#6dkFD|bhSm1~45&yYo+Jdx(I>n@m&Ca{LK z7&Ph;UDY&+PO(nKM+81po+>%@46KH+{R*5$?`?n_U}JKUvTkgGEv&2_|MkZuck2bq ze~pl0NLvKinyrM9typC{Y$nv>#J*jyzQc!MCp$>Bkv5OUtg{_zQ??obwRKR>V6!$~ zw|(%WtQjbc1`T?DF01E&h|1p0K|SoP!^N9kmemb|r3Of=O&vDO0T~Cfb%>7_>W`5Y z&pweR-LrFuNs{+wA>VmVbKPeN%MC{lP$_Z*NA)DPj&a9glg&3CLhj1$?qCWV=?K5* z%uYHY!$|hPQHXI=PjJhu?c8Iy0?K$8L7fxF+6wRM8yq_Bn_h%Xt-OZ!JJc?vIK z{lN-|^u!6CtVoA{nDj7lQ2ko7FlRJJva_9odYId&h_epSrw*5U(%{AxISVl`!ZSoC z>(;C;%)$ebvg)#m#|yUjuOd}kga8Ni$hMWP2@Btj{zQ9bnmACJfpvwyIjCp3{Z`4v z&TOQIKO#``5TPpmQ-t8<{v=!a%I@>oZeR1&ARsVI0MEmEms7hqEEBLX7IC?7b_t5C6_t{oEw`-wcGarn>#bSx>mZs*3Q|#QIR{o<;RUn zY78YmfCuB+$GX=N0vy#d^CkqZJ~v@z@d1h;ZvkHAvo*D_(5t8B1x!sDTsCJuo_+`e zgexN_>$$J>(MV1Na3 z*=(IBI!n&bQGHi1d&bEs!^gE^=KLjOM4#;s4`TMUv5cw5=rv#QX}RON##JP9OWPRB z0(J#ETu1fbz2%|P`?a-uKF^59Cw*E*Io|?i?v0gQJ*w~G!*R@g>ar7GVyK7t&0T#h z<84xhC136gEMVOzt$O@ldl&F=g|!Iv1y34ujU_VXUB-0tH1<<7wZwhCz-)iVV% zV}mP=%#9iMCB~})b^|dA&GFSNZ8MK`{O-ihXGeColAkeQ6n&-Ln5os%`m&r~>^^s) zMySBkqh=a;4e$l8Epk&&mHTo>J^4>PXHL1}fJEwHfa;NSY9e;S6&)`vbB+#Q&$%k2 z0qXzt2>&*JcHKvCVvDiIw^PqOtS;Q@7Wd={J@msPCrmZ$oewrUlM0!OA2t)m>*3ji zvOgBa6fwaUlXX4|^2OHYT@jn>E7%78pH~LoQL#)v!LHB|CUk)b|888*km{fYo9#nWtSn=ygxJ>{#0%p^G9a*t+O0wkGIm+ke`D*UTI?4Kl9EzN9gn+^IqGd zhx%OB4rDWe1YfsfP2~2B3JE&S532kzFF8G9AWrse(fQ|^PrTOEs@jxA2Mf*uWFJU3 zO*6Ta%N>R+j$i3wXAYgF+z~b%Du1#Hyyn=9tq2yv?f;suubteA)ALFNy$~lj(-Y*p zUV|Iuq-CZL^3KW6s+W_UpO;35AE)Gbm-Y`8a(GrZRH(%2g$Y$zQE$PG{aRme#`Cce zy#-g68YWazzF#X06Q-~>dxVNCHdfff%o_+(E!A(ys0{ggFZO$)P{UOYd-u!EDCn7< zkx|dP-d`B@V>`iv9d004vc?9%t@M2Zp_N5x<<>$q9{*$4qm2;FgcQ`Jdy3%BHntI3 zGCoOgElrCPeh^BVv=`P@FrkP0R4%)kfa%97K`_W)8>S=(@j^fdB8R4x7RqoA+I-nS7VE91LYGD${yk9zoD3oJQOaAvKB z2+oB8k{2Xr;LPZH{qxgP`g&*Nm7G&pVrX^?5ncC7hy>x)DH_lv6%gW10&q&G6 z>Y3gvGd(3ITRIXOM^a}dXQU5KLzKMU***HC!HIAJd2i+5dhhhie%U#Bs@5l04Y5Cm z2vsX)rRCz}X%;(Fuqk~oP8eiXdSJNlmS=%Og*mL@aG@zXJydAUN`?u2Sm{XN8&)tJ z9X~Z3rrBL2>|nb`2-uI05So@YE*9`VO1BMzov)9Ask%-R;#t^8VJQ26IHeUv3#$aS zahl-6CXNysl&&2s)aTj7mVzVubsAi5*?NRt4nt?WoF+u#d)>$a_4KF5 zyRlu+l(sJwYG{lxkxO2-ZXY^Fw@1*i^E=QlYzNc_JJ9{ojTJjEgcii3%dhSMC#iFH zB8qD>cp*kjRd#nLZhTRtw4pok3CWkW-6?pN+U*joh0hTJ%NJ~e{|P^L4@&L6QwU(; zW&e`VlRdDHny)lCE3#o_!USxC{Pv((*Aw7xo%EG!4W^yB$ktb>S36;yxp)TD**t0PBztkqR5!RS5CQpM`O03#? z^M7H{{>uMCz?d}8sOc(N)sxG@kdy=7dY>TnG!2!dX~(5}= 6" } }, + "node_modules/compress.js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/compress.js/-/compress.js-2.1.2.tgz", + "integrity": "sha512-DBb6M4wwe0rRAPeiKQ8HJrWuocVppUw9Qte4rEXiDrc5X3TrzeRKLzpvSE9oZ0Nd4HTXSSFphj3/XWwuptkQqw==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", diff --git a/package.json b/package.json index aa8b71f..2b691c8 100644 --- a/package.json +++ b/package.json @@ -12,43 +12,44 @@ "analyze": "vite-bundle-visualizer" }, "dependencies": { - "@heroicons/react": "^2.1.3", + "@heroicons/react": "^2.1.4", "@noble/hashes": "^1.4.0", "@nostr-dev-kit/ndk": "^2.8.2", "@nostr-dev-kit/ndk-cache-dexie": "^2.4.2", - "@tanstack/react-query": "^5.39.0", - "@tanstack/react-query-devtools": "^5.39.0", + "@tanstack/react-query": "^5.50.1", + "@tanstack/react-query-devtools": "^5.50.1", "add": "^2.0.6", "axios": "^1.7.2", "blossom-client-sdk": "^0.9.0", + "blurhash": "^2.0.5", "dayjs": "^1.11.11", "id3js": "^2.1.1", "lodash": "^4.17.21", - "nostr-tools": "^2.5.2", - "p-limit": "^5.0.0", + "nostr-tools": "^2.7.0", + "p-limit": "^6.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-pdf": "^8.0.2", - "react-router-dom": "^6.23.1" + "react-pdf": "^9.1.0", + "react-router-dom": "^6.24.1" }, "devDependencies": { - "@tanstack/eslint-plugin-query": "^5.35.6", - "@types/lodash": "^4.17.4", + "@tanstack/eslint-plugin-query": "^5.50.1", + "@types/lodash": "^4.17.6", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "@typescript-eslint/eslint-plugin": "^7.10.0", - "@typescript-eslint/parser": "^7.10.0", + "@typescript-eslint/eslint-plugin": "^7.15.0", + "@typescript-eslint/parser": "^7.15.0", "@vitejs/plugin-react-swc": "^3.7.0", "autoprefixer": "^10.4.19", "daisyui": "latest", "eslint": "^8.56.0", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.7", - "postcss": "^8.4.38", - "prettier": "^3.2.5", - "tailwindcss": "^3.4.3", - "typescript": "^5.4.5", - "vite": "^5.2.11", + "postcss": "^8.4.39", + "prettier": "^3.3.2", + "tailwindcss": "^3.4.4", + "typescript": "^5.5.3", + "vite": "^5.3.3", "vite-bundle-visualizer": "^1.2.1" }, "optionalDependencies": { diff --git a/src/components/BlurImage.tsx b/src/components/BlurImage.tsx new file mode 100644 index 0000000..614faa4 --- /dev/null +++ b/src/components/BlurImage.tsx @@ -0,0 +1,29 @@ +import { decode } from 'blurhash'; +import { useEffect, useRef } from 'react'; + +type BlurhashImageProps = { + blurhash: string; + width: number; + height: number; + alt: string; +}; + +export function BlurhashImage({ blurhash, width, height, ...props }: BlurhashImageProps) { + const canvasRef = useRef(null); + + useEffect(() => { + const canvas = canvasRef.current; + if (canvas) { + const ctx = canvas.getContext('2d'); + + const pixels = decode(blurhash, width, height); + if (ctx) { + const imageData = ctx.createImageData(width, height); + imageData.data.set(pixels); + ctx.putImageData(imageData, 0, 0); + } + } + }, [blurhash, width, height]); + + return ; +} diff --git a/src/components/FileEventEditor/FileEventEditor.tsx b/src/components/FileEventEditor/FileEventEditor.tsx index f317ac6..c46c454 100644 --- a/src/components/FileEventEditor/FileEventEditor.tsx +++ b/src/components/FileEventEditor/FileEventEditor.tsx @@ -3,19 +3,23 @@ import { formatFileSize } from '../../utils/utils'; import { fetchId3Tag } from '../../utils/id3'; import useVideoThumbnailDvm from './dvm'; import { usePublishing } from './usePublishing'; +import { BlobDescriptor } from 'blossom-client-sdk'; +import { transferBlob } from '../../utils/transfer'; +import { useNDK } from '../../utils/ndk'; export type FileEventData = { originalFile: File; content: string; url: string[]; + width?: number; + height?: number; dim?: string; x: string; m?: string; size: number; thumbnails?: string[]; thumbnail?: string; - //summary: string; - //alt: string; + blurHash?: string; artist?: string; title?: string; @@ -24,13 +28,19 @@ export type FileEventData = { }; const FileEventEditor = ({ data }: { data: FileEventData }) => { + const { signEventTemplate } = useNDK(); const [fileEventData, setFileEventData] = useState(data); + const [selectedThumbnail, setSelectedThumbnail] = useState(); + const { createDvmThumbnailRequest, thumbnailRequestEventId } = useVideoThumbnailDvm(setFileEventData); const { publishAudioEvent, publishFileEvent, publishVideoEvent } = usePublishing(); const [jsonOutput, setJsonOutput] = useState(''); + const isAudio = fileEventData.m?.startsWith('audio/'); + const isVideo = fileEventData.m?.startsWith('video/'); + useEffect(() => { - if (fileEventData.m?.startsWith('video/') && fileEventData.thumbnails == undefined) { + if (isVideo && fileEventData.thumbnails == undefined) { createDvmThumbnailRequest(fileEventData); } if ( @@ -62,9 +72,37 @@ const FileEventEditor = ({ data }: { data: FileEventData }) => { } }, [fileEventData]); + function extractProtocolAndDomain(url: string): string | null { + const regex = /^(https?:\/\/[^/]+)/; + const match = url.match(regex); + return match ? match[0] : null; + } + + const publishSelectedThumbnailToOwnServer = async () => { + const servers = data.url.map(extractProtocolAndDomain); + + // upload selected thumbnail to the same blossom servers as the video + let uploadedThumbnails: BlobDescriptor[] = []; + if (selectedThumbnail) { + uploadedThumbnails = ( + await Promise.all( + servers.map(s => { + if (s && selectedThumbnail) return transferBlob(selectedThumbnail, s, signEventTemplate); + }) + ) + ).filter(t => t !== undefined) as BlobDescriptor[]; + } + + if (uploadedThumbnails.length > 0) { + data.thumbnail = uploadedThumbnails[0].url; // TODO do we need multiple thumbsnails?? or server URLs? + } + }; + + + // TODO add tags editor return ( <> -
+
{fileEventData.m?.startsWith('video/') && ( <> {thumbnailRequestEventId && @@ -82,7 +120,7 @@ const FileEventEditor = ({ data }: { data: FileEventData }) => { setFileEventData(ed => ({ ...ed, thumbnail: t }))} + onClick={() => setSelectedThumbnail(t)} className={'btn btn-xs ' + (t == fileEventData.thumbnail ? 'btn-primary' : '')} >{`${i + 1}`} ))} @@ -95,9 +133,9 @@ const FileEventEditor = ({ data }: { data: FileEventData }) => { ))} )} - {fileEventData.m?.startsWith('audio/') && fileEventData.thumbnail && ( + {isAudio && fileEventData.thumbnail && (
- +
)} @@ -111,31 +149,44 @@ const FileEventEditor = ({ data }: { data: FileEventData }) => {
)}
- {fileEventData.title && ( + {(isAudio || isVideo) && ( <> Title - {fileEventData.title} + setFileEventData(ed => ({ ...ed, title: e.target.value }))} + > )} - {fileEventData.artist && ( + {isAudio && ( <> Artist {fileEventData.artist} )} - {fileEventData.album && ( + {isAudio && ( <> Album {fileEventData.album} )} - {fileEventData.year && ( + {isAudio && ( <> Year {fileEventData.year} )} + Summary / Description + + Type {fileEventData.m} @@ -148,13 +199,6 @@ const FileEventEditor = ({ data }: { data: FileEventData }) => { File size {fileEventData.size ? formatFileSize(fileEventData.size) : 'unknown'} - Content / Description - URL
{fileEventData.url.map((text, i) => ( @@ -172,7 +216,12 @@ const FileEventEditor = ({ data }: { data: FileEventData }) => {
@@ -184,7 +233,12 @@ const FileEventEditor = ({ data }: { data: FileEventData }) => { diff --git a/src/components/FileEventEditor/usePublishing.ts b/src/components/FileEventEditor/usePublishing.ts index d85d432..29f6abb 100644 --- a/src/components/FileEventEditor/usePublishing.ts +++ b/src/components/FileEventEditor/usePublishing.ts @@ -3,26 +3,24 @@ import dayjs from 'dayjs'; import { FileEventData } from './FileEventEditor'; import { uniq } from 'lodash'; import { useNDK } from '../../utils/ndk'; +import { KIND_AUDIO, KIND_FILE_META, KIND_VIDEO_HORIZONTAL, KIND_VIDEO_VERTICAL } from '../../utils/useFileMetaEvents'; export const usePublishing = () => { const { ndk, user } = useNDK(); const publishFileEvent = async (data: FileEventData): Promise => { - // TODO REupload selected video thumbnail from DVM - + // TODO where to put video title? const e: NostrEvent = { created_at: dayjs().unix(), content: data.content, - tags: [ - ...uniq(data.url).map(du => ['url', du]), - ['x', data.x], - //['summary', data.summary], - //['alt', data.alt], - ], - kind: 1063, + tags: [...uniq(data.url).map(du => ['url', du]), ['x', data.x], ['summary', data.content]], + kind: KIND_FILE_META, pubkey: user?.pubkey || '', }; + if (data.title) { + e.tags.push(['alt', `${data.title}`]); + } if (data.size) { e.tags.push(['size', `${data.size}`]); } @@ -32,8 +30,10 @@ export const usePublishing = () => { if (data.m) { e.tags.push(['m', data.m]); } + if (data.blurHash) { + e.tags.push(['blurhash', data.blurHash]); + } if (data.thumbnail) { - // TODO upload thumbnail to own storage e.tags.push(['thumb', data.thumbnail]); e.tags.push(['image', data.thumbnail]); } @@ -55,7 +55,7 @@ export const usePublishing = () => { ['x', data.x], ...uniq(data.url).map(du => ['imeta', `url ${du}`, `m ${data.m}`]), ], - kind: 31337, // TODO vertical video event based on dim?! + kind: KIND_AUDIO, pubkey: user?.pubkey || '', }; @@ -81,6 +81,8 @@ export const usePublishing = () => { }; const publishVideoEvent = async (data: FileEventData): Promise => { + const videoIsHorizontal = data.width == undefined || data.height == undefined || data.width > data.height; + const e: NostrEvent = { created_at: dayjs().unix(), content: data.content, @@ -88,14 +90,16 @@ export const usePublishing = () => { ['d', data.x], ['x', data.x], ['url', data.url[0]], - ['title', data.content], - // ['summary', data.], TODO add summary + ['summary', data.content], ['published_at', `${dayjs().unix()}`], ['client', 'bouquet'], ], - kind: 31337, + kind: videoIsHorizontal ? KIND_VIDEO_HORIZONTAL : KIND_VIDEO_VERTICAL, pubkey: user?.pubkey || '', }; + if (data.title) { + e.tags.push(['title', data.title]); + } if (data.size) { e.tags.push(['size', `${data.size}`]); } @@ -106,7 +110,6 @@ export const usePublishing = () => { e.tags.push(['m', data.m]); } if (data.thumbnail) { - // TODO upload to own blossom instance e.tags.push(['thumb', data.thumbnail]); e.tags.push(['preview', data.thumbnail]); } diff --git a/src/pages/Transfer.tsx b/src/pages/Transfer.tsx index 02f5010..ebfa6b9 100644 --- a/src/pages/Transfer.tsx +++ b/src/pages/Transfer.tsx @@ -8,15 +8,15 @@ import { import { ServerList } from '../components/ServerList/ServerList'; import { useServerInfo } from '../utils/useServerInfo'; import { useMemo, useState } from 'react'; -import { BlobDescriptor, BlossomClient, SignedEvent } from 'blossom-client-sdk'; +import { BlobDescriptor } from 'blossom-client-sdk'; import { useNDK } from '../utils/ndk'; import { useQueryClient } from '@tanstack/react-query'; import { formatFileSize } from '../utils/utils'; import BlobList from '../components/BlobList/BlobList'; import './Transfer.css'; import { useNavigate, useParams } from 'react-router-dom'; -import axios, { AxiosProgressEvent } from 'axios'; import ProgressBar from '../components/ProgressBar/ProgressBar'; +import { downloadBlob, uploadBlob } from '../utils/transfer'; type TransferStatus = { [key: string]: { @@ -31,6 +31,9 @@ type TransferStatus = { }; export const Transfer = () => { + // TODO add transfer for single files + // TODO add support for mirror command (fallback to upload) + const { source } = useParams(); const [transferSource, setTransferSource] = useState(source); const navigate = useNavigate(); @@ -60,38 +63,6 @@ export const Transfer = () => { return []; }, [serverInfo, transferSource, transferTarget]); - const uploadBlob = async ( - server: string, - file: File, - auth?: SignedEvent, - onUploadProgress?: (progressEvent: AxiosProgressEvent) => void - ) => { - const headers = { - Accept: 'application/json', - 'Content-Type': file.type, - }; - - const res = await axios.put(`${server}/upload`, file, { - headers: auth ? { ...headers, authorization: BlossomClient.encodeAuthorizationHeader(auth) } : headers, - onUploadProgress, - }); - - return res.data; - }; - - const downloadBlob = async ( - server: string, - sha256: string, - onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void - ) => { - const response = await axios.get(`${server}/${sha256}`, { - responseType: 'blob', - onDownloadProgress, - }); - - return response.data; - }; - const performTransfer = async (sourceServer: string, targetServer: string, blobs: BlobDescriptor[]) => { setTransferLog({}); setTransferCancelled(false); @@ -111,7 +82,7 @@ export const Transfer = () => { }, })); - const data = await downloadBlob(serverInfo[sourceServer].url, b.sha256, progressEvent => { + const result = await downloadBlob(`${serverInfo[sourceServer].url}/${b.sha256}`, progressEvent => { setTransferLog(ts => ({ ...ts, [b.sha256]: { @@ -136,12 +107,11 @@ export const Transfer = () => { throw e; }); - if (!data) continue; + if (!result) continue; - const file = new File([data], b.sha256, { type: b.type, lastModified: b.created }); - const uploadAuth = await BlossomClient.getUploadAuth(file, signEventTemplate, 'Upload Blob'); + const file = new File([result.data], b.sha256, { type: b.type, lastModified: b.created }); - await uploadBlob(serverInfo[targetServer].url, file, uploadAuth, progressEvent => { + await uploadBlob(serverInfo[targetServer].url, file, signEventTemplate, progressEvent => { setTransferLog(ts => ({ ...ts, [b.sha256]: { diff --git a/src/pages/Upload.tsx b/src/pages/Upload.tsx index b4f5c7b..b39cc89 100644 --- a/src/pages/Upload.tsx +++ b/src/pages/Upload.tsx @@ -13,6 +13,8 @@ import FileEventEditor, { FileEventData } from '../components/FileEventEditor/Fi import pLimit from 'p-limit'; import { Server, useUserServers } from '../utils/useUserServers'; import { resizeImage } from '../utils/resize'; +import { getImageSize } from '../utils/image'; +import { getBlurhashFromFile } from '../utils/blur'; type TransferStats = { enabled: boolean; @@ -79,25 +81,6 @@ function Upload() { // const [resizeImages, setResizeImages] = useState(false); // const [publishToNostr, setPublishToNostr] = useState(false); - type ImageSize = { - width: number; - height: number; - }; - - const getImageSize = async (imageFile: File): Promise => { - const img = new Image(); - const objectUrl = URL.createObjectURL(imageFile); - const promise = new Promise((resolve, reject) => { - img.onload = () => { - resolve({ width: img.width, height: img.height }); - URL.revokeObjectURL(objectUrl); - }; - img.onerror = () => reject(); - }); - img.src = objectUrl; - return promise; - }; - async function uploadBlob( server: string, file: File, @@ -147,11 +130,28 @@ function Upload() { } as FileEventData; if (file.type.startsWith('image/')) { const dimensions = await getImageSize(file); - data = { ...data, dim: `${dimensions.width}x${dimensions.height}` }; + data = { + ...data, + width: dimensions.width, + height: dimensions.height, + dim: `${dimensions.width}x${dimensions.height}`, + }; + + // TODO maybe combine fileSize and Hash! + const blur = await getBlurhashFromFile(file); + if (blur) { + data = { + ...data, + blurHash: blur, + }; + } } fileDimensions[file.name] = data; } + // TODO icon to cancel upload + // TODO detect if the file already exists? if we have the hash?? + const startTransfer = async (server: Server, primary: boolean) => { const serverUrl = serverInfo[server.name].url; let serverTransferred = 0; diff --git a/src/utils/blur.ts b/src/utils/blur.ts new file mode 100644 index 0000000..ee2b583 --- /dev/null +++ b/src/utils/blur.ts @@ -0,0 +1,50 @@ +import { encode } from 'blurhash'; + +const loadImage = async (src: string): Promise => + new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => resolve(img); + img.onerror = (...args) => reject(args); + img.src = src; + }); + +const getImageData = (image: HTMLImageElement) => { + const canvas = document.createElement('canvas'); + canvas.width = image.width; + canvas.height = image.height; + const context = canvas.getContext('2d'); + if (context) { + context.drawImage(image, 0, 0); + return context.getImageData(0, 0, image.width, image.height); + } +}; + +function getFileDataURL(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + + reader.onload = function (event) { + if (event.target) { + const dataURL = event.target.result; + resolve(dataURL); + } + reject(); + }; + + reader.onerror = function (error) { + reject(error); + }; + }); +} + +export async function getBlurhashFromFile(file: File) { + const imageUrl = await getFileDataURL(file); + if (imageUrl) { + const image = await loadImage(imageUrl?.toString()); + const imageData = getImageData(image); + if (imageData) { + return encode(imageData.data, imageData.width, imageData.height, 4, 3); + } + } +} diff --git a/src/utils/image.ts b/src/utils/image.ts new file mode 100644 index 0000000..98dad68 --- /dev/null +++ b/src/utils/image.ts @@ -0,0 +1,18 @@ +type ImageSize = { + width: number; + height: number; +}; + +export const getImageSize = async (imageFile: File): Promise => { + const img = new Image(); + const objectUrl = URL.createObjectURL(imageFile); + const promise = new Promise((resolve, reject) => { + img.onload = () => { + resolve({ width: img.width, height: img.height }); + URL.revokeObjectURL(objectUrl); + }; + img.onerror = () => reject(); + }); + img.src = objectUrl; + return promise; +}; diff --git a/src/utils/transfer.ts b/src/utils/transfer.ts new file mode 100644 index 0000000..da763a5 --- /dev/null +++ b/src/utils/transfer.ts @@ -0,0 +1,48 @@ +import axios, { AxiosProgressEvent } from 'axios'; +import { BlobDescriptor, BlossomClient, EventTemplate, SignedEvent } from 'blossom-client-sdk'; + +export const uploadBlob = async ( + server: string, + file: File, + signEventTemplate: (template: EventTemplate) => Promise, + onUploadProgress?: (progressEvent: AxiosProgressEvent) => void +) => { + const uploadAuth = await BlossomClient.getUploadAuth(file, signEventTemplate, 'Upload Blob'); + + const headers = { + Accept: 'application/json', + 'Content-Type': file.type, + }; + + const res = await axios.put(`${server}/upload`, file, { + headers: uploadAuth ? { ...headers, authorization: BlossomClient.encodeAuthorizationHeader(uploadAuth) } : headers, + onUploadProgress, + }); + + return res.data; +}; + +export const downloadBlob = async (url: string, onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void) => { + const response = await axios.get(url, { + responseType: 'blob', + onDownloadProgress, + }); + + return { data: response.data, type: response.headers['Content-Type']?.toString() }; +}; + +export const transferBlob = async ( + sourceUrl: string, + targetServer: string, + signEventTemplate: (template: EventTemplate) => Promise, + onUploadProgress?: (progressEvent: AxiosProgressEvent) => void +): Promise => { + console.log({ sourceUrl, targetServer }); + const result = await downloadBlob(sourceUrl, onUploadProgress); + + const fileName = sourceUrl.replace(/.*\//, ''); + + const file = new File([result.data], fileName, { type: result.type, lastModified: new Date().getTime() }); + + return await uploadBlob(targetServer, file, signEventTemplate, onUploadProgress); +};