From f7c45929449fae5e424f754475cae732496ebf5a Mon Sep 17 00:00:00 2001 From: Valdo Romao Date: Wed, 24 Mar 2021 23:10:02 +0000 Subject: [PATCH 01/79] Updated favicons --- .../images/favicons/android-icon-144x144.png | Bin 4239 -> 12409 bytes .../images/favicons/android-icon-192x192.png | Bin 4504 -> 17138 bytes .../images/favicons/android-icon-36x36.png | Bin 1654 -> 2405 bytes .../images/favicons/android-icon-48x48.png | Bin 2023 -> 3211 bytes .../images/favicons/android-icon-72x72.png | Bin 2483 -> 5023 bytes .../images/favicons/android-icon-96x96.png | Bin 3093 -> 7334 bytes .../images/favicons/apple-icon-114x114.png | Bin 3397 -> 9081 bytes .../images/favicons/apple-icon-120x120.png | Bin 3541 -> 9776 bytes .../images/favicons/apple-icon-144x144.png | Bin 4239 -> 12409 bytes .../images/favicons/apple-icon-152x152.png | Bin 4302 -> 13449 bytes .../images/favicons/apple-icon-180x180.png | Bin 5132 -> 16382 bytes .../images/favicons/apple-icon-57x57.png | Bin 2199 -> 3804 bytes .../images/favicons/apple-icon-60x60.png | Bin 2223 -> 4034 bytes .../images/favicons/apple-icon-72x72.png | Bin 2483 -> 5023 bytes .../images/favicons/apple-icon-76x76.png | Bin 2596 -> 5410 bytes .../favicons/apple-icon-precomposed.png | Bin 5078 -> 17710 bytes .../static/images/favicons/apple-icon.png | Bin 5078 -> 17710 bytes .../static/images/favicons/favicon-16x16.png | Bin 1221 -> 1499 bytes .../static/images/favicons/favicon-32x32.png | Bin 1596 -> 2150 bytes .../static/images/favicons/favicon-96x96.png | Bin 3093 -> 7334 bytes front/dist/static/images/favicons/favicon.ico | Bin 1150 -> 1150 bytes .../images/favicons/ms-icon-144x144.png | Bin 4239 -> 12409 bytes .../images/favicons/ms-icon-150x150.png | Bin 4283 -> 13082 bytes .../images/favicons/ms-icon-310x310.png | Bin 10098 -> 33026 bytes .../static/images/favicons/ms-icon-70x70.png | Bin 2451 -> 4858 bytes 25 files changed, 0 insertions(+), 0 deletions(-) diff --git a/front/dist/static/images/favicons/android-icon-144x144.png b/front/dist/static/images/favicons/android-icon-144x144.png index b0463804a94f2be528dc1a20b5d6b3a3b5ab996d..eefafe8d4852f9df83447e8d6e2e0baf2cfc085e 100644 GIT binary patch delta 12027 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 delta 3830 zcmb`KM^qCEl!gf*^j<^9Pz8ig4L#CJgwPQXL8{b%^cLwLMLImC2~rdS0i=qM&_x8L zh!iQ(q{l?sqd3l)*~~e!n8hrJjQ;6V)rZ*rNFS;PCwJ>CSYm z885C<0xcIvlzB3>QuicZ_y3#xg)5vJVeQd@xTO8PRtYon-9-yH<7)A;aw13_XHo%i zebs3*s7{Js3hYQ?$MM)?Srq1$wL*_Mjv;pt;(dScNhH%O!#Fb5*$pO;6C{J`0WpK zk64z*LU1?Z^0dR;_K@|%Ql12|jJTEu0+wjP>!)oN+mVJShohjyx}@1_&o77K|x%#&#!$cI)o{X z?OFe`@5Mac82V^_baX<3NDdNT#Ax7%7>w|1{t4#z-9h z08}&nEgS(b&%F@g59Jf?U+w}9qeD4u;bb4xoV^Sen1AkW9(n;IICn#I)dN9pxcUnn z=ieLUJxLe^#DJjnWg5w9ZE*eeWpX3ER4e+Y};VDz0A68r_-fg?NPmG*A zJw=lK40ainB@Qq&G^9tcT!#KbD{(2$+2vQ29Pk$7oFg^&`tlmxXD-Yue_#A?F4TI# zk^l!*|NJT4kUE(8@o$;Xvjt&{#y>Vz8=kmL^= zX>E|_n;f0p)aVWfZQXwV6m0|M{Q5;kv~L%kgfb{pTWm#=+26K`-7SsUl?y#7;11oyk z8x>He^inK7>nj##r9M=FB$wB3$0JM)QvstH4)Bw=?4@mCbKyVqF z^H5bpV}i&KzVocbGI%F4Qq8^~SR(NWxg*!fe*B0TF2P_kDm|T0K1~>2aryAuAmf)% zPW==6M|k@cmMvcGuO_m4$+?eWZUPP5{tymI;=L94S2WPxZ{M6#h zK`)lkR`B!O`X|q0w9JRFHvl$e9LT3GKu}k-`lK0rHo7!`DhlKpXv)$ z;8yHsp5emGRnJLqK5U5r@X$dqqiruuqm|R#|DvCd>SVkhzqr;OlbhmINAW<5Ucpk) zhL~)O`onTIKh4^>Ix9N9Mv`5-6zz?hcdRsaA1* zP)D&=MEN}RsBzu!M(23vQZ8c7vlcCi4(?FC+eL4=o(rJo8Vrf?X}LO64pWO z$98TrFWILrzW@W(lAWWih!&o+lz%hw*il+Bv;tjB{*7bvoKYTYpZRmYsd*U^NJy-hQ#VE*uw{=-aas8B)5B5EKLVyP&@&Yy0Y#+jMrBEu573{KeqBflQweMV)z}b=8(-+n|?L2hl-qJ>6Am z9doO*rPr&2dHNVeA77b)-ZG3%-)z#c!#6hs^ehp1D5W9IxA3OjD0G55UnP>A+&mnQ zZts+#9{*^so5Y%ZtfOKpwWmNsm4@Y&?9fsQP-8Wq2Ce|DeF%NBs|9(ellbU>V2a)_ zzFrb`pNUt5Pl;(WHSz5{n26U;7(#eG7E9~Dm!9E zPo-w@OS*OQDi0r`?IsoP5JFnbHH5rEv)+QS%eDVNJhzbsmAR?c%*Yb;aAUCzwL}oG zM|R!fem^TB&)GPu4{4|0;uA@q|F}6X0VPx2LmUQ|v<=>qm2m+U}RxP)&dqB$}@|dBoXa z1-5Gilu77gAotfvYPw49-Iun=)_jhHe$({UUQk|Wr4BkX=rNV?i2wg(_K%^r6ACSU|?=a8pi7HBaYQ8tdVnRE8zAZUAH z_QH#9{#}{4YSC~~Icit2VJUyL`7w#`#q$R|15I2?$bNQil3RT}t^(#wSYsVOh&$4x z599yM;CqlJe?tLq|3RGAC45@iKc z>-574-INm}Y~ly}F=-w1t$R19zn8Fax)laNnmQFY!FBq!5weJD>uMFBCK4t_d;$uMmm1 zTh^JkRCDi@$^S~S%8i1t#IFxr;|TcfQu;$2(+1^c2HOvpXe!UwQF1&hDhqb!nF&(m z2i+g|8mcS!OvhXDU-6exe>ZUeaN{DeXcS2EU-sk#ON#Z>(Ih&qHKKK$_1w`40`?`c zzO%@As8^+bHu8sqY7FP{pTUmWo^baznUZVml9HU*?|nZPAkoBt;x77j&Z^ApF-!w) zo`el@jj7>zy=0U1M;&b*?PEzd7?mcy+P`}%;-oao-b-aIB&S3%3wRMWaBzV~cm6Vq zlhf}R-j*GUs4dtXHe>Mc$~2r+jbH=5hM~SqbF_{cgPJxspXH+EYSQ)zkfg9@Y80&u~19*gB2UUJu*?@oagU6oIUi3 zbS&#aFILNryJ3;C(w~dv=5h+qi3E*B+B&zdf)=I0@z?RoRp%IQPUL%Qoi9#s?9Quk zU2q>m{CgXI5W9D+V&QuKd%w>z4m6|gSL{z#7ZexpG;P4QoJa#+39muehqP{N!mV@v4Y)u&&>4|7{*_zP*!3X;+l3pl2)`ogZ`_~g z(bC$J|4E^rhLCb;XGd~6jr%oIVUMJ;d%FEnu-LxpT9EM>V2y)VR@63h8jr2&S@>X2 zkO#NYACTDQL;F7a5FO7D^$qvtc}o{0o0LEETNy)UQf*jYqW|-L{2%#+KAl3`_VNlF z{`)eD2J6`cySW6rtGWic{~cuVPt<*wCzx}VPJt~ynxYVRGTsjh&BO^FQv0PvI)q1rFcp8wrIjF(Y+#Ss$#kZDzd zO26}3I_ey&;TqFrxitc3-ihCAcN}k%n`JNenTxTe;}Enl>TpWMDHj{2NQv9^031eW z0q!76Z5f#mVMQQ?h3-##WzOl)B-f}Wo3~tx8wb88-p6}4qvMgkLnBiuC`2zF&X;}j z8*Uc&jI@u(bIl|m40DEcYbb~2n4$~4_@8P+Y5u>jo}m3=s?+?g=M%Fr-{*_}kYiO2 z8Y{)f%d5ckrJ?0HNl(04wf|lwYEfDM_u#KN{J*23a~le$6zA~lc{V`dq1rv&Ng@9l zaRL(!NPQwc{*U5q+tS@!pHm&`U})*1X|p3Aj2Ce5-}ZmY^(GkL{+o)o54D^aQh%Ev z_P^er#8>eKldv*Fy}K_}2&p6HM<2Gk-h7o<#y*bzz27trpx=r>%R(gwkl#P`I$ zvjcVljP8&&MApQNE5)2|yaGub&yhW}IQ0g|=T|`#LBc^rF7r(%Ck{GBl3`E$=feh5 zT+)6-mp?{bRJtc!u~1vs;Z#66>e&I85Nlb04!}1c`}P9cnI|1SB6zV(h>w*XK!m

VxkVn~;;2hKz%#Gl6{EY;SUc7&}_A{25Gm`Xs=>e1vO=9S$z#jkg33>7!o~4e$jh#9~nw z+CBRZmqzhjI7P|T!R7Zuy%5(1QcEliJ-%@M;LDh+Cr181Q{66D3Lx3Q-0=(-8gPdo z>OS}vp^=~rlYRo6;4_`|DJS)L?gNZ8fM_S#fL>aM!o-fxq{Ke@kv3W;3hDHBnYl2L z^;>43bL6EPwNaTMy}1xoWF4I~X(7Sk-31?-b*WsTFm+-yKEvo^@uwA~trkvxDO`HO zCU6+tcYT`~JfkR|toZ*5{NdYz19N3leAd&@TigSzf-&oMwe=C#g{}pM!DmV>ghDB& z1AQpSH7eA1iM&6TA!Jz@Kp!%64GT4#Ai!i{B$S@X061Q*XbNn87W{Ze*=Nv%Pu)^+ z179*V6KbTK&g{24q}(_9n-ES3pCa*0maHCIL5Xrus(ViaMBYA~faFpFIK(;0%jraQ zdqCna?Lx$>KZ~I3W+{;eqq9}7!X)#zA?H}<@sAhp;JHG>G8d(E z?U7_FGvRWx_`GjzpR-;$AV@pS5A#{>qy4@LeY4bX(_2UQ6W{2pbO}3r&MOce!vZ53 zet{a@s73JmZ@xF~srNY10}6c22czi~hvA`inxdTufJ~7l$WS$W>ngT$P^z7e(l7QZS(QJY@TCQ#;Jt~N%^W%{KCC(#YXAUDl-sJET0!)$J zYJPz~wzuoh75&?5*7b0-+4~4-9Y=5>>9k^3vkPrl!?*7z)NftGEkIjUr0YWtClZ`Lfw@QPm>{j<*ViarqxHHB*&1TFCL}1OOO?h;H7uu z;iTi55w;^PeM%*ziIRezXjLUeJ{F)-&9Yt8py;pkDmH0 z=b?#HlSuyY`Tu%G=1c)Y@X*ioFV4*1r+}yl;^EIZIB37ENrh#6dor9p43Bmb^|}vK zh{*FRatm-C_j<<-@-eNmj^0w_?CF1_{|ziN@J89Oz7ilJVI+B$CA*qbSNsc_M70989eZ`0S;=nV=305xj7) zry)d7{xovP5J|jr#{%C%AXFmxm8mW1`zkfvVWC;RE|f7-QS$Z^jut>g@)gqP;4e`^ zbFYe@DKmct*{(YtRCrl!6l2kSHi|#G+!UaNn6dT~I%wF1i#SR{7zR-p6`6C4`%>kE z)iq;y&YcU`u18dfCd4vb6#g!MT5moq7GFZuFDdHK!|oNyxi~SV+%f+J zHELp!q9)cN&>IyS5!qOX(EbhZKjU;={N|8FV#w1;MbIq~?t&CpR zZO>Rybk?A{Wkc2=llU%pSr(9*8UGKtnitv1jEh$L z%53D8Ffw#VW*`!iZv`Zd2AxgtZCz!v?xhQo)uMM+QPhuIssq`ViK4m+0u~AiX z*1v0{_PqL;8%o6jK$U``qqc5VV}_#OLDceKya^~`=vJt^yg~G66dAIuZ@<$;(b6e6 z-C^;6ff_i{;j+m^mtqT9(G`hI20aQZyc?&;U_M+*6*4TNwkli*N2g<+GBvO=WFv?5 zj+5{ag0)s9Jsb%ydTyeGnze(7;8y|kn&8~sY34cFzv(>bo9gm0YXT^>R6nEdhyZHP zxB`A=YXBaW82YcsGPjLG@WluNN{_J{%|DnHL%}{^abC6W3S29?% zH4~P|#zbdBhoXgX`kMEM-xVX_)dlKAIV98ai#-$lFK++rf$Y_P=oh&%)cNHPrSGky zu2HB#wFIZcvAm%e&qg_l!yNQ8g?_XT65fB~2Z`SxKoicQs4dO~K6>uPwj~ANa9qk@ zUKFps)V(By(kh80+d#??R` zqn5E)RcrV;=WZ`-R|)Fm{cqX*^HQcKN-sGF%O48$Z&ud>^NKMk1Gx_X8h{wR%ef6N z6H!$U2lFObDHu88GAy0FRKIjg3|lNZN{EY$aQ%rvMYs*tHj&cL?0E|aTmUA8#Zh8lA)cF6(HtWa!fVn!iPl*HUC^D2|{uN|1?@eU!N zk}O$g2v!}?=3&9DK}wt<_v`hjsT7A5CsaNqz%po|9wHF=&eFHo-Y^|dfSxEsG^sg> z(AbQWN(c-WDJxYWYtgAq1V}vJZo_;tXDAI}j7lMlN{QBNT_WSBe*zy|5^X6`wD}fx z2)Gkd_ygk_UQYpGqL)%=!A^EnkiDfH0{nTWFPn$fPP1# zW6rhU6kgRhM9y`dGZ`O$$BkS+F{LsU3wXR;Iz8Y!HM9dXk%m*ukrR94H_|xXuVIbwdh8Bk05zJG2Gyc-$s?0gJF9@=>!d(pCOGk+TzX-AWc5C%s@ zl7M4cNBQk=3PU2(=(2)mvDiE?3Yma5oI~@|1>+s&u)U!Sxh}&$8iAJ#7iP#m49`2s z8kGKNMls#DbIk_K!AK90Ed^S?iO6 z9Ka>@&Y!?SD?mw?c(qw9+gKs%#h<<$8rp1)=R1Z6$w%CtaeafBbd9_f@)=W>l?>oc z+|hZ>(}@DhN3V~TuQga4PBwaC!~5M*iU&0hAUfq!Aqxm%C!~I3s|5}d>Z|mx!UeI zi5rgg)kE8%2)pWCH3^BK))!)uQF`$aa1SArAL4;s^!dvbR*GrC=O4ktv}5B%Z*6r+ z2OWg-IrAU)6$X+1eskq$|gEQzwvNR$ucS;jh|faegS8N6MOdOOTg za;;^Mapt96Y>-tV@`I@A{f9!mDD5$*R(Vc$0b>Y#p*tuyc$SUb12vag_CPpP^(qyY zr=f8uqn>s2kK=nW}GLuuqwyCKo+wo&sW@hA}q`0retefs@b(TB2pYILJ!n7UK zp;&D7bd8op#3`u66wIlDvBLnLXKc7sgW zqVj=3*{t@5M&(q~B14kQ8M$KJM7B4Jl(4wQp_HZ(_jYuS8ofgyvp=feMqhqG`#|is zNOP2-TOt4ZH%bCvQE)$K8W*~!`HxyTpFwFd5pP#>^d0K}uB~m;oYL=Nfk6jd?}TL_ zol??u6H1eDm1EhvGLOUtZT2F2btom2iHJ!vTA!_7IaM{qm)8}ePzR)*TOAVisz&w0 z%X*H-c{D>dZ_OM}(q4B=|E6IR{rJ)KWK`PvFlyCyN`e50`IMiO`f%fVdP*P9tmAjk z{Y3L*wtjoC^NSYg#gqRt+$J&J{3R-1vtVzlR(vS8=hhYY9UX3%$Xuw+_LiZ@w4Bj9 z5jUJW{2_wHH(rvTR`uHP=#UNsPJXYLG;7y+Hu73JAnLamPqilp=WI?ue71OFuY6uz~RjF&O zUly_&TMMb$Rc6^!O3Nb{+Fo3HdWi81JioYm?vD_p!u95%J~tQG+VqN@{y3W4=^uMj zOs>u>Mje-k7cmkp%j!Y@u`G^HPiv0Z>gMW>y=s6yg~BF<{PDm)t4d z<$3a2((e{@(s}b*;(Px<#Bo)>GL zMB9+JER_CGC{(#_Mj4hX#e(%Qsdlc~gf~{6!SA^r`6{%x=RxK5w&dfzl>bRdSAy8x zaG+{@;AyD!^JD89TYfG*cB`>^bCzF;3;^AFZDGP(ouuj{4kNswZA+h%JpbEUUWuCq z$%=sc3bWr4@vqs6h)1sQ*ddxz+`;X6XsetPdie3n(SfUNf32ucBe#Dr^&D#3AQ|sD z)pUGrPwC@^tbATJQz!Da&gbmBDB$kWfY0}yF+1@7L%q+LOsbuoBPov6*bEQ9{y|d1 zxTSz)W3$Hn+Luv$f~(%AhaaI41ZVr33|ITP3iq4+5!cU85AR`kpLpMz!=iG9Ajo!k zek+08nZ2&lSGo8Yf1fb+* zm%HXG6)$m1clRUks-_|Td9-`;Z|B2Yl$3F2zoE*Up}v)Qoa^vhp&AYygzaP66m0G zN-P|iu&et&49%$8*rA&sLl`A~gg~zP@6F9GK6{(8;=U)V*4Jyki;s5~)*gN`gX4HS z{Gmw$Gw?`j5#7JN(n+iB>39VAvunN5_1)br*VoraA4M9Ie=NKyS{Pf$KRY;y?yQ_r z3VC|Cd5h*Fkjs2SE3P~dAOCA`@k7?+UYqf_#e0=8Ip>9xFw}HIST=ryZHH%2s6u>F z1g)R*>BL^ZOmV-K15N6y8n+`UqE=RCg)0KxU@={R6}ZqhOaj{S6k!SijK#lC51|)( zlUL`L#CMh!P6i^CI-}Z2hThd2ob`T<`1oR_5Z7bV~c z{nTa-QrmQEU&MUt?v(m%bYxRF=f&#Nk@&}}I*aB7H~9FoZi2rm?u^M|!no3KOsYw( zFIJZ71<;u_0Ag#)8pSs!1lP2kG1Jchwz0e&#%|&(xSzh@2JBkYuf$$D9x-sc0k7?= zcq!vkeQq^vl#*0$ZjuMvPl_upF23n=rM?Z%K$(eVRZ5xQ%f-b=!Hsa>d3~^Ho%H$o zdfdxpvN$xjan3LkZ*y4>uM}n^yTSzFwlO(vi_opJp%v`>7qyJ)k0oUr#KR@;pc0;` zx=a+L7c0GE&0%Y8Aiq5wIr=wcv|m=2gS6b3OSjdKO@|o&L#kyeqoIvKAqHB-ui0`& zi(E#I^G*nA&P7~*p3Ao{aw7^sM8eEO%Js4G$yll0>cy-h#9@4L)v8*oMx@bkC>aJH ztcl$hd{ zGm&fV@)UVpDw!*NXzg=8{SDo7iJmfFMr1tfSQQK z7Q&pJpnv62Pig4oQW1dHy;V!~lj9InrD0uKR5Bov{}JY#qM; zFz`*ue=R!9_Rq)y-$HtgXV$n}QC$3LWL`oHKg3_V@{SdStM_|bB zMUP;PP@X1Q#=Vm!{bgj|-P{O-1|QPCj(CWnoeTa5S9~P|Qz$B`ABW<}uwZ0lNKceo z{})ZRPZ#~x{}Wg%qZg{0$JS%WMyELm_`;Ul`3ieRom8gfbfyJQH_~S{4Nmf!ZPYh> zw=h@*;10q5`R5D7pWU1R<@PLn0EZ251#re_iEq1oSCZ5D8N>*x=ED)lr@v(h9LVDq z0dVkp|EbB^{n|9u=INPxQQXyrhrehn>majdE>LDM?=p&19*nSB>ocrtcYi^Z&rG2@{-4do$XCYJ=J_zMWPX z{Z`RRRTka)B1IQtBgK0}d&ZxG@GgESM{K-bX#WvP;m#Sdx&$O!9cUxD_A#_X1qr1e z1HD}QIzq3Wri=Sgbitg`&IQ#uEOKa+uZG)pIvJHtt6cmmeDua^tI@jdf7et$`7@w+ z8GX`kq&H?z`DZFv5Hp62`93(VBMas6PEbY5D{w)Hcgxdh(|k>CU$i1@YVgdL4=3>v z{fx-1=))GOwZ((Xg&?fzOd;@6;r7Z5c|~>9-o+o}_mP}rscfgwBL{w!PyyT;Bb`8% z9yTG*A+LJ?^_YZ@`B}y8A7sdnU2IPQ3qD}f2%gWeN&6KM!>YV_$WQ0HpRcly+t&D{ zzMh0s{fAJbVF<|H|5b>1`}sexwBJvB2YcC5e3SARAZ0IHS|4gubd&)s@L%vuv!PnP zFSnpVwa4*)Ymd?2C^LR%1$B{q^bnEqs)l{?qJriNE?B$9+|c)_=zk)~TU||eMK3*r zf@->%qb|u}DTjxL6@Oh04cjeK6mVkraiR=Z01p!Ub029v&F3rW9-89-bGHI-_Op0p zzx^k%=70wtz~x~KlJDKp^dnu5#4Iu{jXF}i>)>z{Us&D;Y^wOgNYydX3&?#6lXv`; zr-nFf>R~207pYSKnKdPV3f-%B*xQ1l-zw^5da2VEG&OnV9^7BY6a7y_v-3USgsVWa@^e?dzC72O#=F^*Bl$Oa;1kwR&(0V3A*nu73fX1R6 z_Ws{R)N>bTQ})NV^x?Zku+(jbAZ$uE@1}g&Pp=^6A%c3HJhYyJ3JNl%l^B+dyhNY; zyP0UlmdaX0Smm*mYA)-c7!|jqAWorAH4{;2Z~~8+R|lMr>)Re#jU6wi(V%>nrD{GR z3~jRw=2<}p4-2iay{;2qbjNv4HnUyziC& zp*qR*2%gzRtSt98`PWU{6R{7}Nla~75G0ymj^w{7(uRyi+V3~Zu*vN>uo}3KzRJOB z{KBEIH5;Y@ygoc3NUvQIIXX`TNtxB{^1U9X`7Wy$*ep!+UuJwe2lTwVyKC$I^Y)>o@kQ+YA+A)*zt>&MUWuAXV;2SJq-^!>qBHW>IURX5y-%w%Ey za8a;lc^}u)ziIy$naS(*(`Jb$Vz9jZpXu=&FIR>46`8C>*D2bq+R6!mMJaFk&`WqA zv)RKHn%8NW1!`*qzxec8S_as=o{n6a8@olC69I;Ag0B8mdKDBBI8oQ|hkn9)Pg^l% zl4EhdaQ7gSO=h}Pw}eXkwU(UMm04mpP_bGcehluOt)>nrZ_HyYa)V&&ZDO?sPJP=o z1ojeSzhG~+eE6w0-X1gs+$fiF9ZeI*+koYPRv!%4&blc*yCwO{*v<>>dTkk!xsK2b zgF8SAl z@sBqDrsGje4{)ax?wqy-Og2H;JHKwTTJSuhKy!DZO}wu>!-^EW+UD9Wyc4;l4j(`4 zFK8<#eo9~~8d5F`(+jZa+*0OpQHsX0c^2B>+Ij4IVq!SL<&s$}n1Jf|4Qe(6WuBL8iEh&y4x>d=$I^AZX8Cm@H{{rn~nd2_oo-__KlQ4>7h97E?+jdQkNo%EY-gpU;Sipi)pI~|i4ib{C ze6&A$1)TXtKst^ekg-nen|%HY3S;@DQ<&L9R^>B=#8(vm3Yp8c;`nnaNN070fG2(k zUH9W!DmoFt9J96euL6Q|mu(%ad=}(z0}8*Hxn_WvObvG_25(I9P8!?^F9~OTdTwos zk~jzdXPyiIXSTw8^nQv3j?TkU$fw=G{jRc$6m?%TsBU~w5DkGgGc?`b`+*_kw(E2y z8@G1iKnZ{hT z93Gr#`f5by!|6;$AJnAveCKn!anD`aN@6H}e1V1kvY6)ZAsOB=OL*Fb$E4|h4c8~` zV5scEZ1E}xi7ZwwLlaWb{2NHy!wr}iujlb;0krrRiAYsiH+melZF|%^Ij#cN7w4|k z(#j9+cMrlnFv1VspIcRElIcVb&`FOk=-__WH(BU?HE=R26|*)iRNVIIFd>o|KEnNs zQD(yw(~78|it@1MM=>7W2ty9jv>vy>q~_Ef4k9gx=vQs^?It=V_G)}>F|7*h)8f?9 zuOtsbkA#>*PHB2CpqCJ#&VIsaDVVtBE?ZEN$I&2G8@xUwd;HG-|?^n za=eF}kxy=5-*9P7q7KH519(H4GR5at-Dyr%d#_~DJxvo<%=*V2#q0U`mp5`!-2-el znkag*O_-ipn@GVqLf&H=+y-mfX|9qQ*IWn2IgelQ%<3P>-uT)Y7SZE1{#%h-tSc%q+6i9cTxp1WyNL^4sG7{DNiL z$l!?SNib)yA>+M9P88j!OWobrWWL*HXrvEwx1GrJW}@my9?j~j_9p8DXpY%g zF!o`Quv>>lCg9v8>V(!cc?2C25#==^-!Y7lv}#mpPxx z#tnG}q2e8j0-nx~%upWrB#u;n(*)QpD+OHI%w`goF-Y79; zNM?BOj_oZ|q3o>1q{X;4b>TEO?ThAUKZN{m|3z?TS2XC#QuD9PqgT{#B7i~wsZhfl z*JwRQM-{ChER|77gdBH9KrKQkDK=F*Y%Z&l3ocbn2|Zr#w>OX#c@8&H11x6n{mU32Ef{y;v~O1!vfnsKRh)XWD3{fB-0u7q<#6LCVxi$gEq>E-v1bsXXHo5vxaWPg-2JpX=-K^X z!oweMMiw~zf*Y@YH!XX=R&3V8Hn%w{;3VfN8fC5k5N*MJ7db@c?jyTTDBXvk_?$P) z!ongXA%OvS{JH*!C;t^pWhHdV7-R1-?X*JFRO8zcN+T4RsMi;_-1E_~>Nf;Hr$i_s zCN}W=Eb#&~o%Yi{C+uFdzKNEhup)k#;V@3OZ+-WDtttnFi|8J1qV+}8KB82z)e4Qv z?Y`VFd!12I55i9++!n-tU-F^+d4u5j1*Y^}-{u&D!{G&E^|MMz?_yjETA9xbZJKeM znNSe9YmI~5&A2o8ru1Hk4909TeS;5?ijavKLJxsmww%Vjg_B85mMRr;1U)315i4+) zKfl*b^1%QCc{8T{p^6E&(5Zcvr)LkgPhI;aawrp~rMR(nb*8^Y|rM!Sn8LG@7<4X*I84Isp4{w2j zj#qX-rlKg0GNV(7Cgv_bhT(O3; z_Z|gIqLj3-30E{o1M|aN#YvMd)yf?G z6u-_}*@IqE_u$-4VF>M*MztV{iBy|HB1e18dkoSNwUznZx163Sf`SzuQH(o~0FR^? zkgO^ImbjBjBy~t^;7JoC!WX2J1)h_nKkJx`vit%5R0P;Z7 zDu5E`cc6Hq#1M->1SLRFP3Lu-{2Mi$&3pQKf=#SmT9xAYk$fUZg5O5@-+elSkucgo z?i%xn3!ksEET}!x8W!2v!c?ef2N4O*2AhJW%d{QJQr-ac^LhFubnDMq^kHWcq9;>u zp_JctGK~4RNLdIuN(SlzPQc_8M?{}idvK7Db1cAey0{9}kSuBVp_+PY`ThCnXK8z6 zjZh1L3aekwGGWh*Vf&OJw_QV~(l6oqoceTfzSDLDJ!k*|l$9GKfmV!2J>MVV9&zLY@QMTETkN7I zU{7n)#LjD#tUmn-DcF5v&WRvd?_<*Gze2HA0qhe%u{MF-aL+Z;l+ zATu$-d|~VwNnovA{+7{nZnIrl|I5UuBC~rY2~p90N5P6AqxZP?6P{hN;~eKmi51+_ z*VNz@Sv9~W>I?VbB_O8;Ks7m&$L$EGFgJ)UMk>?ljuK$2ulR0P^X^Da|I;riMdh$D8XnbvPX858MF1%pW29F^*YJ zV`Jd;Gy(Lv8&n>=s|t6gu&6^_8UErygMQWo{w-qi58I!8EHg>rnTJ_{NK3bo{v}7i za(y-!l~S}v9jcu2_L^9uvFZJ(WEM>;#U9{@7yTZ9I15}#yuwl#pP}Dx-kt#BaF$iJ z))O(gUH15i;X-xZZ3*0Vw*K)hYF0Ip^{ir`@F2{n{L+@bM?A6W<}%bD*#O@DulRyz72lc*R`WEjC`} zoX>u9V$I^$m+tv@jO{S&S$WV_%a-XScD+Aw7L86fFbc>vy+Ij{uzKgy8#e*)fA5kA z^L+p1$RgQ(v1xtFo8(+&P*CXCUQndGGF#cQj(yb+hQQ6%K&w`r*&K>B6hXOP2UM7? zgK>E5s)*^b61cR_o;_hmq{4sgQu5hPlF$BR$9_+663FS4X24?Aa5n15H{&nb4jM2M z)*^kjRKi3KeVQx=o6hx>2+f`dQQ8;GPcYK1nSDklFS$QVZ`d0{As}$~g776!T%0jZ zX?>Gx0%7JRTbkKh%R0u`a{0JH@aI0V%6etC%|{(bQiv{T>D(P3Bq_mYi5On@>0}`K~S1ub*XE)aF^!uxMOt#-N?)Js{TgcB>iYV>j=o?rFGXkt@J=>jK1|ag(Dc0*Z9hG77Fkp1 zD{Is}@~i+~K@tJ-QN5z*=E*%;ua-BloQb?Y!9Dh2TFX1POLRyaFr8FDRSF?0IaYHV z@j3>I;>h=TEI>1P`0PzOI(gI+SX>&)Qg?=flG8)~#1GJTJfk*U8jLa&8(^YJ7^0DJpv)^ ztkpSVl$ZvPfoZoa#im%dYFdJWa#fjmhBo>MBnKMoCdo_7mC#yx`}FG$X+0c0o}K7I zxb*kxI=ObbnSr6db^__Pny9+MEKyH^k_-^Ai_8uwQRxD5aDqF=oF9<)(>6oB~ms3^Y5>prXB6au0ccKda#OQQ`P^G;O+) z-E_z5?w6~@f3|uEU~&emwgok&^>r#7t}r~{u6-|Sl#q6s5r8r>=|c&Cot<$804dos z{7w0>!ppOBQ%n|LB6FP_buyI8Op@xQytMTuRt!cuAADS>S`O1O!gB8n43n8_Uzn~mqjR~^9?e(vrag}Jj z8=s5983J??|1a$)-2G=Ye6>0QIAW+WME=5km~*Z~60S7&pS;=f^f#h94cPSIpx z{Y`_FDzEwu%ny+tb=R2hx&suH%l5`!q)aTzWp?9!ol=~rixRZ1Unwlk z=o(F$kCFe;+(QC7or;$u;$#4 zVF|If6tMnXN^$wZOq%xn7JN%L-Wcj{#&L|9#3+re-k;6eT<+Y2m>6y4!L>Sb+W#fm=B*k7FjE<5w%6}mSW(b z-+08a&AW3tj-s<`<_eM)N2|?8K>!G?a4Q?b8n0tBZJdIwAR=sI=eRwKFC_HOEWxm0 zT!7?_yJYhRvRu#|~grmU0 z-_kg=JD!tn^PzFh$gbBZu-Xp^^OziOBsHjUjd^c@VHVqG^cUY)_ibY?r>Rs^3R*!_D`iCZX`73xfnHU=NTFwtrugD-ulKG zypghajsq5H{(k395uaf**LblC9VrIg4l{?OXpJsEh;5-4MKKa)<0 zx466K;#7-^=UTN_}KBa>Q?(8|5o?r~ToL zVV=jRRGmV2Qu{6*BraM}gv#v{r5H$mNJRNby+ zyzSAz%Q+$$Gxeq7M`xcrklH+MJtz3mRV@5up+F-+PoIq$n;rvJHTz)DYY_QV{ZMSBl^bFWm(Zqfh;CNZOL?$_vIZH$siPkX+XX|Kn>N_OIk|I{CZ3vK%g3a{+dI zSUuoSr(rhS5^-tfj@^Zfgcar)DTm1zlyT{lhscIlSOa+;fMne*Rb5q={)#ZU5i)?s z+;;e$+b>XyxL%N4#aU{qSl+6cEw2ghk|`+s2abnu8?~DCXEMzLLxpSk%{TSd=_{T1 zD9t}S)(|~<^g*{br+Eqen30onOQ5q?cl{${UhJX!8uY~UA>};P2p!duU!ZyK)#eUt z)cuM9Y?KiNl|v7GXA$El2IDklu4wcXh-J&^7Z|7k2HVb9cFeX7^*AwQDgNGAr`ARA zA8jI*74E}NLs(q=^pC{|@!FVc#pYcUyZXE zcKswSXxKOc2Nb)ZybxA>IOHZAKmb*qQIj^qUu13?>B5~_OD(XaN72r`%7h#({}K5P<^6;ILMpl`_fq5wHwt19LP$nOYaT&+vnb4#CqZiEs@v;IK-YNYA5W4+J|^vgmF0 z_%F{feX4@#EXU4XRo-Uu@;+D*{wf&pw9Qd=pW<}G zi_V^^*6*v>SJyo;A`fbCH(T0sl1ZMdy?qTXbb&IlJr>fxC&;?W3I85|3Xg88VKp&; z`=%NVUrWa0`)aOLdLMcHlHq!OUoP=IvH^lb)6bKdHKXLry#qyB&UlXeXglz&NnUEZ z|DG92;FeS9pFsov6=)Gb`_D+o5O|wyiGezFhGqjMCGhLSKQ&Zapm45V4o2Ba*PJ48 zmU|hNEr=VYoCWu2pQ9?PS#-J)vs7kFc_d8~?p!r>1Q@d#_2i1bsKblEf3#`efr>*lIRjc+UwErFl0fc0bkJiV!;3XqNJC( z;?btp7aLHx|k&4_J&)WGhU7 z`=7l+4A6}&Q@JRS^SBRcZ49CD9m&B z8#ax|J-lSd=U&TuMlZNyZ@eP)F_IXeL4WUaq4VgJg{z#B%;?mXwcI20A#lD zW)rJVNv22$H=3xNjJv*NhGlN4j@S0^hDkJCH0FsS$PzQ%L0=E!efBw}S@U(M?*j5c ztmNds@8T?^2ZAFHPLp_cK{LaL9GjgT@TO;Xh=|Ru)h(nEjt<|#VraVN;kV);FJO%x zdZ=y)pR5QnMF+7Vet#rY{raM?Nm%#%^heolH;mVIlDt7L&E`XF-4MA_lunm~b)N^} zV56UPIx73#eFoQZo`=EUjYt=oR83AuOGc`-gZGf`y@QkGZi-Ni)eHHOt58Z-nEmDR zb!EtEBv4K(fw!126dxZS?&EW>{IF9Fr$CJ)Var768`wWxDu*XEHt`Il*cdF;b7-Y8 z-L;)YH42_VrW(_G_Or0=7lzvNnvV`$bJxL57Bt|GJY488`YNlr3BZ9R_kr7L_ot@&3qcxA%fDLZsIk%a2zA=dpSSqWSr`RA>WaLf&9%+W2Oa1)b zYiBZ!SI!`rapc78cXTmhD zigq0OlvA8vuEZz(3N_H~7D;N#|2=o7eQuH8f1#~q8iGcWUMr~t;^+D5wW09u zhv zgES)8(TZXFC3ZcdF`01$V8{m>^sSEl^3T5_ji$#VGha^!w%RPvIc< zZmFx*oQv+gJOhd@^5fIdz6CR;%Q=*Dv2M6TEhpLK63LI+|7 zQBR=>HWW4Q*$p{J)BLpuE$cKd?p8R`Rfy%jbY2|9W)6>Y@X8Q+a%q*Q%#yH}QW&-3 zS&moSrr|>pytrKbI5&hIG6@`sboQ{ox%?L#)#n>_yGfC;ycTs4S&NacfPjFb`GBM1 zb{hs`Lv)|>9jwv%fly3>KKDm{$qW8RvcNO4m&}v6Cp;riB%o%%CZ}9B;GT^FR)UB> z;O8}{G$jmBMlmd90h;i54&0y1|Igl6hi*u%1`b?`3hF+{{qOQc=s>Ya?mzDMyOCUR zl1r!jeH#BS_1|av|DUCQKYQKcEqoxuWY>kCi!X8j2df3`KF>TKVlN>0|K0n4>ni{J zD72L74fC9I##T{deS_}9HN5%{C&VvuSMA8tesinrUyCk>?L@9`2YXnpa08x9q&5e1i$t5wC4Z7*)QL^4-XnoZdwDJC75%yN5^>m zo;#K80u9xEj2u9;?r#hmvS!9 zZdq_v^ndmK@6|zEyT2Gd7R&qCV)=^WlGcQC?{C|jmQIu|Ou2pY5pVmp3EwRKREoag zzOt>3`#@B-<@w^ixr_3RVrDVe{d%D+BQO70JN~Cv>AU0h{|rH6>5tz3d-pzInrhdg z1Lpfa@=7EhaTHU#`XpnWfP_-b43*2$+om4fK2tZ(ILGlw(t7^Y=HXj^t2&vuRMlHD zC&b?tj{o;*`p3WT>;Esa{~h(<(qjF&am*B4K010u3&SWQ`fB zDuIhm>2t7sIu)^VoytSA=l4Y-gjvEnmpxSL4mu$f+p%e@QvQ?Hb3cDw-(PomevR&j zU8}dQeS1ar&4bdzv!Cw-9{sRR5H$O|T?#?sJ_!QpsEBgjfA#&9wz)1^-ZOw_Sul9I L`njxgN@xNAz+}yz literal 4504 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 xVwsL^nY{ts@hTD<@O diff --git a/front/dist/static/images/favicons/android-icon-36x36.png b/front/dist/static/images/favicons/android-icon-36x36.png index cc93c290293f18d9f534aec0afc382cb4ee8bc0c..79f0c6abe7ebec763a715a6df60050344bacb306 100644 GIT binary patch delta 2025 zcmVb<(}-s7U)Ypa{4yBS%|Ok0$@UZx}0AB}w0_+4n23kEZUjPmQ z-!DM#0b$SfVfJhvX5_}`l0eZCZGSPa6*vG?i;2g8HZTahT&Qpz7zG{`YV-lSf$tUV zqrl@*ssBeI3dx(mK%s^dO0|JT(Y~hu{|J~a#wA7FSBgFlig|Q7h0J1=x%XHRZ>+s9 z#@vvuk}m>7Kw_+&GS*&4Q zkmo}ogv;LhKQ(7(Z~f-Yw>o{zCSxOFs^Xj2Hd-uQ8?yJ}JSAVa)Hu&JffA8wA#C+oihelrf&MUKPH-6FAR;}V+7cRtq zedCQC&Dq(()mw(fUj5n0TibT-%#AUingeO+Pay<`hK3dessVcolV}{rBuRoxQ`?%F zvQnw$Q4)IqRh3S=ZCX>4u{B2P8wN9DEvhOCXb7|?C!@UMq`JDESAS|X6%hdf-N3k} zst-g&Rh3PfHq8TyR25(qa1{715Q;i#^?IoG_iL7A(&=uU4y1m@LY&47yG`1PqG^d7k5(!x)2xfDeH@ z&nZEdD_udlCUMS@rhh5k`!;Y#L@KH(-h1)hbMfNEvabR}&be})P?!V}!I*9UmJln2 z>Fw*ir`c@MXf#OEGysw$$-ugG>o!&3iBi)66bkNtJRV$%Mg))N~N-|Ua#M`*8c6OXW3`* zouvRp1n)g*nt!S>W+F|~v39%t&{`WON%AG&TdI09TFHdZ0}8-7$JEqRE+QYQ>aV@` z7gnuWHPGMRe@sO7s%m9fpyvexgb+eIgwPO?TfkVQQn@uaIJh>1(4dI&MS*(5);Y(* zjqfv-HZY>9zZH=YV0`oD%^N}pJAh3T2mMoly!V6<=6@Xdc_;)-s_M1<`}e=sXf&kV zZvPZG4r~Etfv_l0=-HW9y8k?FVLfB5F=lmNU*8Mk~3c$J^@l`j18AWf94MeX44J_krJu$h%91{j2x~ec|P~_Dpl;6jaCGx* zvm8wM(ei!aQ<|(Wqk8Y1s-|h0PE1Tpd^#{N;PX7^H3cO4AzBLRCE<8ZX zm6sp$-p`%tz?}2uc|K>pGC^y%+cVC&YrrKDd6cH9s;ahHEsh^QKJPmv&@Au=U;;QU zB7bW{#EOVnsEBiJ?!6bNEZ%$C?KaJ3vxL+T!l$ZwPDIWDx1%Tu-g_2JVGE~os(OQN z?z^$m>5ygF*Sz=ZOY@bETjVkGHqXU?vMfVYJ0fydRo@2QQPuybYFe7;$dMyY013do zd-n=1>Cduk>;3!p56;ZY9L@857sb`6*MC=fopm8l@3CX8ownA#gbF(d>R+2H6{gWioIF7wB#_ij;FMnLQ za)kp24lFzN^RiU=FF=Wdd9~>$U;qFBC3HntbYx+4WjbSWWnpw>05UK#F)c7NEiyDz zGBY|bFgh|dD=;uRFfjC1*kS+x03~!qSaf7zbY(hiZ)9m^c>ppnGBGVMGc7VSR5CL< zFfckYH7hVMIxsMW3t-*=002gGMU(CX@(ngIHa0RgI5IV8$YRrz?FC6dF)c7NEiyDO zVP-ZsW;8K0Gd40cV`elnWHVx6GG=BtH928oVK*~nE@@+LE^uyV?%bt(00000NkvXX Hu0mjfoo&fD delta 1263 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 jpUg|)vr~P&JvOY%@Q_NUjWAJr9bg)DHsy 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 delta 2023 zcmZ{lX*?5*1IO7B8**l&$=SpjGt5vq7h{_H_-u|Llxu{RmCaB&dRj)VVj?QnXyh!> z;i@EODMu*LBY#39|L4W?_IdIBd|!M&pWoYGiS|Qz?q!yUy@<#^oa_xhAtEA%B-&cJ zGv0qM@{Kq^P6ZD_Q4NfX_@Ke*gFx=e{8 zSMFITYwS%t?IMCYKc&Y;I`+%v8XX2N3JcpnkqG!H| zmnPJ)-J!)70L33gu>885!SnkPlEiKDe(y9xN2}R2Z=Mb)ed;HQt50u>Uu-e{w67bG0VJj{U^5ysaEL;JrhfO$(_ zgoFO>oBi#e)Gxx1x_39IcU6S00R^Fnk@vR(!)>{#@!_;ZIq&ztNOW$lMWYY-LksIW zT?k9s0>5}Sv9Yb)!*N=o^@lXLRk_|Q=f~?tP{j_Gq;^osEK-AnXy;Rnp7*%0;An%MA$&v~#t&7cOh{Vj#i2Qy37wkKEZJk%tE{+?YMJLZBY(Ts1ent!L3;lE=d1NFJFQXr zU;?wVJOdK9%tiSyp59a;2S*$q@OSya%v3t#w(^{0g%g$U|bs>toz^ajW8 zy)k?Y=WImO8>7N^S?yDwzsY5)c`f2!CUmPVGcHXkZ{2B9Y_6nSLA1CHc+f+|NSR8B zIb<%U&79VMGT&5X;nnH|!BG9+cH`hj2r6%%_A&|p``cGiwOzEy>1~K{?ojJ!7GF%2 z?a_ZO_QeU%s{*io^oWzIdx09i1+wty3J)$;U*W9-RJ44+8dX}+CCyVbKZ;JI`p85r zr7D@ZBy6{TItpu6E3EA7`i1HEnQ+*?tMs!V2!{CnPFsCnt%R?{%qy>%nUP@}&65l# zcXtx-=F{_)l2%`Nnf0rTSd^V}sqYvZX@Kk{a|@0@N!YNA!uk zS?{dpjWiZMvOA9%u!du7%ESM2r3N$2e!R^4l_?^0rpX$;CT> zIvFBq($$~%jIz+7DNz8O8lkvuR_&QJ1>{6qS!>TpZ+5M^(H9+7q3Y~|CNrH@DzoSH zImKyGNnuR!@e)-R8R6)7g&GQ8-t1Ap18 z_Y*fUP0BdB2F*8?Ul`bbQ=9!Z2G2azPhx`a0P;0xe&z#Lw|-AA9HGJoFQ^wq_(O!J zl3UL44{MbjNj)@{Cc5H{vJ*QEx|^zqn8#uX5(@zIU^~Kwo1RYUQ`coUUSMS`$PLde z&e=|utV#lgT0j?C@w#T;X^$DM!4TbTKa#khZ%ZrV$a6vyKr1Q^)iDN-XN_x|fz;Tv z*eejfjN+=;Q`+=%{<2!gp3-rX{4<4ZLr$Jsa_)WXtTkxk`&$?3M64q|t2%_)3(8F- zQD5iaMisn5Q9=Pt!~N5xWdJF_!4Y+_X6s>w#ssH=vxllgj@waWg-wR0)y zbrrmx{LW`z=Q_1s4-fB?;%E%29Rd8SJs@MsQolQoG|sD&g(+ju7#td7>W;yg85o&i z@hA+&41@8A$*|^4|7X@Ziv7crmD`dsHZ{QE4Y4>pTC?sgPf?y|`R|^7kg>t(5F@-X z4ucJ*nV6h52sAXs1kyqRalxhrr}4&mXM&>j=ux4BoHpb?k1j$aoU*O84&eMB1ILP} diff --git a/front/dist/static/images/favicons/android-icon-96x96.png b/front/dist/static/images/favicons/android-icon-96x96.png index 43324e2c02b98a2a5230a59d167979addbef1c7f..1a5048088a7e9d12ee967cb01c045ceed11b4045 100644 GIT binary patch delta 6949 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 P-hjEu72{Wi?(zQv2}%3r delta 2675 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 NBH$gYXqMgy{{XbX*V+I8 diff --git a/front/dist/static/images/favicons/apple-icon-114x114.png b/front/dist/static/images/favicons/apple-icon-114x114.png index f205a3ada377bdab3e75beb06330727312702bef..0fe16ec58742918975ff08ee35af0bb63df5a7a0 100644 GIT binary patch delta 8710 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 diff --git a/front/dist/static/images/favicons/apple-icon-144x144.png b/front/dist/static/images/favicons/apple-icon-144x144.png index b0463804a94f2be528dc1a20b5d6b3a3b5ab996d..eefafe8d4852f9df83447e8d6e2e0baf2cfc085e 100644 GIT binary patch delta 12027 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 delta 3830 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 Jg{A}MKL7@B_@w{< diff --git a/front/dist/static/images/favicons/apple-icon-57x57.png b/front/dist/static/images/favicons/apple-icon-57x57.png index bd72841f5166b2a4211de6d9536a23deb5d2a060..e356d09ebd0945fc3ed2c733d058c73462b63b0b 100644 GIT binary patch delta 3433 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 C3xdV~ delta 1800 zcmZuwc{mh`9z8Rbv6C%puCWd?jKSEVtnn-%*(D6py(GL>+?Z05+pRE`lC6^HURD*=K9`u|9pS^zVrRgcg}bIIcGw>cuTIbhyZ~0C&_p6G~L-m zp0|ge^Cm$AHhBR82!Id)62g!cg#-yAL`_cgfIbQ6$H7X{4~A{ zq3b4M17K~8)^e!H;YKd@?!t0E)_=qLI7&`IR1m6isLqAID?SaO^fWepV`JeN-gV(m z9#&uAKm9ls2PPFv8qBHKv^Yk-KD zA7G&uRcB$Kh2j&~7(-zOgavUr37lgP5yYuPtdHVxEn)&-L4yzx3<{$5;@(wM{Rn9Z zh>PN4CiuTYRua-;*qFq7F4`;cPZq>Uh!4dF9yC>;rHZoCXsv*<92=_g_%w{g=UD2; zQXlFHp(G0xIV|^~>_-P-bB~`olEz(cAIUtV!Jun`?+?_y?7Ts zL{}m$GK-^JcY9BS+^m_Qlm?%Ba!zDEM=VLIYCfkuK}hfQl@C zJ)W0p8~pZY??UhO-OKUcS5$i{RqH$ZJR+Lwx{B+1KI~sN)-94U`~IlG(lV3M^~*(R z+R?;i5%jZF=sPidleDO9 zeo**klmDDbl4j+3>8Bmvc;6{6JaWDzLqNtOTI7({D^+Wj*7g1I9#w9~Jxj=!lf9d# z(abOW?rc$A@c6B`!P{jPiA7iD!dOaq9@&c`TMwL_8Le%eF1ZpG%3=o?`_8;5n=1nNNrjuHT#vftuo0e zPn}uH+miYR*XP>Z&Tf;auE$L+6UF$kg6<=jmAk~z-F?ZLme^Kas_7b7YULjOy-*{~ACeu*b7xQj5urB5NPbug8#|jq+N#WI} z<~=cnkblFs=ee}TP`bg#2V;3B{9+ugHio9__wJlFT`N#y+a;OZ^OjY&$skY8 zBpkLoqPiuO@y3rAmrnUZtN&8-yJz98M=4?a5-KZ3rTb|>CF=JL7L$WUCA8bN{Yzl~ z?DUxsu5C_2?9`IZ^8CG?H7>1NZwHqye$g~`duZ2IZ@VU*M#i6S^r)!|R$j-(Tq&W_ zd)}Jb#En@|FCID@`DKpz>noS_FDV@;x*uMdr7^`)=vk%QNzPbjItr(`43_U`7+OZrm!MXqOGHnqBj8yI)hH5Gih`yKRVOe#LSvuX-KDA)9HH;|KRYq zfuk)cqw@b%+Em5{$4l~yq`8#|)6$f|w4`Y@^mFz|x$XShU>s#`5*=e^Y0jiGSP>Q$ s(I%0mR`ke-m`Ene$|Ty-+$b*UkWpentaCxT?q(p|oV}dt9rkDb1r(LEB>(^b diff --git a/front/dist/static/images/favicons/apple-icon-60x60.png b/front/dist/static/images/favicons/apple-icon-60x60.png index 89238b40509ae1d3d36f364d2dd405a11c5c02e0..ca4616de04a6e694305794055b979ac264290c18 100644 GIT binary patch delta 3625 zcmV+^4%YFn5yBsk83+ad0027t*>aH~OMd_cVoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB601Y)sL_t(&-tC&pj~&N#hrd(R-S^Im9Fn3q95RwjSrQ#Nf$flv z;v|R!7%-e<;YA=VCD?`oB+KMK2@GT!#y$!BH82Uh5Bw`|6Yyz)Hnr$pCB75D za9iyrh!UAi!1pB(gLFyaZ2^1I{UGotPyjt(ov7x`v0K1i;1OUW@lO*~JAbg%baqU3 zJBDvQ{nYS;rji6oV3{b{b^;Q(ZKuArQX1o68mE(hYh&#F3G76Az67kMF>9>| z0H@A>^uPg2`e+xpp1`(%r-2hho9!gYbcvE)0X`wB^5ejt0pB1>{W8%`Yl&$C)PCRy zaDb>uC9syp^!EdJY?T%f6@MrZSyHG})uMVRN$klq);nntFvfHgt^&)d+C%k^fM?SG z*OCU*q#`l^mJ}9HT_(F)?E6G@)er(kL=c$(<^-Npk$tFc5VhmK(|_0-?1Bw%3;2!K zf6kwL=i7X8;R3TWd$P$^F`J8dLW~?zYgfhEi`JN1s9yKZUF!`7tKCjVrzR(-@+>=? z8TpDaW{((qQL^keK&h%XtFpX)`SNA`^plS{`qa}L-nY-T^L&pHvkzs-Ef-#KhndFKX_ z=bhKy`Y*vdPQCwu{__uiP)<9yW{tU`dA6!qzHIXRy=b_Dsk?B2(GGReSdMW*tmH!2hn0?+RYp|;3j5f zych$jb!>M;d%i)879Emp&(F{A2yhO39r$hn9srCn=bGoa+AIqq;=T8jWogR6 zz*bddHYcC7LPUZw#v5xrpx(JWgkTfju-Rz()+WEIn&5q=swOR_h=?J^?nR>GJ41As z13(WQI)Ah+;HJ|uM6dd7;7d&=RYg@bgaFP!9#;3&8vt&%~odG!K>Pbukhl!ztTEYnc-g`tOs%;2(?>DcZ zN$nbi5H_#0FdHJ$1!hFVCX>^IVLZT9RW%m%&VQ8>5sWdEWr;~nsa}8@cnINvfvu{F z!C*jHmV{{g9;ig50)nbytu8VqvM$JWQ4SMdm=kAbj6Z9RFY?7v98g<`Isx_)C zOFEqnyL7-Nkw6YX|;YI1VYs%p^NCQ}3TM0MW;nBB{SYv~R@+YOMax~WCp zdw-mBR8>X4-)Arw;JvpZ($2E%k*TSv$BUv6Yi(adG&wa%POu_+@I_T!s|A?>iIoP-c^W6Xh~D8ABew{4bXOCsV#M2#`E zC$Ec~%fMSAvc4_g8niKnq9`bef-H+py??4IoO4@%-!EY29K+$wT1%_d+H{UUVT{?A z=XsuG*_?sdYUC~5+$uYcj^ z_2ll?Q`ca(n+LZWghoX26zeI=vi9WlJGr&?XuI7$sc{wF0T#BcmhLu*QG88@vlANU zo#s4}jZ1xZ&V{lpwcqcn_rCVxQWVABR;%^<@fGa>VW&N|6fuqW$!)xejo3>8e`T~^_=(qZB>0c zgs^0-&9W@p-)gmvXIb`wsnpKS6Bu7Q`xWe89tC>jYsqN%%Y{6#+fq67Z&~ z{)dQsOzd5+Pft(x+U@oXv6DJSZ1=Y6*T)7tTpiW-yYW6?3aZ*w)oZHyfrwn_^?L64 z=bz`sjT>!ZpZx2@GWRiJZ-0BU4j)UUYc<{u(1Qk>oR$%h0+_JYPEAfuP8>aYw70yx z?1?)a-vIuG*cvVp;OnH`*H{v+y<+`)6tLX{yG@#EM5M6R&a~U@12Z!-%UPCny!UbH(FL#LBrQtf!sg+{`u2s0H&JR+eO*0x=03#xkd7dAfoSZz?YJar~V@wa|h{zs| zyCIWAbL|mx=<~pWh^*XmSb7j(httOqC8!XqrTs-w950HZFvhHiNF^dY;`Yo}Rdtfs zguDs73H*w@T}InDFjkL|Q}voab6 zJF0tfw{NJbSJi$9VHH?W)t+-M^!xpyEXxgR?XQh7zihQy%YUj`?`8W|tHp^ECw7*2 z>=vo&7Ld-laka>)>YDfdVhG{9s^0S6yRs~cUa!~l-oM%F^-do+9>Cm6a9u$}6vI z`Mw!7r`-lQ9Dh^qR)CD+Z7xLe!Xbpvuc~TsZEfvbr_(trB4@0%r;RaZvMjqgF)S>h?Pb$F5qF?Ykx|zSw;>!r}!e41Go}G=xuCltSv1q<^6vDfcO6B&>rj^QXWnicCwE=9GZ+VVvOeZ^-?LO+ge^gbcG#(4Q!)rIo#@*Od zOXNi4mNDi$a9%_%8e?v%YTtVw?qz)V9e?rl)mLBT%$YNsJb7}eD2m5K4 zC6%WjIu1>iGdOn3kO{eEL(gBMK9W(2^rWgvy=TJ=tufqjD?EnRA}=Pk(&R`+VQ?z0do{@B8kn*HSJhONaoF*{x@t ztKrBeK6E+4wigZvfG7c?01+S&Kp;T4rmlnoCisg9)VD#L03rdBqS&hmD^naWg}E^( zQXm0hL}>58emcYmIQcDJ_u)++{^&ud2PBBtN=8{076!1;kEeG)*M$TLXa~kVoSb2gdUpQGK>pS%Pg3WP!9z{zj(xPEy0&yZpL|i<}hu{S^ zrV-0Qqz_(p!_)w|33&YwQ}-~{hUt4q1IiJ}xNk3dWW=`n~2fVe0m#gM^+ zgeZ<$;Y*mZi=!|ZOHbh80BJE?;=`SVZL*M+gqk8Qr(yXC0$ia;249K45z#xEs2@}e0ql3ONjD=i5{E} zA~6g@b%^i*g^XV=V)-dVh3sYF>jHTh91OMjX#5-G$+(%1 zjVXK@N6jS&W+8ZvXg_4dVto=8#)uC=d?-!_gJB3Z3unS`CKM6AP*nuW5(#054Fb~$ zc1*;DKp+sXV%}x^_oM?HS$1#T@1^Dl8%eIi@fe{x{I9<14L<;gxbhF#9b`LnzkT+H z?}+twaqewtKkF`ASyz{>N?)aweNDTJS%y5oDMxm(5Vml8plZYjfmZEy_zp`epn@UI5bS~gR~ z__nlTC->%$%E3x{H3g)7{5!w-RO%bCKU9(BuI&$RzFYOph_b%p@mkLrX2a{%9!tx$ zXVvLaNlU8#jtdt2kkjfGI#r>^{_YyFIFtG4+oGtx_FZ0&Nli9mIg!&kc5M6EMzspt z%F?50X{y}ahochIj9yvt?21G-FI%XxDJ^!x)_#K2#R0F;i0Oo?TUYtPwKHdv#aQDL zcOrk?EIX*Wk(|=1!U|u!(y?7u@@pga^W}-IaYf$E-H-igG+O7zj*fsriYAA{VdaoD z{P*cKMtNn&U8-E~){VPqHU|-nAXSzQRoW_IKcy~HLC1-1k(H;j=Q_k`c^2B{GSqL> zIu49n8!R#pU*17<66K@+GOK<5gVNsq9TOv$|&3Wot z6FGGz(F?;8vj(jKetSf#;6-`2w5Ijo9_P~eH(iwZtF4qWmW*KCer>UR_q^gVz1Y?{ zQG)dR^Pgr$ug=ltw#+(=G|e1dolXd4Z{5Y&OuaD{NLS>q+nG@FZ6b3j27O&RJ}yn3 z7JH#?RoPKH>lzY&zf?XnDl>gOY3|3v*_|TEo-$df?Ngh`;9H7(FbxZ+qH8KkCFhEe zB&E)|>Nzy?R5&3&4F`~8_1L;HYWJgK+%oILZDRtD9M?bheczEnr-OG?%M9~eLeX?(E?1&sR8~c0vLhjv>>LLQ79+Uh#L`X TpWmV*%*G*mC%YOOzjOZqfQGnM diff --git a/front/dist/static/images/favicons/apple-icon-72x72.png b/front/dist/static/images/favicons/apple-icon-72x72.png index 9ae8c47aef9accdc911bf95d20fbd09ee867b86e..910ad552c889adfaaf06a69feedb7c5061f6695e 100644 GIT binary patch delta 4620 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 delta 2023 zcmZ{lX*?5*1IO7B8**l&$=SpjGt5vq7h{_H_-u|Llxu{RmCaB&dRj)VVj?QnXyh!> z;i@EODMu*LBY#39|L4W?_IdIBd|!M&pWoYGiS|Qz?q!yUy@<#^oa_xhAtEA%B-&cJ zGv0qM@{Kq^P6ZD_Q4NfX_@Ke*gFx=e{8 zSMFITYwS%t?IMCYKc&Y;I`+%v8XX2N3JcpnkqG!H| zmnPJ)-J!)70L33gu>885!SnkPlEiKDe(y9xN2}R2Z=Mb)ed;HQt50u>Uu-e{w67bG0VJj{U^5ysaEL;JrhfO$(_ zgoFO>oBi#e)Gxx1x_39IcU6S00R^Fnk@vR(!)>{#@!_;ZIq&ztNOW$lMWYY-LksIW zT?k9s0>5}Sv9Yb)!*N=o^@lXLRk_|Q=f~?tP{j_Gq;^osEK-AnXy;Rnp7*%0;An%MA$&v~#t&7cOh{Vj#i2Qy37wkKEZJk%tE{+?YMJLZBY(Ts1ent!L3;lE=d1NFJFQXr zU;?wVJOdK9%tiSyp59a;2S*$q@OSya%v3t#w(^{0g%g$U|bs>toz^ajW8 zy)k?Y=WImO8>7N^S?yDwzsY5)c`f2!CUmPVGcHXkZ{2B9Y_6nSLA1CHc+f+|NSR8B zIb<%U&79VMGT&5X;nnH|!BG9+cH`hj2r6%%_A&|p``cGiwOzEy>1~K{?ojJ!7GF%2 z?a_ZO_QeU%s{*io^oWzIdx09i1+wty3J)$;U*W9-RJ44+8dX}+CCyVbKZ;JI`p85r zr7D@ZBy6{TItpu6E3EA7`i1HEnQ+*?tMs!V2!{CnPFsCnt%R?{%qy>%nUP@}&65l# zcXtx-=F{_)l2%`Nnf0rTSd^V}sqYvZX@Kk{a|@0@N!YNA!uk zS?{dpjWiZMvOA9%u!du7%ESM2r3N$2e!R^4l_?^0rpX$;CT> zIvFBq($$~%jIz+7DNz8O8lkvuR_&QJ1>{6qS!>TpZ+5M^(H9+7q3Y~|CNrH@DzoSH zImKyGNnuR!@e)-R8R6)7g&GQ8-t1Ap18 z_Y*fUP0BdB2F*8?Ul`bbQ=9!Z2G2azPhx`a0P;0xe&z#Lw|-AA9HGJoFQ^wq_(O!J zl3UL44{MbjNj)@{Cc5H{vJ*QEx|^zqn8#uX5(@zIU^~Kwo1RYUQ`coUUSMS`$PLde z&e=|utV#lgT0j?C@w#T;X^$DM!4TbTKa#khZ%ZrV$a6vyKr1Q^)iDN-XN_x|fz;Tv z*eejfjN+=;Q`+=%{<2!gp3-rX{4<4ZLr$Jsa_)WXtTkxk`&$?3M64q|t2%_)3(8F- zQD5iaMisn5Q9=Pt!~N5xWdJF_!4Y+_X6s>w#ssH=vxllgj@waWg-wR0)y zbrrmx{LW`z=Q_1s4-fB?;%E%29Rd8SJs@MsQolQoG|sD&g(+ju7#td7>W;yg85o&i z@hA+&41@8A$*|^4|7X@Ziv7crmD`dsHZ{QE4Y4>pTC?sgPf?y|`R|^7kg>t(5F@-X z4ucJ*nV6h52sAXs1kyqRalxhrr}4&mXM&>j=ux4BoHpb?k1j$aoU*O84&eMB1ILP} diff --git a/front/dist/static/images/favicons/apple-icon-76x76.png b/front/dist/static/images/favicons/apple-icon-76x76.png index fbd0c29c2005bd9c8e541205a36357de5dca4ff1..f681020df642ee19e80fb288fae8f0cf841404c7 100644 GIT binary patch delta 5010 zcmZ{IX*ARi^!{hYz6=K0mzp9Zj6o<$hB6~EWtSz4EnAd*W|U=wWShv^FxCcNOR{7u zQe)rOB1Iv_zVq`x=l|yS;`ip>SI>R!x%WBGx##BdljUAT;T6sUfWmoDAm#c~0N_nE zKbR-0gFHnC=l9$0A->9d=s;e7UAypwTD72jzBgftcPI?po;>{ z+__JrDAC4zEy3WHOF)z8iZz&i3&;Um5M%wVFi*wUNKRy*vR)b4<+`_o)q*jNR1nUbdv*{avbor(h-Lv`zWOFj0=PsUUl543zb(eOm+##T?@1H`)}hmn3iZI|az zn&7i)^daC3Zp*vX8~lTb9&p+qFRt)Vyzx%G#6DW6O1z^{h#<+Lgy+jPfp9prg;6VR zuOMHs`^$)K=a`of02!DG>OagkIqKE?`^iMIsKbn@=Y`Oxp6t+=feASZ`#`+`%JaGF zMF0Z{i_&dgdB|&W$PcST4uMQq7)9srvW2v;RExGXyEW7PhwQ3I>C*2jZ@LY(Fu1|4V$*}g@*=6;6f z4iFfDY+~rk=Rpy>J|*lm170nwef^AwLBPRY`S&l^ZFaR^kZ*jnEtIDzQ-_Mc-OyQC z;QZYZCh{b5^WwVUkMK?~qTG5xb2+o$DnSP05WjKt|^Pohrr7^pTFha=AuzI!`U7iR0iFyv-eR`e%AGU`tbvAE|9kFl%$G`K8 z=>xwQaR*<@r@DScWC=?uE~TQ<-G;+4Jt9?I($?SNY-uSI{2sLdB9rm$!!;u;wSwOS zm*!OP2-V+FCBv0(gkKA{ql(1`=9x|9%ya$G$jr;Mj{S*=dA!}>8<$&A^m0GH^{&y^ zQ{25quNN*Z>Sa6Mp=^r8z3;0hU`BN|DNj~9M&_||J^T8;A)ej1jroL#<0WRFt&SMb z$ne_U-riqHf^#+j>fLb%6RE|jqTb7J2Wh59H zuU@;RYRP46SYLQD&$80SjXQg?xzaG6+N&K#{Pq|-R&>if{S92dwyH7M*MEMBGW)m6 zll9f_Ga)N~UA~R}2+oLy%e(+j2pw+p;z*ZIw($kU(>N5Pi={_<`gj4#+1EAnaQ-~L zSF3iLK9?}LV2g{)3Eg*tTQXBrE0(O5V_--|KR>@{7I_f5bu!T*&@qdPnW|bnQa$b3 z*PU^t3Y`nBqiQde1?a*bF$T=@B!n39xAkviHt zSWTEd{FC=LD?T1B{kv<-)p32Qu6w~Q?8(NSxt(Xa{BtE;Br6cZ`l&%%sxfS8&Pz;C ziDC7yyVbjYh$9WooX?+@ZWSN(-O8#&#oQcmE(!I~c1T+Kn>%&BQl1Y6M&j#}-#Z(` zQ!h_j*!5avES;I0`dW#Kyogv7T2S`5znfoM9%D zJt#L#6)MZlzpG=6XNKy^GRsP{#t@^3FRS8@h^(-r^@ZnqV|I)Y_%f9z2dg-q8S(k6 zKpCT(wP2z&P?)0WKjs7w>Xyn}cR4?A(1k%~(#>rl4c!{iA8x|H3pTZIL=0;%2u^9iLV zhLW}Lc$@MJ(_muiq~=_o6OqNgh0+%rw2G~iHz7HHdw!k_Zk3IytpgKQrs{M7`fRi$ zmsP|HqixXd)CVoRY)eeb3i^X2<*9=pD|<+qh2XoV4fuGYLWZS01~xAymee6+inAqY zA;<{U23^*^0pFH}2Y}|DPe!QlG;vhu;B(Y0`lCqBF-C>Y%}OK5GWFXj#5ha|IYs2# zMyP!61_@Jgf&2%wY;d19@l)AV43QYR*?;SU&urU+1}D2HOhvHNBZ7mrst=(-@WW_S z!J;eB<;y*CSdA!L`->y^WxjfB2E_@qq6@XWr>q!$iN@k8rUsYyGto@J{lwFv?t+Aj? z8L}WbYtkxBs9OOuQn!_}x9unI4c&%5sMHO1dV83vKK;aZGfF!-jBaogo|JVXu<7q^L%EJNMA1#o`sg1mrFl~5iqll&M zKzYYAuL$?=sc4UeG+7*L>)Uf#Y2z}~5<747s?0`KwV)>36s5(Mi~rVjp0&{ABG*`L zIL2B46w7NylHxPbMb7K};)boq5N&u9JzL*b6-6BNL7Cj{1}*()ruw+8 z0h54F$&8nNOC6I41vhV3GuT!7+(9h9NoK2`{CB6duxdx_ml%7+{gQ#Zl+jz0(R&9kMQ_%n604C;(;BrGU;#+v z_rN(w=vupAky&XTY+Z=3+L}YsJMrMP{l>*@@y1reQ2!^6pz2y1^_q+6?0K@-jXScc z_o-$ZiHZ2cpXrXLtk3T@4X+#%?eIK7#U!>I{`_&RGApe~uUq!l2x; zL35XOmvy^^5Vobm5+%d#Jwk)$$QBbCT;);c>G((jTydmr#VshAI1#P353Szp{D+ea z<^+4S{B(nr$Wj*npE>kycv82V)D7JEOgLIB*S6f(x{_u~r58Xp%syE}66leH+w0{3 zp2yc+`(lQ*WgmlI$x`%dMyf1H|G}|Ip~H?lyYa+DAe%QavF}S;GlDKmNSHP5e(j6T zoN4HVABVA&3s!57^FA~GVnVGsJ1 zlJ7i^c<^Btw2$q1LQ6jDy$CuYVeszbLVlMbj3B#*qauWt@F-d(zgW9(Jm+e_Szq3v z2KA!}kRXb8xC&mxrJ-Col03FGqM@BEE^J0g$qbc5Ee`dV_~BGo~dD)Q^LI{S|5_{~4$6?I1U= z!uAP7oGwyQj^gD;rPbAG9R3t+7V5OV&W7f2Hx^IBHB&ID&wO}HmlKgM$`}x}v1AKx z6%h`Ya?V1~C7SYxRjJRq_r|q=*9s3rEHvS)6*@_&^Y6J0@z2F3adN5P#?3Tw)_PR2 zCaYfVBix~xr)Vem{#>PeyVX)!ozuK@sLdFRd=qJ7!f!*OFkUwtSnJKXo{)3AS;)d1 zH(UFCcO47CYnzJxFuYnVI&dHi3AuJ@nTbGLDM=Q0stRt6(|h7F=<%9v9oZt-Y< zyP|rRnu8P@@i3M`jOga~zWg4t8-_-D5j*u@`+SG8{jf%N&0SO$A46AbG?Q_}>OG)` z*FxT$tf&CGn$5Pp0EkoC_yof+y^2*F5po^GWDG35EgWSr7}G(~s5MYmF3i2NVx_Ik zQ^99!E4>&jq+muJP+<3~G463-%TJoY#>NIY<#E?kc=p;~qC;b-o_{Wb( zKy1*@vY|h2;hvV%V%E;e2~#ei%uY`#ytD4S{{y4|@|Wh}WZmdz!8TQB30Fc!a|8P3 z#6LB;RQ60E}&{LTczqk-8F@&SUzS68genu6m zL8&h~&tQ*}(iLBHUWShICn|%tMy$QF#O$l8sw`}5I1dfvKVq?7r`pbg zY3p%*M#$~*?lJC^PZF7%E5sPlo8OX6QJyi^XPY=@q!H!H8%A9u365eMLFy9oLgsn0 znI-A{;NRtU@7`6vfB)WH&m_}%9syI9sC*Tc)6bnU$Gm)$%_ryk2371#8G;e4k(&^N zB78#tA^3PIe23vtaIgqaQHigFbqEFgn^4Wp$x(bk=AmxI+Yi2>clbZN-m;zX2e%{1 zSe>84V-iaoA^wquhUpfc(0e^k31TW_m?h$$FwxURzL3eoEB=_p76jr zIXPK)i1*STwsuTw%!Z^q&gPFSaqG9b>S*{j#hs_0ncxXqbh4a(kAMIE*HoQf;NI`o z#!g)!QJUo+w?*7*M9j?1sWng0$oI1x9y>?3M2fke41Dd-?oFCTX9L6<0ZUSrkOlW2 zkXG*L_vpCo&k3O29$E9ImevOVfRw+Ug};-7zq6*}W9QQZC@U$$FDoG~E34dBMrbN2 zX)3E;I*m$7n<{2w5C0d!)7!}n8}xr8B0228CzDurA!=|nHDxt*WmVVChl9x(Yz8;} zKZT+LR!tqN0#`w(DXThSRS-^yhYreEth%a(!$XHF2xkR1M{flmFPD`mO6Vy!V4!D& KEW2s{?0*2`Voap~ delta 2174 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 diff --git a/front/dist/static/images/favicons/favicon-32x32.png b/front/dist/static/images/favicons/favicon-32x32.png index 957f500661794fded12906de8d183dee59c864a2..6c2e88a7168677d5dbabdebee45c8548f295616a 100644 GIT binary patch delta 1744 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 P-hjEu72{Wi?(zQv2}%3r delta 2675 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 NBH$gYXqMgy{{XbX*V+I8 diff --git a/front/dist/static/images/favicons/favicon.ico b/front/dist/static/images/favicons/favicon.ico index ec628ea22acf6696ddb792150cb740b893acacff..8272d095d28fd066decac955e6881d1a3b912497 100644 GIT binary patch literal 1150 zcmb`GOK4L;6ox0USVYu{5*IG2SfLAX;lfpYAjKq&f{Ka=x)fU#i!MY2Q@V8}uG)Xn7kvt)mkZ2p)rU`A6LLP0?JW85`Bvi!{=;)}{YPD)D z7K@14Y#zW^pWSX(IGs+W*Xy@;UR_<4fv&~H#qZH*baQ@wo~Ea#X<}l6{C>ZP+wCU! z{e;h3gTb)hWHK?M(I|T4^Z8@o8~6nu;$*W~T3%kJcsx$rZ)$3a0)YTI9F83Hw>=&Y z69hrhw?n%E=x=hl93_)U(PL(2hQ`Lm8i-%;dc6;TqFgS^pb_pq%9nN`L(w9mlg5N)S9usmS#^Ce$Hjv-Ja5#JsTK*fB^p#45*4NjG$H8;J zbrM{U+!FK+oZp1poe70PH=q|JeYIMpdc97?VsR%up@t3Q@eArbG&wn`%VaX`V1=KZ zd+^i*blc^;m>i0R9%j%Yv(j)0~Mvu5Y0VVDQL!4s$ zkuO=fTxyNQqWe_pXI(dM^=n?de0RKC`LyTmy;n+Mw9Xo z8biId@926V m4JZJgy8zBL`;u%|S|Y{<4#BFm$xg1#H9oBOePsCV8T$>28)udP literal 1150 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 delta 3830 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 E50%WC@c;k- delta 3874 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 diff --git a/front/dist/static/images/favicons/ms-icon-310x310.png b/front/dist/static/images/favicons/ms-icon-310x310.png index 56ceeb95c90cb0f3546656fe2702db44f3aa3990..a7b7aeaad731ca5d0ad1ac99a95a0ced893b7ad3 100644 GIT binary patch literal 33026 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 literal 10098 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 diff --git a/front/dist/static/images/favicons/ms-icon-70x70.png b/front/dist/static/images/favicons/ms-icon-70x70.png index 3fa12caee21f5d6d3f22a6288f541466ee5e87a9..00a5a65308bfbf045390b495cbb9dbac50ee63ce 100644 GIT binary patch delta 4454 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 XVo<=%pHr35hvI+*$_CkBf{yzScQQ!9 delta 2028 zcmVFCUynX~umVXZyqoPG8>mwhfXr_LV^GkfoI*80|eeShD#*7~ot1)!)S3v##( z7^$ceV?mu53+lvJP$$NMIx!a1iLszgj0JUKET|J>LCpV0m`^cK+zj%^>5Re2^jxRd`qyjfjc><7z!NEZN!jyvwC#@9t?eKtPpdGP{3kM#a z4P#p@4Hn?KaDVTB$zx2q@?s^uK?x2 z)$4Hins$aDhKNx>OAEZa6&B6c1cVuQuNU5W5B7hP-#`V-p94F#!}xY>Ob}8+*I&@N z1uk3(FGosP{Q_*>08Nd#y|f@+gzkioK8GEIMm6bjnw#=>NPcECF{7GOIZz6dYg7m^Kq?d2Dp$q# znDTOzX(>+{<2+-mmu^=X6eQ(==cW28W{ORkzp=JOqz-5q4Q*q!=adUVz__t6 zb2{|ggMXXdkWBOF5lf6hyZYN(VetZJZGlEh0*_L7LkwPd5uSerj-7xvw!`haB@$B! zEi|l5DPj5)m^}*^EN{g^=$zFt0j_t0<77UM>@npeCud6YFo`BtD|-I>lq_0MupujnHWceFY@xOkIDsqo#hB{NOh7j_oXOo zAzP6g;|P$Bc4IfG-MCuBR0<^%qZG`T2AkIFrNYO1;8YjI>kT6eWsy2M+F;dESp5=A zd4J%)vFBTo$2g=GUY-legpn}MY8jC|Mxa?y=*5^1&olW&RX>_U$R{cydyEjUa4sxa z3^S*}xG~TWhjlAq;XL^982oU&I$0a zEswh8Rq^dT4sNR$n6nAj05oQfu){}#zaK8$(6h{`6Jd07^@#B~Fca>fnXi8x zwUbKMV+5Q&2OGD++AZ+&DQaG%f--qbVgN2))h8i>f^;?Dhf>(;dU!&~SRHbo;E{P$Zdiyx!L^%k^d#K9uL}~u?`Pr6C0Me^XdyW^ zx+;NkRTYgG6TE(I@i)4p(8HyJIU!ZUqf;be29q$5gk;EZG{47OSC3YX)S5}Haa0^J zLJ!Bg_riC_;LHU*xyu*{gnuS%{~N#+&}L8A{Vesi7O7>nyzUfj(#$CRPinCxrjp$u=&LfKT?qx8GZ~LCi_@<0tii5bQbEg~LBW zPoI%l!;Z~LxO5eI`?6=v%I~?jNbS$$Lb{R|Jd5wYlORb=w(=dJ^Lt2$)Vli-+MP{f#^1Gd(L;Hy9tR2an0!4f(!QiZ+*Cz)-ym;&5ucY$b|@rw z6x}dUnCIBIEOzT7>hII*kJ%m+TafK}F=eVe*HP?=T7O2)kALz`WG+PMXf;B9Ut=qn z>Y@I6u?2NvET|J>L7f;2>cm*^FTWr?Kh&d@oB#j-C3HntbYx+4WjbSWWnpw>05UK# zFfA}PEigG$Fg7|dG&(XkD=;uRFfdhdib4PY03~!qSaf7zbY(hiZ)9m^c>ppnGB7PL zHZ3qYR4_I=F%C33GB_(RFgh?WBrG!ilWY Date: Thu, 27 May 2021 17:06:39 +0200 Subject: [PATCH 02/79] first step to change tile --- front/src/Api/Events/ChangeTileEvent.ts | 14 +- front/src/Phaser/Game/GameScene.ts | 17 +- front/src/iframe_api.ts | 21 +- maps/tests/Metadata/changeTile.html | 28 +++ maps/tests/Metadata/changeTile.json | 275 ++++++++++++++++++++++++ 5 files changed, 334 insertions(+), 21 deletions(-) create mode 100644 maps/tests/Metadata/changeTile.html create mode 100644 maps/tests/Metadata/changeTile.json diff --git a/front/src/Api/Events/ChangeTileEvent.ts b/front/src/Api/Events/ChangeTileEvent.ts index 23599881..4a071403 100644 --- a/front/src/Api/Events/ChangeTileEvent.ts +++ b/front/src/Api/Events/ChangeTileEvent.ts @@ -1,12 +1,14 @@ import * as tg from "generic-type-guard"; export const isChangeTileEvent = - new tg.IsInterface().withProperties({ - x: tg.isNumber, - y: tg.isNumber, - tile: tg.isUnion(tg.isNumber, tg.isString), - layer: tg.isUnion(tg.isNumber, tg.isString) - }).get(); + tg.isArray( + new tg.IsInterface().withProperties({ + x: tg.isNumber, + y: tg.isNumber, + tile: tg.isUnion(tg.isNumber, tg.isString), + layer: tg.isString + }).get() + ); /** * A message sent from the game to the iFrame when a user enters or leaves a zone marked with the "zone" property. */ diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index b314fe3b..cb820ba4 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -890,19 +890,12 @@ ${escapedMessage} this.userInputManager.restoreControls(); })) -/* this.iframeSubscriptionList.push(iframeListener.loadPageStream.subscribe((url: string) => { - this.loadNextGame(url).then(() => { - this.events.once(EVENT_TYPE.POST_UPDATE, () => { - this.onMapExit(url); - }) - }) - }))*/ - - this.iframeSubscriptionList.push(iframeListener.updateTileEvent.subscribe(event => { + this.iframeSubscriptionList.push(iframeListener.changeTileStream.subscribe(event => { for (const eventTile of event) { const layer = this.gameMap.findPhaserLayer(eventTile.layer); if (layer) { - const tile = layer.getTileAt(eventTile.x, eventTile.y) + console.log('layer : ', layer); + const tile = layer.getTileAt(eventTile.x, eventTile.y, true) if (typeof eventTile.tile == "string") { const tileIndex = this.getIndexForTileType(eventTile.tile); if (tileIndex) { @@ -911,11 +904,11 @@ ${escapedMessage} return } } else { - tile.index = eventTile.tile + tile.index = eventTile.tile //+ firsrtgid du layer } } } - this.scene.scene.sys.game.events.emit("contextrestored") + //this.dirty = true; })) let scriptedBubbleSprite: Sprite; diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index a390e616..b8b5ccdf 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -44,9 +44,10 @@ interface WorkAdventureApi { displayBubble(): void; removeBubble(): void; loadSound(url : string): Sound; - registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void): void - getCurrentUser(): Promise - getCurrentRoom(): Promise + registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void): void; + getCurrentUser(): Promise; + getCurrentRoom(): Promise; + changeTile(tiles: TileDescriptor[]): void; //loadTileset(name: string, imgUrl : string, tilewidth : number, tileheight : number, margin : number, spacing : number): void; onPlayerMove(callback: (playerMovedEvent: HasPlayerMovedEvent) => void): void @@ -65,6 +66,13 @@ interface Room { startLayer: string | null } +interface TileDescriptor { + x: number + y: number + tile: number | string + layer: string +} + declare global { // eslint-disable-next-line no-var var WA: WorkAdventureApi @@ -221,6 +229,13 @@ window.WA = { }) }, + changeTile(tiles: TileDescriptor[]) { + postToParent({ + type: 'changeTile', + data: tiles + }) + }, + /** * Send a message in the chat. * Only the local user will receive this message. diff --git a/maps/tests/Metadata/changeTile.html b/maps/tests/Metadata/changeTile.html new file mode 100644 index 00000000..214908a9 --- /dev/null +++ b/maps/tests/Metadata/changeTile.html @@ -0,0 +1,28 @@ + + + + + + + + + \ No newline at end of file diff --git a/maps/tests/Metadata/changeTile.json b/maps/tests/Metadata/changeTile.json new file mode 100644 index 00000000..48f57e0f --- /dev/null +++ b/maps/tests/Metadata/changeTile.json @@ -0,0 +1,275 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":1, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 49, 50, 50, 50, 50, 50, 50, 50, 50, 51], + "height":10, + "id":2, + "name":"bottom", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":4, + "name":"metadata", + "opacity":1, + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"changeTile.html" + }, + { + "name":"openWebsiteAllowApi", + "type":"bool", + "value":true + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":8, + "name":"changeTile", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":5, + "name":"floorLayer", + "objects":[ + { + "height":159.866671635267, + "id":1, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":9, + "text":"Test : \nWalk on the grass\n\nResult : \nTiles of the first left colum become red tile (tile find by Number)\nTiles of the below the grass become blue (tile find by String)\n", + "wrap":true + }, + "type":"", + "visible":true, + "width":287.674838251912, + "x":32.5473600365393, + "y":160.305680721763 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":9, + "nextobjectid":2, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.4.3", + "tileheight":32, + "tilesets":[ + { + "columns":8, + "firstgid":1, + "image":"tileset_dungeon.png", + "imageheight":256, + "imagewidth":256, + "margin":0, + "name":"TDungeon", + "spacing":0, + "tilecount":64, + "tileheight":32, + "tiles":[ + { + "id":0, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":1, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":2, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":3, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":4, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":8, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":9, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":10, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":11, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":12, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":16, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":17, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":18, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":19, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":20, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }], + "tilewidth":32 + }, + { + "columns":8, + "firstgid":65, + "image":"floortileset.png", + "imageheight":288, + "imagewidth":256, + "margin":0, + "name":"Floor", + "spacing":0, + "tilecount":72, + "tileheight":32, + "tiles":[ + { + "id":34, + "type":"Red" + }, + { + "id":44, + "type":"blue" + }], + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":1.4, + "width":10 +} \ No newline at end of file From 0623ee0bf2ebb8cb1f4b4277aad3d1373d2050f9 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Mon, 21 Jun 2021 11:44:18 +0200 Subject: [PATCH 03/79] HOTFIX: loading errors after the preload stage should not crash the game anymore --- .../Entity/PlayerTexturesLoadingManager.ts | 22 ++++++++++++++++--- front/src/Phaser/Game/GameScene.ts | 16 +++++++++----- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts index 95f00a9e..eaba79aa 100644 --- a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts +++ b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts @@ -59,9 +59,11 @@ export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, textur } else { returnPromise = Promise.resolve(texturekeys); } + + //If the loading fail, we render the default model instead. return returnPromise.then((keys) => keys.map((key) => { return typeof key !== 'string' ? key.name : key; - })) + })).catch(() => lazyLoadPlayerCharacterTextures(loadPlugin, ["color_22", "eyes_23"])); } export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptionInterface): BodyResourceDescriptionInterface => { @@ -80,11 +82,25 @@ export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptio } export const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface, frameConfig: FrameConfig) => { - return new Promise((res) => { + return new Promise((res, rej) => { + console.log('count', loadPlugin.listenerCount('loaderror')); if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { return res(playerResourceDescriptor); } loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig); - loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor)); + const errorCallback = (file: {src: string}) => { + if (file.src !== playerResourceDescriptor.img) return; + console.error('failed loading player ressource: ', playerResourceDescriptor) + rej(playerResourceDescriptor); + loadPlugin.off('filecomplete-spritesheet-' + playerResourceDescriptor.name, successCallback); + loadPlugin.off('loaderror', errorCallback); + } + const successCallback = () => { + loadPlugin.off('loaderror', errorCallback); + res(playerResourceDescriptor); + } + + loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, successCallback); + loadPlugin.on('loaderror', errorCallback); }); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 72279c61..62d66c30 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -192,6 +192,7 @@ export class GameScene extends DirtyScene implements CenterListener { private pinchManager: PinchManager|undefined; private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time. private emoteManager!: EmoteManager; + private preloading: boolean = true; constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) { super({ @@ -259,11 +260,15 @@ export class GameScene extends DirtyScene implements CenterListener { return; } - this.scene.start(ErrorSceneName, { - title: 'Network error', - subTitle: 'An error occurred while loading resource:', - message: this.originalMapUrl ?? file.src - }); + //once preloading is over, we don't want loading errors to crash the game, so we need to disable this behavior after preloading. + console.error('Error when loading: ', file); + if (this.preloading) { + this.scene.start(ErrorSceneName, { + title: 'Network error', + subTitle: 'An error occurred while loading resource:', + message: this.originalMapUrl ?? file.src + }); + } }); this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (key: string, type: string, data: unknown) => { this.onMapLoad(data); @@ -388,6 +393,7 @@ export class GameScene extends DirtyScene implements CenterListener { //hook create scene create(): void { + this.preloading = false; this.trackDirtyAnims(); gameManager.gameSceneIsCreated(this); From 3e29ed4376a9e469c51291a93f8d0c1599e03b84 Mon Sep 17 00:00:00 2001 From: GRL Date: Tue, 22 Jun 2021 11:48:08 +0200 Subject: [PATCH 04/79] Correction of button customize WOKA scene issue --- .../CustomCharacterScene.svelte | 12 +++---- front/src/Phaser/Login/CustomizeScene.ts | 32 +++++++++---------- front/src/Stores/CustomCharacterStore.ts | 4 ++- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/front/src/Components/CustomCharacterScene/CustomCharacterScene.svelte b/front/src/Components/CustomCharacterScene/CustomCharacterScene.svelte index f9e3a66b..3fff6bc0 100644 --- a/front/src/Components/CustomCharacterScene/CustomCharacterScene.svelte +++ b/front/src/Components/CustomCharacterScene/CustomCharacterScene.svelte @@ -1,11 +1,11 @@ diff --git a/maps/tests/Metadata/changeTile.json b/maps/tests/Metadata/changeTile.json index 48f57e0f..834ea9a1 100644 --- a/maps/tests/Metadata/changeTile.json +++ b/maps/tests/Metadata/changeTile.json @@ -1,4 +1,11 @@ { "compressionlevel":-1, + "editorsettings": + { + "export": + { + "target":"." + } + }, "height":10, "infinite":false, "layers":[ @@ -50,7 +57,7 @@ "y":0 }, { - "data":[65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "data":[65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0], "height":10, "id":8, "name":"changeTile", @@ -67,7 +74,7 @@ "name":"floorLayer", "objects":[ { - "height":159.866671635267, + "height":191.866671635267, "id":1, "name":"", "rotation":0, @@ -75,14 +82,14 @@ { "fontfamily":"Sans Serif", "pixelsize":9, - "text":"Test : \nWalk on the grass\n\nResult : \nTiles of the first left colum become red tile (tile find by Number)\nTiles of the below the grass become blue (tile find by String)\n", + "text":"Test : \nWalk on the grass\n\nResult : \nThe Yellow Tile open a jitsi with Trigger.\n\nThe Red Tile open cowebsite Wikip\u00e9dia. The highest Red Tile is find by 'string' index, the lowest by 'number' index.\n\nThe White Tile are silent tile. You can not open a bubble in it. (Even if the other player didn't activate the script.)\n\nThe Pale Tile (Lowest) is an exitUrl tile to customMenu.json.\n\nThe Blue Tile are 'collides' tile. The two tile in the center are 'number' index. The others are 'string' index.\n", "wrap":true }, "type":"", "visible":true, "width":287.674838251912, "x":32.5473600365393, - "y":160.305680721763 + "y":128.305680721763 }], "opacity":1, "type":"objectgroup", @@ -258,13 +265,62 @@ "tilecount":72, "tileheight":32, "tiles":[ + { + "id":9, + "properties":[ + { + "name":"exitUrl", + "type":"string", + "value":"customMenu.json" + }] + }, + { + "id":27, + "properties":[ + { + "name":"jitsiRoom", + "type":"string", + "value":"TEST" + }, + { + "name":"jitsiTrigger", + "type":"string", + "value":"onaction" + }, + { + "name":"jitsiUrl", + "type":"string", + "value":"meet.jit.si" + }] + }, { "id":34, + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"https:\/\/fr.wikipedia.org\/wiki\/Wikip%C3%A9dia:Accueil_principal" + }], "type":"Red" }, { "id":44, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }], "type":"blue" + }, + { + "id":52, + "properties":[ + { + "name":"silent", + "type":"bool", + "value":true + }] }], "tilewidth":32 }], From f536d538ead53f43885c319e6393f5863ec8f3c7 Mon Sep 17 00:00:00 2001 From: jonny Date: Fri, 25 Jun 2021 17:35:42 +0200 Subject: [PATCH 10/79] added backwards compatible check and maps --- front/src/Phaser/Game/GameMap.ts | 4 ++ front/src/Phaser/Game/GameScene.ts | 21 +++---- maps/tests/function_tiles.json | 33 ++++++++++ maps/tests/function_tiles.png | Bin 0 -> 1313 bytes maps/tests/start-tile.json | 95 +++++++++++++++++++++++++++++ 5 files changed, 142 insertions(+), 11 deletions(-) create mode 100644 maps/tests/function_tiles.json create mode 100644 maps/tests/function_tiles.png create mode 100644 maps/tests/start-tile.json diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index b84ec477..7c446dbf 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -17,6 +17,8 @@ export class GameMap { public exitUrls: Array = [] + public hasStartTile = false; + public constructor(private map: ITiledMap) { this.layersIterator = new LayersIterator(map); @@ -27,6 +29,8 @@ export class GameMap { tile.properties.forEach(prop => { if (prop.name == "exitUrl" && typeof prop.value == "string") { this.exitUrls.push(prop.value); + } else if (prop.name == "start") { + this.hasStartTile = true } }) } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 30b92a91..98e990ef 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1043,7 +1043,7 @@ ${escapedMessage} } else { // Now, let's find the start layer if (this.startLayerName) { - this.initPositionFromLayerName(this.startLayerName, null); + this.initPositionFromLayerName(this.startLayerName, this.startLayerName); } if (this.startX === undefined) { // If we have no start layer specified or if the hash passed does not exist, let's go with the default start position. @@ -1059,10 +1059,10 @@ ${escapedMessage} } } - private initPositionFromLayerName(layerName: string, startLayerName: string | null) { + private initPositionFromLayerName(selectedOrdDefaultLayer: string, selectedLayer: string | null) { for (const layer of this.gameMap.layersIterator) { - if ((layerName === layer.name || layer.name.endsWith('/' + layerName)) && layer.type === 'tilelayer' && (layerName === defaultStartLayerName || this.isStartLayer(layer))) { - const startPosition = this.startUser(layer, startLayerName); + if ((selectedOrdDefaultLayer === layer.name || layer.name.endsWith('/' + selectedOrdDefaultLayer)) && layer.type === 'tilelayer' && (selectedOrdDefaultLayer === defaultStartLayerName || this.isStartLayer(layer))) { + const startPosition = this.startUser(layer, selectedLayer); this.startX = startPosition.x + this.mapFile.tilewidth / 2; this.startY = startPosition.y + this.mapFile.tileheight / 2; } @@ -1116,8 +1116,8 @@ ${escapedMessage} return gameManager.loadMap(room, this.scene).catch(() => { }); } - private startUser(layer: ITiledMapTileLayer, startName: string | null): PositionInterface { - const tiles = layer.data; + private startUser(selectedOrDefaultLayer: ITiledMapTileLayer, startName: string | null): PositionInterface { + const tiles = selectedOrDefaultLayer.data; if (typeof (tiles) === 'string') { throw new Error('The content of a JSON map must be filled as a JSON array, not as a string'); } @@ -1126,21 +1126,20 @@ ${escapedMessage} if (objectKey === 0) { return; } - const y = Math.floor(key / layer.width); - const x = key % layer.width; + const y = Math.floor(key / selectedOrDefaultLayer.width); + const x = key % selectedOrDefaultLayer.width; - if (startName) { + if (startName && this.gameMap.hasStartTile) { const properties = this.gameMap.getPropertiesForIndex(objectKey); if (!properties.length || !properties.some(property => property.name == "start" && property.value == startName)) { return } } - possibleStartPositions.push({ x: x * this.mapFile.tilewidth, y: y * this.mapFile.tilewidth }); }); // Get a value at random amongst allowed values if (possibleStartPositions.length === 0) { - console.warn('The start layer "' + layer.name + '" for this map is empty.'); + console.warn('The start layer "' + selectedOrDefaultLayer.name + '" for this map is empty.'); return { x: 0, y: 0 diff --git a/maps/tests/function_tiles.json b/maps/tests/function_tiles.json new file mode 100644 index 00000000..9bc374eb --- /dev/null +++ b/maps/tests/function_tiles.json @@ -0,0 +1,33 @@ +{ "columns":2, + "image":"function_tiles.png", + "imageheight":64, + "imagewidth":64, + "margin":0, + "name":"function_tiles", + "spacing":0, + "tilecount":4, + "tiledversion":"1.6.0", + "tileheight":32, + "tiles":[ + { + "id":0, + "properties":[ + { + "name":"start", + "type":"string", + "value":"S1" + }] + }, + { + "id":1, + "properties":[ + { + "name":"start", + "type":"string", + "value":"S2" + }] + }], + "tilewidth":32, + "type":"tileset", + "version":"1.6" +} \ No newline at end of file diff --git a/maps/tests/function_tiles.png b/maps/tests/function_tiles.png new file mode 100644 index 0000000000000000000000000000000000000000..147eb61942894f428ec11dd1bb40271a2c9d7bdd GIT binary patch literal 1313 zcmV++1>X9JP)WFU8GbZ8()Nlj2>E@cM*00f0eL_t(|+U=W7j1)x_ z$A2@7kwl1r>;Nu!@CJI&17HkXICwDxv+S3$#&}T!CyyqCgd<0ecu^B}$ujH)Tw~xu z0tXT}5hCCPBd#C7u4_yrh7Wg}L%qt>7G2%dJ>9+Y(f?%9+tu~)>Q%judi55v!XE2A z@NQYL3kHAfU)H)R&kuC7oI8!lVehkwd z$9MDh91a5Sc=G#TW@N{15PMx*TsBEu4@~${Pz@=27nt_#pk|F;iFoe?ya`-#2mjsSMatSS#X8+74a<{%(Z_ z75P0#+QAv%g`#&(|Gw5;P zKj3#@cM{*i`o8FB*H`ZHME0Ondy&R93Ph{hSTEliS#qH2;7ZNfXcNGKY@m@W38*>B z>%gc*L@s__l+n0o{5_Y~CUg;S8kh&x$;_*H`BSwB?N@Ou$uKb0HDXwzL|fuJ;4$#6 zQMMJhTEyAvCU5~*ZRz}5z#B0H3^*JXvuuAH0k?r8DSf&}z{ZI0uK`bexG)ppK1C6r zHRT3& zp+aZVMG!DA1C7anR3B6~6?HH{SRKfLR4+l{QlOoSf5k^AoPZzYxi-(Zj*Ean*^K*) zeNiX@Q^23V_r_987Da%L1#C9nr^V;%D?$;~f%ihBBA2DG31|Sn0p}y6qLHT_h%(P} z^xl17i4rABtSkm-W*-C|r)-}(zSxP1mCZ_+;Vvp>w#%ybHt-ejv(%df&H_8KV8A1p z#KshDTVcRWX?p>c8nRj(gb|u)A{tkKPk>iZNgQuUSpfXhDg#EX)x0A<*%WwJ3Vh1)E9XqnFT^~~ zkpW@YCVkF^eEU@N)e`VoRs#kRzeSoMNT2#im~jo2Gq_dcdJQVq@Urw{#%&Kv*%I)1 zw^*O;sO*Uws0@lHs4R-9kZm2u^fF7LM2QkktayN7r4t@{KGU*ffQL^#XV}z}9(|4* z^&hmm9gbtbJSsx2FRed9-Ij#qTI-t>ohL9Ur^vs$K?H{7WC-e-Xo2Rn0jS%X1)5h# z1HgyfXazQiAFj9cg!FMh-tVLGYP^%_JIxBriC)k*m1D-_$AMQ!_@4xwY6VV(l%4Gr zGdOANfcu7$nYcXi{*;t;ECaL`sFP>D2DSkIB(M@AMp;{;=HUgV!BDs9fUd Date: Fri, 25 Jun 2021 17:57:09 +0200 Subject: [PATCH 11/79] cleanup --- front/src/Phaser/Game/GameScene.ts | 94 ++------------ .../Phaser/Game/StartPositionCalculator.ts | 117 ++++++++++++++++++ 2 files changed, 129 insertions(+), 82 deletions(-) create mode 100644 front/src/Phaser/Game/StartPositionCalculator.ts diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 98e990ef..458e5f96 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -96,6 +96,7 @@ import { peerStore } from "../../Stores/PeerStore"; import { EmoteManager } from "./EmoteManager"; import AnimatedTiles from "phaser-animated-tiles"; +import { StartPositionCalculator } from './StartPositionCalculator'; export interface GameSceneInitInterface { initPosition: PointInterface | null, @@ -132,7 +133,6 @@ interface DeleteGroupEventInterface { groupId: number } -const defaultStartLayerName = 'start'; export class GameScene extends DirtyScene implements CenterListener { Terrains: Array; @@ -145,8 +145,6 @@ export class GameScene extends DirtyScene implements CenterListener { mapFile!: ITiledMap; animatedTiles!: AnimatedTiles; groups: Map; - startX!: number; - startY!: number; circleTexture!: CanvasTexture; circleRedTexture!: CanvasTexture; pendingEvents: Queue = new Queue(); @@ -183,7 +181,6 @@ export class GameScene extends DirtyScene implements CenterListener { private outlinedItem: ActionableItem | null = null; public userInputManager!: UserInputManager; private isReconnecting: boolean | undefined = undefined; - private startLayerName!: string | null; private openChatIcon!: OpenChatIcon; private playerName!: string; private characterLayers!: string[]; @@ -194,6 +191,7 @@ export class GameScene extends DirtyScene implements CenterListener { private pinchManager: PinchManager | undefined; private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time. private emoteManager!: EmoteManager; + startPositionCalculator!: StartPositionCalculator; constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) { super({ @@ -395,7 +393,6 @@ export class GameScene extends DirtyScene implements CenterListener { gameManager.gameSceneIsCreated(this); urlManager.pushRoomIdToUrl(this.room); - this.startLayerName = urlManager.getStartLayerNameFromUrl(); if (touchScreenManager.supportTouchScreen) { this.pinchManager = new PinchManager(this); @@ -458,7 +455,9 @@ export class GameScene extends DirtyScene implements CenterListener { throw new Error('Your map MUST contain a layer of type "objectgroup" whose name is "floorLayer" that represents the layer characters are drawn at. This layer cannot be contained in a group.'); } - this.initStartXAndStartY(); + this.startPositionCalculator = new StartPositionCalculator(this.gameMap, this.mapFile, this.initPosition, urlManager.getStartLayerNameFromUrl()) + + //add entities this.Objects = new Array(); @@ -563,8 +562,8 @@ export class GameScene extends DirtyScene implements CenterListener { this.playerName, this.characterLayers, { - x: this.startX, - y: this.startY + x: this.startPositionCalculator.startX, + y: this.startPositionCalculator.startY }, { left: camera.scrollX, @@ -970,9 +969,9 @@ ${escapedMessage} this.scene.start(roomId); } else { //if the exit points to the current map, we simply teleport the user back to the startLayer - this.initPositionFromLayerName(hash || defaultStartLayerName, hash); - this.CurrentPlayer.x = this.startX; - this.CurrentPlayer.y = this.startY; + this.startPositionCalculator.initPositionFromLayerName(hash, hash); + this.CurrentPlayer.x = this.startPositionCalculator.startX; + this.CurrentPlayer.y = this.startPositionCalculator.startY; setTimeout(() => this.mapTransitioning = false, 500); } } @@ -1035,40 +1034,7 @@ ${escapedMessage} } } - private initStartXAndStartY() { - // If there is an init position passed - if (this.initPosition !== null) { - this.startX = this.initPosition.x; - this.startY = this.initPosition.y; - } else { - // Now, let's find the start layer - if (this.startLayerName) { - this.initPositionFromLayerName(this.startLayerName, this.startLayerName); - } - if (this.startX === undefined) { - // If we have no start layer specified or if the hash passed does not exist, let's go with the default start position. - this.initPositionFromLayerName(defaultStartLayerName, this.startLayerName); - } - } - // Still no start position? Something is wrong with the map, we need a "start" layer. - if (this.startX === undefined) { - console.warn('This map is missing a layer named "start" that contains the available default start positions.'); - // Let's start in the middle of the map - this.startX = this.mapFile.width * 16; - this.startY = this.mapFile.height * 16; - } - } - private initPositionFromLayerName(selectedOrdDefaultLayer: string, selectedLayer: string | null) { - for (const layer of this.gameMap.layersIterator) { - if ((selectedOrdDefaultLayer === layer.name || layer.name.endsWith('/' + selectedOrdDefaultLayer)) && layer.type === 'tilelayer' && (selectedOrdDefaultLayer === defaultStartLayerName || this.isStartLayer(layer))) { - const startPosition = this.startUser(layer, selectedLayer); - this.startX = startPosition.x + this.mapFile.tilewidth / 2; - this.startY = startPosition.y + this.mapFile.tileheight / 2; - } - } - - } private getExitUrl(layer: ITiledMapLayer): string | undefined { return this.getProperty(layer, "exitUrl") as string | undefined; @@ -1081,10 +1047,6 @@ ${escapedMessage} return this.getProperty(layer, "exitSceneUrl") as string | undefined; } - private isStartLayer(layer: ITiledMapLayer): boolean { - return this.getProperty(layer, "startLayer") == true; - } - private getScriptUrls(map: ITiledMap): string[] { return (this.getProperties(map, "script") as string[]).map((script) => (new URL(script, this.MapUrlFile)).toString()); } @@ -1116,38 +1078,6 @@ ${escapedMessage} return gameManager.loadMap(room, this.scene).catch(() => { }); } - private startUser(selectedOrDefaultLayer: ITiledMapTileLayer, startName: string | null): PositionInterface { - const tiles = selectedOrDefaultLayer.data; - if (typeof (tiles) === 'string') { - throw new Error('The content of a JSON map must be filled as a JSON array, not as a string'); - } - const possibleStartPositions: PositionInterface[] = []; - tiles.forEach((objectKey: number, key: number) => { - if (objectKey === 0) { - return; - } - const y = Math.floor(key / selectedOrDefaultLayer.width); - const x = key % selectedOrDefaultLayer.width; - - if (startName && this.gameMap.hasStartTile) { - const properties = this.gameMap.getPropertiesForIndex(objectKey); - if (!properties.length || !properties.some(property => property.name == "start" && property.value == startName)) { - return - } - } - possibleStartPositions.push({ x: x * this.mapFile.tilewidth, y: y * this.mapFile.tilewidth }); - }); - // Get a value at random amongst allowed values - if (possibleStartPositions.length === 0) { - console.warn('The start layer "' + selectedOrDefaultLayer.name + '" for this map is empty.'); - return { - x: 0, - y: 0 - }; - } - // Choose one of the available start positions at random amongst the list of available start positions. - return possibleStartPositions[Math.floor(Math.random() * possibleStartPositions.length)]; - } //todo: in a dedicated class/function? initCamera() { @@ -1184,8 +1114,8 @@ ${escapedMessage} try { this.CurrentPlayer = new Player( this, - this.startX, - this.startY, + this.startPositionCalculator.startX, + this.startPositionCalculator.startY, this.playerName, texturesPromise, PlayerAnimationDirections.Down, diff --git a/front/src/Phaser/Game/StartPositionCalculator.ts b/front/src/Phaser/Game/StartPositionCalculator.ts new file mode 100644 index 00000000..5dc454aa --- /dev/null +++ b/front/src/Phaser/Game/StartPositionCalculator.ts @@ -0,0 +1,117 @@ +import type { PositionInterface } from '../../Connexion/ConnexionModels'; +import type { ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledMapTileLayer } from '../Map/ITiledMap'; +import type { GameMap } from './GameMap'; + + +const defaultStartLayerName = 'start'; + +export class StartPositionCalculator { + public startX!: number; + public startY!: number; + + + + constructor( + private readonly gameMap: GameMap, + private readonly mapFile: ITiledMap, + private readonly initPosition: PositionInterface | null, + private readonly startLayerName: string | null) { + this.initStartXAndStartY(); + } + private initStartXAndStartY() { + // If there is an init position passed + if (this.initPosition !== null) { + this.startX = this.initPosition.x; + this.startY = this.initPosition.y; + } else { + // Now, let's find the start layer + if (this.startLayerName) { + this.initPositionFromLayerName(this.startLayerName, this.startLayerName); + } + if (this.startX === undefined) { + // If we have no start layer specified or if the hash passed does not exist, let's go with the default start position. + this.initPositionFromLayerName(defaultStartLayerName, this.startLayerName); + } + } + // Still no start position? Something is wrong with the map, we need a "start" layer. + if (this.startX === undefined) { + console.warn('This map is missing a layer named "start" that contains the available default start positions.'); + // Let's start in the middle of the map + this.startX = this.mapFile.width * 16; + this.startY = this.mapFile.height * 16; + } + } + + /** + * + * @param selectedLayer this is always the layer that is selected with the hash in the url + * @param selectedOrDefaultLayer this can also be the {defaultStartLayerName} if the {selectedLayer} didnt yield any start points + */ + public initPositionFromLayerName(selectedOrDefaultLayer: string | null, selectedLayer: string | null) { + if (!selectedOrDefaultLayer) { + selectedOrDefaultLayer = defaultStartLayerName + } + for (const layer of this.gameMap.layersIterator) { + if ((selectedOrDefaultLayer === layer.name || layer.name.endsWith('/' + selectedOrDefaultLayer)) && layer.type === 'tilelayer' && (selectedOrDefaultLayer === defaultStartLayerName || this.isStartLayer(layer))) { + const startPosition = this.startUser(layer, selectedLayer); + this.startX = startPosition.x + this.mapFile.tilewidth / 2; + this.startY = startPosition.y + this.mapFile.tileheight / 2; + } + } + + } + + private isStartLayer(layer: ITiledMapLayer): boolean { + return this.getProperty(layer, "startLayer") == true; + } + + /** + * + * @param selectedLayer this is always the layer that is selected with the hash in the url + * @param selectedOrDefaultLayer this can also be the default layer if the {selectedLayer} didnt yield any start points + */ + private startUser(selectedOrDefaultLayer: ITiledMapTileLayer, selectedLayer: string | null): PositionInterface { + const tiles = selectedOrDefaultLayer.data; + if (typeof (tiles) === 'string') { + throw new Error('The content of a JSON map must be filled as a JSON array, not as a string'); + } + const possibleStartPositions: PositionInterface[] = []; + tiles.forEach((objectKey: number, key: number) => { + if (objectKey === 0) { + return; + } + const y = Math.floor(key / selectedOrDefaultLayer.width); + const x = key % selectedOrDefaultLayer.width; + + if (selectedLayer && this.gameMap.hasStartTile) { + const properties = this.gameMap.getPropertiesForIndex(objectKey); + if (!properties.length || !properties.some(property => property.name == "start" && property.value == selectedLayer)) { + return + } + } + possibleStartPositions.push({ x: x * this.mapFile.tilewidth, y: y * this.mapFile.tilewidth }); + }); + // Get a value at random amongst allowed values + if (possibleStartPositions.length === 0) { + console.warn('The start layer "' + selectedOrDefaultLayer.name + '" for this map is empty.'); + return { + x: 0, + y: 0 + }; + } + // Choose one of the available start positions at random amongst the list of available start positions. + return possibleStartPositions[Math.floor(Math.random() * possibleStartPositions.length)]; + } + + private getProperty(layer: ITiledMapLayer | ITiledMap, name: string): string | boolean | number | undefined { + const properties: ITiledMapLayerProperty[] | undefined = layer.properties; + if (!properties) { + return undefined; + } + const obj = properties.find((property: ITiledMapLayerProperty) => property.name.toLowerCase() === name.toLowerCase()); + if (obj === undefined) { + return undefined; + } + return obj.value; + } +} \ No newline at end of file From 769e0fcc297b4b7ef9f9c26293462f591336897a Mon Sep 17 00:00:00 2001 From: jonny Date: Fri, 25 Jun 2021 18:03:43 +0200 Subject: [PATCH 12/79] refactor to position object --- front/src/Phaser/Game/GameScene.ts | 11 ++++----- .../Phaser/Game/StartPositionCalculator.ts | 23 ++++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 458e5f96..3b1d0b44 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -562,8 +562,7 @@ export class GameScene extends DirtyScene implements CenterListener { this.playerName, this.characterLayers, { - x: this.startPositionCalculator.startX, - y: this.startPositionCalculator.startY + ...this.startPositionCalculator.startPosition }, { left: camera.scrollX, @@ -970,8 +969,8 @@ ${escapedMessage} } else { //if the exit points to the current map, we simply teleport the user back to the startLayer this.startPositionCalculator.initPositionFromLayerName(hash, hash); - this.CurrentPlayer.x = this.startPositionCalculator.startX; - this.CurrentPlayer.y = this.startPositionCalculator.startY; + this.CurrentPlayer.x = this.startPositionCalculator.startPosition.x; + this.CurrentPlayer.y = this.startPositionCalculator.startPosition.y; setTimeout(() => this.mapTransitioning = false, 500); } } @@ -1114,8 +1113,8 @@ ${escapedMessage} try { this.CurrentPlayer = new Player( this, - this.startPositionCalculator.startX, - this.startPositionCalculator.startY, + this.startPositionCalculator.startPosition.x, + this.startPositionCalculator.startPosition.y, this.playerName, texturesPromise, PlayerAnimationDirections.Down, diff --git a/front/src/Phaser/Game/StartPositionCalculator.ts b/front/src/Phaser/Game/StartPositionCalculator.ts index 5dc454aa..aaad5415 100644 --- a/front/src/Phaser/Game/StartPositionCalculator.ts +++ b/front/src/Phaser/Game/StartPositionCalculator.ts @@ -6,10 +6,8 @@ import type { GameMap } from './GameMap'; const defaultStartLayerName = 'start'; export class StartPositionCalculator { - public startX!: number; - public startY!: number; - + public startPosition!: PositionInterface constructor( private readonly gameMap: GameMap, @@ -21,24 +19,25 @@ export class StartPositionCalculator { private initStartXAndStartY() { // If there is an init position passed if (this.initPosition !== null) { - this.startX = this.initPosition.x; - this.startY = this.initPosition.y; + this.startPosition = this.initPosition; } else { // Now, let's find the start layer if (this.startLayerName) { this.initPositionFromLayerName(this.startLayerName, this.startLayerName); } - if (this.startX === undefined) { + if (this.startPosition === undefined) { // If we have no start layer specified or if the hash passed does not exist, let's go with the default start position. this.initPositionFromLayerName(defaultStartLayerName, this.startLayerName); } } // Still no start position? Something is wrong with the map, we need a "start" layer. - if (this.startX === undefined) { + if (this.startPosition === undefined) { console.warn('This map is missing a layer named "start" that contains the available default start positions.'); // Let's start in the middle of the map - this.startX = this.mapFile.width * 16; - this.startY = this.mapFile.height * 16; + this.startPosition = { + x: this.mapFile.width * 16, + y: this.mapFile.height * 16 + }; } } @@ -54,8 +53,10 @@ export class StartPositionCalculator { for (const layer of this.gameMap.layersIterator) { if ((selectedOrDefaultLayer === layer.name || layer.name.endsWith('/' + selectedOrDefaultLayer)) && layer.type === 'tilelayer' && (selectedOrDefaultLayer === defaultStartLayerName || this.isStartLayer(layer))) { const startPosition = this.startUser(layer, selectedLayer); - this.startX = startPosition.x + this.mapFile.tilewidth / 2; - this.startY = startPosition.y + this.mapFile.tileheight / 2; + this.startPosition = { + x: startPosition.x + this.mapFile.tilewidth / 2, + y: startPosition.y + this.mapFile.tileheight / 2 + } } } From abfa010bbf59891a1d0d8997d9729d88e5e269ea Mon Sep 17 00:00:00 2001 From: jonny Date: Fri, 25 Jun 2021 18:07:03 +0200 Subject: [PATCH 13/79] added husky to gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 70660058..8fa69985 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ docker-compose.override.yaml *.DS_Store maps/yarn.lock maps/dist/computer.js -maps/dist/computer.js.map \ No newline at end of file +maps/dist/computer.js.map +node_modules +_ \ No newline at end of file From bbdf0a12897429b5edd489b65a6c2d9d67bf253c Mon Sep 17 00:00:00 2001 From: jonny Date: Fri, 25 Jun 2021 18:20:16 +0200 Subject: [PATCH 14/79] fixed merge conflict --- front/src/Phaser/Game/GameScene.ts | 1 - front/src/Phaser/Game/StartPositionCalculator.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index a3b4efb1..06a288bc 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -79,7 +79,6 @@ import { TextUtils } from "../Components/TextUtils"; import { touchScreenManager } from "../../Touch/TouchScreenManager"; import { PinchManager } from "../UserInput/PinchManager"; import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick"; -import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes"; import { waScaleManager } from "../Services/WaScaleManager"; import { EmoteManager } from "./EmoteManager"; diff --git a/front/src/Phaser/Game/StartPositionCalculator.ts b/front/src/Phaser/Game/StartPositionCalculator.ts index de321615..7460c81c 100644 --- a/front/src/Phaser/Game/StartPositionCalculator.ts +++ b/front/src/Phaser/Game/StartPositionCalculator.ts @@ -51,7 +51,7 @@ export class StartPositionCalculator { if (!selectedOrDefaultLayer) { selectedOrDefaultLayer = defaultStartLayerName; } - for (const layer of this.gameMap.layersIterator) { + for (const layer of this.gameMap.flatLayers) { if ( (selectedOrDefaultLayer === layer.name || layer.name.endsWith("/" + selectedOrDefaultLayer)) && layer.type === "tilelayer" && From b0eb241fc32a1d2dacc461ca8a37f2cf3e15ed02 Mon Sep 17 00:00:00 2001 From: jonny Date: Fri, 25 Jun 2021 18:45:15 +0200 Subject: [PATCH 15/79] oO something kept movin the comment to the next line --- front/src/Connexion/RoomConnection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index ce1956f1..1b080a55 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -67,8 +67,8 @@ export class RoomConnection implements RoomConnection { private closed: boolean = false; private tags: string[] = []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any public static setWebsocketFactory(websocketFactory: (url: string) => any): void { - // eslint-disable-line @typescript-eslint/no-explicit-any RoomConnection.websocketFactory = websocketFactory; } From b182a08ca2c6290e37570522ec49037fec45beb7 Mon Sep 17 00:00:00 2001 From: GRL Date: Mon, 28 Jun 2021 09:33:13 +0200 Subject: [PATCH 16/79] correction from review --- docs/maps/api-room.md | 9 ++++++ front/src/Api/IframeListener.ts | 2 -- front/src/Phaser/Game/GameMap.ts | 48 ++++++++++++++++++++++++++--- front/src/Phaser/Game/GameScene.ts | 30 +----------------- maps/tests/Metadata/changeTile.json | 29 +++++++++++++---- 5 files changed, 76 insertions(+), 42 deletions(-) diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index 3bb01cc0..74eafee7 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -118,6 +118,14 @@ WA.room.getCurrentUser().then((user) => { WA.room.changeTile(tiles: TileDescriptor[]): void ``` Replace the tile at the `x` and `y` coordinates in the layer named `layer` by the tile with the id `tile`. + +If `tile` is a string, it's not the id of the tile but the value of the property `name`. +

+ `TileDescriptor` has the following attributes : * **x (number) :** The coordinate x of the tile that you want to replace. * **y (number) :** The coordinate y of the tile that you want to replace. @@ -126,6 +134,7 @@ Replace the tile at the `x` and `y` coordinates in the layer named `layer` by th **Important !** : If you use `tile` as a number, be sure to add the `firstgid` of the tileset of the tile that you want to the id of the tile in Tiled Editor. + Example : ```javascript WA.room.changeTile([ diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index ac3723b7..cbe4dcf3 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -193,8 +193,6 @@ class IframeListener { this._unregisterMenuCommandStream.next(data); }) handleMenuItemRegistrationEvent(payload.data) - } else if (payload.type == "registerMenuCommand" && isMenuItemRegisterEvent(payload.data)) { - this._registerMenuCommandStream.next(payload.data.menutItem) } else if (payload.type == "changeTile" && isChangeTileEvent(payload.data)) { this._changeTileStream.next(payload.data); } diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index ab3e6010..aeb4cb62 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -13,8 +13,9 @@ export class GameMap { private key: number | undefined; private lastProperties = new Map(); private callbacks = new Map>(); + private tileNameMap = new Map(); - private tileSetPropertyMap: { [tile_index: number]: Array } = {} + private tileSetPropertyMap: { [tile_index: number]: Array } = {}; public readonly flatLayers: ITiledMapLayer[]; public readonly phaserLayers: TilemapLayer[] = []; @@ -36,6 +37,9 @@ export class GameMap { if (tile.properties) { this.tileSetPropertyMap[tileset.firstgid + tile.id] = tile.properties tile.properties.forEach(prop => { + if (prop.name == 'name' && typeof prop.value == "string") { + this.tileNameMap.set(prop.value, tileset.firstgid + tile.id); + } if (prop.name == "exitUrl" && typeof prop.value == "string") { this.exitUrls.push(prop.value); } @@ -130,8 +134,8 @@ export class GameMap { return this.map; } - public getTilesetProperties(): { [tile_index: number]: Array } { - return this.tileSetPropertyMap; + private getTileProperty(index: number): Array { + return this.tileSetPropertyMap[index]; } private trigger(propName: string, oldValue: string | number | boolean | undefined, newValue: string | number | boolean | undefined, allProps: Map) { @@ -169,13 +173,47 @@ export class GameMap { } } - public putTileInFlatLayer(index: number, x: number, y: number, layer: string): void { + private putTileInFlatLayer(index: number, x: number, y: number, layer: string): void { const fLayer = this.findLayer(layer); - if (fLayer?.type !== 'tilelayer') { + if ( fLayer == undefined ) { + console.error("The layer that you want to change doesn't exist."); + return; + } + if (fLayer.type !== 'tilelayer') { + console.error("The layer that you want to change is not a tilelayer. Tile can only be put in tilelayer."); return; } // @ts-ignore fLayer.data[x+y*fLayer.height] = index; } + public putTile(tile: string | number, x: number, y: number, layer: string): void { + const phaserLayer = this.findPhaserLayer(layer); + if ( phaserLayer ) { + const tileIndex = this.getIndexForTileType(tile); + if ( tileIndex !== undefined ) { + this.putTileInFlatLayer(tileIndex, x, y, layer); + const phaserTile = phaserLayer.putTileAt(tileIndex, x, y); + for (const property of this.getTileProperty(tileIndex)) { + if ( property.name === "collides" ) { + phaserTile.setCollision(true); + } + } + } + else { + console.error("The tile that you want to place doesn't exist."); + } + } + else { + console.error("The layer that you want to change is not a tilelayer. Tile can only be put in tilelayer."); + } + } + + private getIndexForTileType(tile: string | number): number | undefined { + if (typeof tile == "number") { + return tile; + } + return this.tileNameMap.get(tile); + } + } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 9a46991a..a3a890e6 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -954,19 +954,7 @@ ${escapedMessage} })); this.iframeSubscriptionList.push(iframeListener.changeTileStream.subscribe((eventTiles) => { for (const eventTile of eventTiles) { - const layer = this.gameMap.findPhaserLayer(eventTile.layer); - if ( layer ) { - const tileIndex = this.getIndexForTileType(eventTile.tile); - if ( tileIndex ) { - this.gameMap.putTileInFlatLayer(tileIndex, eventTile.x, eventTile.y, eventTile.layer); - const tile = layer.putTileAt(tileIndex, eventTile.x, eventTile.y); - for (const property of this.gameMap.getTilesetProperties()[tileIndex]) { - if ( property.name === "collides" ) { - tile.setCollision(true); - } - } - } - } + this.gameMap.putTile(eventTile.tile, eventTile.x, eventTile.y, eventTile.layer); } })) @@ -997,22 +985,6 @@ ${escapedMessage} this.dirty = true; } - private getIndexForTileType(tileType: string | number): number | null { - if (typeof tileType == "number") { - return tileType; - } - for (const tileset of this.mapFile.tilesets) { - if (tileset.tiles) { - for (const tilesetTile of tileset.tiles) { - if (tilesetTile.type == tileType) { - return tileset.firstgid + tilesetTile.id - } - } - } - } - return null - } - private getMapDirUrl(): string { return this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/')); } diff --git a/maps/tests/Metadata/changeTile.json b/maps/tests/Metadata/changeTile.json index 834ea9a1..bdf34704 100644 --- a/maps/tests/Metadata/changeTile.json +++ b/maps/tests/Metadata/changeTile.json @@ -82,12 +82,12 @@ { "fontfamily":"Sans Serif", "pixelsize":9, - "text":"Test : \nWalk on the grass\n\nResult : \nThe Yellow Tile open a jitsi with Trigger.\n\nThe Red Tile open cowebsite Wikip\u00e9dia. The highest Red Tile is find by 'string' index, the lowest by 'number' index.\n\nThe White Tile are silent tile. You can not open a bubble in it. (Even if the other player didn't activate the script.)\n\nThe Pale Tile (Lowest) is an exitUrl tile to customMenu.json.\n\nThe Blue Tile are 'collides' tile. The two tile in the center are 'number' index. The others are 'string' index.\n", + "text":"Test : \nWalk on the grass\n\nResult : \nThe Yellow Tile open a jitsi with Trigger.\n\nThe Red Tile open cowebsite Wikip\u00e9dia. The highest Red Tile is find by 'name' property index, the lowest by 'number' index.\n\nThe White Tile are silent tile. You can not open a bubble in it. (Even if the other player didn't activate the script.)\n\nThe Pale Tile (Lowest) is an exitUrl tile to customMenu.json.\n\nThe Blue Tile are 'collides' tile. The two tile in the center are 'number' index. The others are 'name' property index.\n", "wrap":true }, "type":"", "visible":true, - "width":287.674838251912, + "width":274.674838251912, "x":32.5473600365393, "y":128.305680721763 }], @@ -296,12 +296,25 @@ { "id":34, "properties":[ + { + "name":"name", + "type":"string", + "value":"Red" + }, { "name":"openWebsite", "type":"string", "value":"https:\/\/fr.wikipedia.org\/wiki\/Wikip%C3%A9dia:Accueil_principal" - }], - "type":"Red" + }] + }, + { + "id":40, + "properties":[ + { + "name":"name", + "type":"string", + "value":"" + }] }, { "id":44, @@ -310,8 +323,12 @@ "name":"collides", "type":"bool", "value":true - }], - "type":"blue" + }, + { + "name":"name", + "type":"string", + "value":"blue" + }] }, { "id":52, From 0b161a2368ce52161b64def7c155961201394d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 28 Jun 2021 09:56:18 +0200 Subject: [PATCH 17/79] Fixing typos --- maps/tests/Metadata/changeTile.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maps/tests/Metadata/changeTile.json b/maps/tests/Metadata/changeTile.json index bdf34704..71e4c45b 100644 --- a/maps/tests/Metadata/changeTile.json +++ b/maps/tests/Metadata/changeTile.json @@ -82,7 +82,7 @@ { "fontfamily":"Sans Serif", "pixelsize":9, - "text":"Test : \nWalk on the grass\n\nResult : \nThe Yellow Tile open a jitsi with Trigger.\n\nThe Red Tile open cowebsite Wikip\u00e9dia. The highest Red Tile is find by 'name' property index, the lowest by 'number' index.\n\nThe White Tile are silent tile. You can not open a bubble in it. (Even if the other player didn't activate the script.)\n\nThe Pale Tile (Lowest) is an exitUrl tile to customMenu.json.\n\nThe Blue Tile are 'collides' tile. The two tile in the center are 'number' index. The others are 'name' property index.\n", + "text":"Test : \nWalk on the grass\n\nResult : \nThe Yellow Tile opens a Jitsi with Trigger.\n\nThe Red Tile opens cowebsite Wikipedia. The highest Red Tile is found by 'name' property index, the lowest by 'number' index.\n\nThe White Tiles are silent tiles. You cannot open a bubble in it. (Even if the other player didn't activate the script.)\n\nThe Pale Tile (Lowest) is an exitUrl tile to customMenu.json.\n\nThe Blue Tile are 'collides' tile. The two tiles in the center are 'number' index. The others are 'name' property index.\n", "wrap":true }, "type":"", @@ -345,4 +345,4 @@ "type":"map", "version":1.4, "width":10 -} \ No newline at end of file +} From f18291e9d2022a566a3d62e5e0ace142a070ef03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 28 Jun 2021 10:06:56 +0200 Subject: [PATCH 18/79] Referencing test in index.html and adding some text in the test map. --- maps/tests/index.html | 16 ++++ maps/tests/start-tile.json | 192 +++++++++++++++++++------------------ 2 files changed, 115 insertions(+), 93 deletions(-) diff --git a/maps/tests/index.html b/maps/tests/index.html index 0929ab83..7142060a 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -162,6 +162,22 @@ Test animated tiles + + + Success Failure Pending + + + Test start tile (S1) + + + + + Success Failure Pending + + + Test start tile (S2) + + + +
From 651b0a45184934bf119681427518ac11bbfc8bf1 Mon Sep 17 00:00:00 2001 From: GRL Date: Mon, 28 Jun 2021 12:03:06 +0200 Subject: [PATCH 20/79] Add test map for changing tiles by script in index --- maps/tests/index.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/maps/tests/index.html b/maps/tests/index.html index 0929ab83..d1c887ae 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -162,6 +162,14 @@ Test animated tiles + + + Success Failure Pending + + + Test change tiles + + From 71a5e29ae4e2afe1b078b7646b1bc9a93dda3735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 28 Jun 2021 14:13:49 +0200 Subject: [PATCH 23/79] Making script URL dynamic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: David Négrier --- maps/tests/Metadata/changeTile.html | 43 +++++++++++++++++------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/maps/tests/Metadata/changeTile.html b/maps/tests/Metadata/changeTile.html index 2813aa1d..eddf3323 100644 --- a/maps/tests/Metadata/changeTile.html +++ b/maps/tests/Metadata/changeTile.html @@ -1,24 +1,31 @@ - + - - \ No newline at end of file + From 1e57028e6ea507a6da75da8a9c98a3f614150393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 28 Jun 2021 14:58:49 +0200 Subject: [PATCH 24/79] Renaming `changeTile` to `setTiles` --- CHANGELOG.md | 2 +- docs/maps/api-room.md | 14 ++--- front/src/Api/Events/IframeEvent.ts | 4 +- .../{ChangeTileEvent.ts => SetTilesEvent.ts} | 6 +- front/src/Api/IframeListener.ts | 10 +-- front/src/Api/iframe/room.ts | 4 +- front/src/Phaser/Game/GameScene.ts | 2 +- maps/tests/Metadata/changeTile.html | 31 ---------- maps/tests/Metadata/setTiles.html | 31 ++++++++++ .../{changeTile.json => setTiles.json} | 62 +++++++++---------- maps/tests/index.html | 4 +- 11 files changed, 85 insertions(+), 85 deletions(-) rename front/src/Api/Events/{ChangeTileEvent.ts => SetTilesEvent.ts} (56%) delete mode 100644 maps/tests/Metadata/changeTile.html create mode 100644 maps/tests/Metadata/setTiles.html rename maps/tests/Metadata/{changeTile.json => setTiles.json} (94%) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4bc7ba3..c8992891 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ - Use `WA.room.getCurrentUser(): Promise` to get the ID, name and tags of the current player - Use `WA.room.getCurrentRoom(): Promise` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started - Use `WA.ui.registerMenuCommand(): void` to add a custom menu - - Use `WA.room.changeTile(): void` to change an array of tiles + - Use `WA.room.setTiles(): void` to change an array of tiles ## Version 1.4.1 diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index 74eafee7..17e3d48e 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -115,7 +115,7 @@ WA.room.getCurrentUser().then((user) => { ### Changing tiles ``` -WA.room.changeTile(tiles: TileDescriptor[]): void +WA.room.setTiles(tiles: TileDescriptor[]): void ``` Replace the tile at the `x` and `y` coordinates in the layer named `layer` by the tile with the id `tile`. @@ -137,10 +137,10 @@ If `tile` is a string, it's not the id of the tile but the value of the property Example : ```javascript -WA.room.changeTile([ - {x: 6, y: 4, tile: 'blue', layer: 'changeTile'}, - {x: 7, y: 4, tile: 109, layer: 'changeTile'}, - {x: 8, y: 4, tile: 109, layer: 'changeTile'}, - {x: 9, y: 4, tile: 'blue', layer: 'changeTile'} +WA.room.setTiles([ + {x: 6, y: 4, tile: 'blue', layer: 'setTiles'}, + {x: 7, y: 4, tile: 109, layer: 'setTiles'}, + {x: 8, y: 4, tile: 109, layer: 'setTiles'}, + {x: 9, y: 4, tile: 'blue', layer: 'setTiles'} ]); -``` \ No newline at end of file +``` diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index ac6ee1cd..137eccad 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -18,7 +18,7 @@ import type { PlaySoundEvent } from "./PlaySoundEvent"; import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent"; import type { MenuItemRegisterEvent } from './ui/MenuItemRegisterEvent'; import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent"; -import type { ChangeTileEvent } from "./ChangeTileEvent"; +import type { SetTilesEvent } from "./SetTilesEvent"; export interface TypedMessageEvent extends MessageEvent { data: T @@ -46,7 +46,7 @@ export type IframeEventMap = { loadSound: LoadSoundEvent playSound: PlaySoundEvent stopSound: null - changeTile: ChangeTileEvent + setTiles: SetTilesEvent getState: undefined, registerMenuCommand: MenuItemRegisterEvent } diff --git a/front/src/Api/Events/ChangeTileEvent.ts b/front/src/Api/Events/SetTilesEvent.ts similarity index 56% rename from front/src/Api/Events/ChangeTileEvent.ts rename to front/src/Api/Events/SetTilesEvent.ts index 4a071403..24dd2e35 100644 --- a/front/src/Api/Events/ChangeTileEvent.ts +++ b/front/src/Api/Events/SetTilesEvent.ts @@ -1,6 +1,6 @@ import * as tg from "generic-type-guard"; -export const isChangeTileEvent = +export const isSetTilesEvent = tg.isArray( new tg.IsInterface().withProperties({ x: tg.isNumber, @@ -10,6 +10,6 @@ export const isChangeTileEvent = }).get() ); /** - * A message sent from the game to the iFrame when a user enters or leaves a zone marked with the "zone" property. + * A message sent from the iFrame to the game to set one or many tiles. */ -export type ChangeTileEvent = tg.GuardedType; \ No newline at end of file +export type SetTilesEvent = tg.GuardedType; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index cbe4dcf3..e8b0fc0b 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -29,7 +29,7 @@ import type {GameStateEvent} from "./Events/GameStateEvent"; import type {HasPlayerMovedEvent} from "./Events/HasPlayerMovedEvent"; import {isLoadPageEvent} from "./Events/LoadPageEvent"; import {handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent} from "./Events/ui/MenuItemRegisterEvent"; -import {ChangeTileEvent, isChangeTileEvent} from "./Events/ChangeTileEvent"; +import {SetTilesEvent, isSetTilesEvent} from "./Events/SetTilesEvent"; /** * Listens to messages from iframes and turn those messages into easy to use observables. @@ -103,8 +103,8 @@ class IframeListener { private readonly _loadSoundStream: Subject = new Subject(); public readonly loadSoundStream = this._loadSoundStream.asObservable(); - private readonly _changeTileStream: Subject = new Subject(); - public readonly changeTileStream = this._changeTileStream.asObservable(); + private readonly _setTilesStream: Subject = new Subject(); + public readonly setTilesStream = this._setTilesStream.asObservable(); private readonly iframes = new Set(); private readonly iframeCloseCallbacks = new Map void)[]>(); @@ -193,8 +193,8 @@ class IframeListener { this._unregisterMenuCommandStream.next(data); }) handleMenuItemRegistrationEvent(payload.data) - } else if (payload.type == "changeTile" && isChangeTileEvent(payload.data)) { - this._changeTileStream.next(payload.data); + } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { + this._setTilesStream.next(payload.data); } } }, false); diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts index a16a8918..f3d2f360 100644 --- a/front/src/Api/iframe/room.ts +++ b/front/src/Api/iframe/room.ts @@ -137,9 +137,9 @@ class WorkadventureRoomCommands extends IframeApiContribution { + this.iframeSubscriptionList.push(iframeListener.setTilesStream.subscribe((eventTiles) => { for (const eventTile of eventTiles) { this.gameMap.putTile(eventTile.tile, eventTile.x, eventTile.y, eventTile.layer); } diff --git a/maps/tests/Metadata/changeTile.html b/maps/tests/Metadata/changeTile.html deleted file mode 100644 index eddf3323..00000000 --- a/maps/tests/Metadata/changeTile.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - diff --git a/maps/tests/Metadata/setTiles.html b/maps/tests/Metadata/setTiles.html new file mode 100644 index 00000000..90b5a84d --- /dev/null +++ b/maps/tests/Metadata/setTiles.html @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/maps/tests/Metadata/changeTile.json b/maps/tests/Metadata/setTiles.json similarity index 94% rename from maps/tests/Metadata/changeTile.json rename to maps/tests/Metadata/setTiles.json index 71e4c45b..5b281a15 100644 --- a/maps/tests/Metadata/changeTile.json +++ b/maps/tests/Metadata/setTiles.json @@ -20,7 +20,7 @@ "width":10, "x":0, "y":0 - }, + }, { "data":[33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 49, 50, 50, 50, 50, 50, 50, 50, 50, 51], "height":10, @@ -32,7 +32,7 @@ "width":10, "x":0, "y":0 - }, + }, { "data":[0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "height":10, @@ -43,8 +43,8 @@ { "name":"openWebsite", "type":"string", - "value":"changeTile.html" - }, + "value":"setTiles.html" + }, { "name":"openWebsiteAllowApi", "type":"bool", @@ -55,19 +55,19 @@ "width":10, "x":0, "y":0 - }, + }, { "data":[65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 65, 65, 65, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0], "height":10, "id":8, - "name":"changeTile", + "name":"setTiles", "opacity":1, "type":"tilelayer", "visible":true, "width":10, "x":0, "y":0 - }, + }, { "draworder":"topdown", "id":5, @@ -124,7 +124,7 @@ "type":"bool", "value":true }] - }, + }, { "id":1, "properties":[ @@ -133,7 +133,7 @@ "type":"bool", "value":true }] - }, + }, { "id":2, "properties":[ @@ -142,7 +142,7 @@ "type":"bool", "value":true }] - }, + }, { "id":3, "properties":[ @@ -151,7 +151,7 @@ "type":"bool", "value":true }] - }, + }, { "id":4, "properties":[ @@ -160,7 +160,7 @@ "type":"bool", "value":true }] - }, + }, { "id":8, "properties":[ @@ -169,7 +169,7 @@ "type":"bool", "value":true }] - }, + }, { "id":9, "properties":[ @@ -178,7 +178,7 @@ "type":"bool", "value":true }] - }, + }, { "id":10, "properties":[ @@ -187,7 +187,7 @@ "type":"bool", "value":true }] - }, + }, { "id":11, "properties":[ @@ -196,7 +196,7 @@ "type":"bool", "value":true }] - }, + }, { "id":12, "properties":[ @@ -205,7 +205,7 @@ "type":"bool", "value":true }] - }, + }, { "id":16, "properties":[ @@ -214,7 +214,7 @@ "type":"bool", "value":true }] - }, + }, { "id":17, "properties":[ @@ -223,7 +223,7 @@ "type":"bool", "value":true }] - }, + }, { "id":18, "properties":[ @@ -232,7 +232,7 @@ "type":"bool", "value":true }] - }, + }, { "id":19, "properties":[ @@ -241,7 +241,7 @@ "type":"bool", "value":true }] - }, + }, { "id":20, "properties":[ @@ -252,7 +252,7 @@ }] }], "tilewidth":32 - }, + }, { "columns":8, "firstgid":65, @@ -273,7 +273,7 @@ "type":"string", "value":"customMenu.json" }] - }, + }, { "id":27, "properties":[ @@ -281,18 +281,18 @@ "name":"jitsiRoom", "type":"string", "value":"TEST" - }, + }, { "name":"jitsiTrigger", "type":"string", "value":"onaction" - }, + }, { "name":"jitsiUrl", "type":"string", "value":"meet.jit.si" }] - }, + }, { "id":34, "properties":[ @@ -300,13 +300,13 @@ "name":"name", "type":"string", "value":"Red" - }, + }, { "name":"openWebsite", "type":"string", "value":"https:\/\/fr.wikipedia.org\/wiki\/Wikip%C3%A9dia:Accueil_principal" }] - }, + }, { "id":40, "properties":[ @@ -315,7 +315,7 @@ "type":"string", "value":"" }] - }, + }, { "id":44, "properties":[ @@ -323,13 +323,13 @@ "name":"collides", "type":"bool", "value":true - }, + }, { "name":"name", "type":"string", "value":"blue" }] - }, + }, { "id":52, "properties":[ diff --git a/maps/tests/index.html b/maps/tests/index.html index 97a3ad07..37458659 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -164,10 +164,10 @@ - Success Failure Pending + Success Failure Pending - Test change tiles + Test set tiles From 3fd4f9d38423026ef9e651023e8cd0e5b4d7e782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 28 Jun 2021 15:20:27 +0200 Subject: [PATCH 25/79] Adding a warning message if an unauthorized iFrame tries to communicate with WA Closes #1241 --- front/src/Api/IframeListener.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 9311d7b6..0adaac85 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -123,11 +123,18 @@ class IframeListener { } } + const payload = message.data; + if (foundSrc === undefined) { + if (isIframeEventWrapper(payload)) { + console.warn('It seems an iFrame is trying to communicate with WorkAdventure but was not explicitly granted the permission to do so. ' + + 'If you are looking to use the WorkAdventure Scripting API inside an iFrame, you should allow the ' + + 'iFrame to communicate with WorkAdventure by using the "openWebsiteAllowApi" property in your map (or passing "true" as a second' + + 'parameter to WA.nav.openCoWebSite())'); + } return; } - const payload = message.data; if (isIframeEventWrapper(payload)) { if (payload.type === 'showLayer' && isLayerEvent(payload.data)) { this._showLayerStream.next(payload.data); From feab5da2ad36d1905ad2cb52b8eea6d29255fbf6 Mon Sep 17 00:00:00 2001 From: GRL Date: Mon, 28 Jun 2021 15:55:30 +0200 Subject: [PATCH 26/79] Allow a website opened by script to use iframe_api --- docs/maps/api-reference.md | 4 +- front/src/Api/Events/OpenCoWebSiteEvent.ts | 2 + front/src/Api/IframeListener.ts | 2 +- front/src/Api/ScriptUtils.ts | 4 +- front/src/iframe_api.ts | 8 +- maps/tests/Metadata/cowebsiteAllowApi.html | 15 ++++ maps/tests/Metadata/cowebsiteAllowApi.js | 1 + maps/tests/Metadata/cowebsiteAllowApi.json | 98 +++++++++++++++++++++ maps/tests/Metadata/tileset_dungeon.png | Bin 0 -> 9696 bytes 9 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 maps/tests/Metadata/cowebsiteAllowApi.html create mode 100644 maps/tests/Metadata/cowebsiteAllowApi.js create mode 100644 maps/tests/Metadata/cowebsiteAllowApi.json create mode 100644 maps/tests/Metadata/tileset_dungeon.png diff --git a/docs/maps/api-reference.md b/docs/maps/api-reference.md index 9891a88a..49bc9dac 100644 --- a/docs/maps/api-reference.md +++ b/docs/maps/api-reference.md @@ -190,11 +190,11 @@ WA.goToPage('https://www.wikipedia.org/'); ### Opening/closing a web page in an iFrame ``` -openCoWebSite(url: string): void +openCoWebSite(url : string, allowApi: boolean = false, allowPolicy: string = "") : void closeCoWebSite(): void ``` -Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. +Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allow the webpage to use the "IFrame API" and execute script. `allowPolicy` grant additional access rights to the webpage. Example: diff --git a/front/src/Api/Events/OpenCoWebSiteEvent.ts b/front/src/Api/Events/OpenCoWebSiteEvent.ts index 0fbc0ce2..d2937405 100644 --- a/front/src/Api/Events/OpenCoWebSiteEvent.ts +++ b/front/src/Api/Events/OpenCoWebSiteEvent.ts @@ -5,6 +5,8 @@ import * as tg from "generic-type-guard"; export const isOpenCoWebsite = new tg.IsInterface().withProperties({ url: tg.isString, + allowApi: tg.isBoolean, + allowPolicy: tg.isString, }).get(); /** diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 6c3afa31..47f04aad 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -114,7 +114,7 @@ class IframeListener { this._loadSoundStream.next(payload.data); } else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) { - scriptUtils.openCoWebsite(payload.data.url, foundSrc); + scriptUtils.openCoWebsite(payload.data.url, foundSrc, payload.data.allowApi, payload.data.allowPolicy); } else if (payload.type === 'closeCoWebSite') { diff --git a/front/src/Api/ScriptUtils.ts b/front/src/Api/ScriptUtils.ts index e1c94507..08775e4a 100644 --- a/front/src/Api/ScriptUtils.ts +++ b/front/src/Api/ScriptUtils.ts @@ -11,8 +11,8 @@ class ScriptUtils { } - public openCoWebsite(url: string, base: string) { - coWebsiteManager.loadCoWebsite(url, base); + public openCoWebsite(url: string, base: string, allowApi: boolean, allowPolicy: string) { + coWebsiteManager.loadCoWebsite(url, base, allowApi, allowPolicy); } public closeCoWebSite(){ diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index 17b979df..0264d0a6 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -22,7 +22,7 @@ interface WorkAdventureApi { openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup; openTab(url : string): void; goToPage(url : string): void; - openCoWebSite(url : string): void; + openCoWebSite(url : string, allowApi: boolean, allowPolicy: string): void; closeCoWebSite(): void; disablePlayerControls(): void; restorePlayerControls(): void; @@ -166,11 +166,13 @@ window.WA = { }, '*'); }, - openCoWebSite(url : string) : void{ + openCoWebSite(url : string, allowApi: boolean = false, allowPolicy: string = "") : void{ window.parent.postMessage({ "type" : 'openCoWebSite', "data" : { - url + url, + allowApi, + allowPolicy, } as OpenCoWebSiteEvent }, '*'); }, diff --git a/maps/tests/Metadata/cowebsiteAllowApi.html b/maps/tests/Metadata/cowebsiteAllowApi.html new file mode 100644 index 00000000..0076b9e4 --- /dev/null +++ b/maps/tests/Metadata/cowebsiteAllowApi.html @@ -0,0 +1,15 @@ + + + + + + + +

Website opened by script.

+ + + \ No newline at end of file diff --git a/maps/tests/Metadata/cowebsiteAllowApi.js b/maps/tests/Metadata/cowebsiteAllowApi.js new file mode 100644 index 00000000..71ba96fa --- /dev/null +++ b/maps/tests/Metadata/cowebsiteAllowApi.js @@ -0,0 +1 @@ +WA.openCoWebSite("cowebsiteAllowApi.html", true, ""); \ No newline at end of file diff --git a/maps/tests/Metadata/cowebsiteAllowApi.json b/maps/tests/Metadata/cowebsiteAllowApi.json new file mode 100644 index 00000000..55ed615f --- /dev/null +++ b/maps/tests/Metadata/cowebsiteAllowApi.json @@ -0,0 +1,98 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 49, 50, 50, 50, 50, 50, 50, 50, 50, 51], + "height":10, + "id":1, + "name":"bottom", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":116.5, + "id":1, + "name":"", + "rotation":0, + "text": + { + "text":"Test : \nThe iframe is opened by script.\n\nResult : \nA message is send to the chat.", + "wrap":true + }, + "type":"", + "visible":true, + "width":295.875, + "x":11.8125, + "y":188.5 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 16, 0, 0, 0, 16, 16, 16, 0, 0, 16, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 16, 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 16, 16, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":4, + "name":"mushroom", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }], + "nextlayerid":5, + "nextobjectid":2, + "orientation":"orthogonal", + "properties":[ + { + "name":"script", + "type":"string", + "value":"cowebsiteAllowApi.js" + }], + "renderorder":"right-down", + "tiledversion":"1.4.3", + "tileheight":32, + "tilesets":[ + { + "columns":8, + "firstgid":1, + "image":"tileset_dungeon.png", + "imageheight":256, + "imagewidth":256, + "margin":0, + "name":"tileset_dungeon", + "spacing":0, + "tilecount":64, + "tileheight":32, + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":1.4, + "width":10 +} \ No newline at end of file diff --git a/maps/tests/Metadata/tileset_dungeon.png b/maps/tests/Metadata/tileset_dungeon.png new file mode 100644 index 0000000000000000000000000000000000000000..fcac082c33704b31451bc8a58af5982c06586aa6 GIT binary patch literal 9696 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 Date: Mon, 28 Jun 2021 16:05:29 +0200 Subject: [PATCH 27/79] Update docs/maps/api-reference.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Négrier --- docs/maps/api-reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/maps/api-reference.md b/docs/maps/api-reference.md index 49bc9dac..e5fc2c48 100644 --- a/docs/maps/api-reference.md +++ b/docs/maps/api-reference.md @@ -194,7 +194,7 @@ openCoWebSite(url : string, allowApi: boolean = false, allowPolicy: string = "") closeCoWebSite(): void ``` -Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allow the webpage to use the "IFrame API" and execute script. `allowPolicy` grant additional access rights to the webpage. +Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow). Example: From 06920a227121597e9bef3bc033827e01ce0b0b16 Mon Sep 17 00:00:00 2001 From: GRL Date: Mon, 28 Jun 2021 16:13:38 +0200 Subject: [PATCH 28/79] Use dynamic Iframe API --- maps/tests/Metadata/cowebsiteAllowApi.html | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/maps/tests/Metadata/cowebsiteAllowApi.html b/maps/tests/Metadata/cowebsiteAllowApi.html index 0076b9e4..ded7193e 100644 --- a/maps/tests/Metadata/cowebsiteAllowApi.html +++ b/maps/tests/Metadata/cowebsiteAllowApi.html @@ -1,9 +1,12 @@ - From 389ca25b6a85428720d69bd2400244ee92810493 Mon Sep 17 00:00:00 2001 From: GRL Date: Mon, 28 Jun 2021 18:00:48 +0200 Subject: [PATCH 29/79] Cowebsite opened by script can use Iframe Api --- docs/maps/api-nav.md | 4 +- front/src/Api/Events/OpenCoWebSiteEvent.ts | 2 + front/src/Api/IframeListener.ts | 14 +++- front/src/Api/ScriptUtils.ts | 4 +- front/src/Api/iframe/nav.ts | 6 +- front/src/iframe_api.ts | 4 +- maps/tests/Metadata/cowebsiteAllowApi.html | 17 ++++ maps/tests/Metadata/cowebsiteAllowApi.js | 1 + maps/tests/Metadata/cowebsiteAllowApi.json | 98 ++++++++++++++++++++++ maps/tests/index.html | 8 ++ 10 files changed, 149 insertions(+), 9 deletions(-) create mode 100644 maps/tests/Metadata/cowebsiteAllowApi.html create mode 100644 maps/tests/Metadata/cowebsiteAllowApi.js create mode 100644 maps/tests/Metadata/cowebsiteAllowApi.json diff --git a/docs/maps/api-nav.md b/docs/maps/api-nav.md index 29323632..2abc5aff 100644 --- a/docs/maps/api-nav.md +++ b/docs/maps/api-nav.md @@ -52,11 +52,11 @@ WA.nav.goToRoom("/_/global/.json#start-layer-2") ### Opening/closing a web page in an iFrame ``` -WA.nav.openCoWebSite(url: string): void +WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void WA.nav.closeCoWebSite(): void ``` -Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. +Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` parameter allow the iframe to use the "IFrame API" and communicate with WorkAdventure. `allowPolicy` parameter grant additional rights to the iframe. Example: diff --git a/front/src/Api/Events/OpenCoWebSiteEvent.ts b/front/src/Api/Events/OpenCoWebSiteEvent.ts index 0fbc0ce2..d2937405 100644 --- a/front/src/Api/Events/OpenCoWebSiteEvent.ts +++ b/front/src/Api/Events/OpenCoWebSiteEvent.ts @@ -5,6 +5,8 @@ import * as tg from "generic-type-guard"; export const isOpenCoWebsite = new tg.IsInterface().withProperties({ url: tg.isString, + allowApi: tg.isBoolean, + allowPolicy: tg.isString, }).get(); /** diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 0adaac85..1c2adfdb 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -135,6 +135,8 @@ class IframeListener { return; } + foundSrc = this.getBaseUrl(foundSrc, message.source); + if (isIframeEventWrapper(payload)) { if (payload.type === 'showLayer' && isLayerEvent(payload.data)) { this._showLayerStream.next(payload.data); @@ -168,7 +170,8 @@ class IframeListener { this._loadSoundStream.next(payload.data); } else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) { - scriptUtils.openCoWebsite(payload.data.url, foundSrc); + console.log(foundSrc); + scriptUtils.openCoWebsite(payload.data.url, foundSrc, payload.data.allowApi, payload.data.allowPolicy); } else if (payload.type === 'closeCoWebSite') { @@ -281,6 +284,15 @@ class IframeListener { } + private getBaseUrl(src: string, source: MessageEventSource | null): string{ + for (const script of this.scripts) { + if (script[1].contentWindow === source) { + return script[0]; + } + } + return src; + } + private static getIFrameId(scriptUrl: string): string { return 'script' + btoa(scriptUrl); } diff --git a/front/src/Api/ScriptUtils.ts b/front/src/Api/ScriptUtils.ts index e1c94507..75a18dc0 100644 --- a/front/src/Api/ScriptUtils.ts +++ b/front/src/Api/ScriptUtils.ts @@ -11,8 +11,8 @@ class ScriptUtils { } - public openCoWebsite(url: string, base: string) { - coWebsiteManager.loadCoWebsite(url, base); + public openCoWebsite(url: string, base: string, api: boolean, policy: string) { + coWebsiteManager.loadCoWebsite(url, base, api, policy); } public closeCoWebSite(){ diff --git a/front/src/Api/iframe/nav.ts b/front/src/Api/iframe/nav.ts index 0d31c1ea..7c7d38b4 100644 --- a/front/src/Api/iframe/nav.ts +++ b/front/src/Api/iframe/nav.ts @@ -36,11 +36,13 @@ class WorkadventureNavigationCommands extends IframeApiContribution + + + + + + + \ No newline at end of file diff --git a/maps/tests/Metadata/cowebsiteAllowApi.js b/maps/tests/Metadata/cowebsiteAllowApi.js new file mode 100644 index 00000000..4dac8ed2 --- /dev/null +++ b/maps/tests/Metadata/cowebsiteAllowApi.js @@ -0,0 +1 @@ +WA.nav.openCoWebSite("cowebsiteAllowApi.html", true, ""); \ No newline at end of file diff --git a/maps/tests/Metadata/cowebsiteAllowApi.json b/maps/tests/Metadata/cowebsiteAllowApi.json new file mode 100644 index 00000000..55ed615f --- /dev/null +++ b/maps/tests/Metadata/cowebsiteAllowApi.json @@ -0,0 +1,98 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 49, 50, 50, 50, 50, 50, 50, 50, 50, 51], + "height":10, + "id":1, + "name":"bottom", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":116.5, + "id":1, + "name":"", + "rotation":0, + "text": + { + "text":"Test : \nThe iframe is opened by script.\n\nResult : \nA message is send to the chat.", + "wrap":true + }, + "type":"", + "visible":true, + "width":295.875, + "x":11.8125, + "y":188.5 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 16, 0, 0, 0, 16, 16, 16, 0, 0, 16, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 16, 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 16, 16, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":4, + "name":"mushroom", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }], + "nextlayerid":5, + "nextobjectid":2, + "orientation":"orthogonal", + "properties":[ + { + "name":"script", + "type":"string", + "value":"cowebsiteAllowApi.js" + }], + "renderorder":"right-down", + "tiledversion":"1.4.3", + "tileheight":32, + "tilesets":[ + { + "columns":8, + "firstgid":1, + "image":"tileset_dungeon.png", + "imageheight":256, + "imagewidth":256, + "margin":0, + "name":"tileset_dungeon", + "spacing":0, + "tilecount":64, + "tileheight":32, + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":1.4, + "width":10 +} \ No newline at end of file diff --git a/maps/tests/index.html b/maps/tests/index.html index 1f5f2a0e..93e4fd58 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -162,6 +162,14 @@ Test animated tiles + + + Success Failure Pending + + + Test cowebsite opened by script is allowed to use IFrame API + + \ No newline at end of file diff --git a/maps/tests/index.html b/maps/tests/index.html index 9c95c281..b7e88925 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -90,6 +90,14 @@ Test the HelpCameraSettingScene + + + Success Failure Pending + + + Test a iframe opened by a script can use Iframe API + + +

Website opened by script.

- \ No newline at end of file + diff --git a/maps/tests/Metadata/cowebsiteAllowApi.js b/maps/tests/Metadata/cowebsiteAllowApi.js index 4dac8ed2..56c7b767 100644 --- a/maps/tests/Metadata/cowebsiteAllowApi.js +++ b/maps/tests/Metadata/cowebsiteAllowApi.js @@ -1 +1 @@ -WA.nav.openCoWebSite("cowebsiteAllowApi.html", true, ""); \ No newline at end of file +WA.nav.openCoWebSite("cowebsiteAllowApi.html", true, ""); diff --git a/maps/tests/index.html b/maps/tests/index.html index 93e4fd58..bf1a056f 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -106,6 +106,14 @@ Test the HelpCameraSettingScene + + + Success Failure Pending + + + Test a iframe opened by a script can use Iframe API + + Success Failure Pending diff --git a/yarn.lock b/yarn.lock index 166b84c7..b9698f61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,6 @@ # yarn lockfile v1 -husky@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/husky/-/husky-6.0.0.tgz#810f11869adf51604c32ea577edbc377d7f9319e" +"husky@^6.0.0": + "resolved" "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz" + "version" "6.0.0" From bfcdd31ed2a3e54175640d1be95dfdf57dc2132b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 30 Jun 2021 10:15:55 +0200 Subject: [PATCH 40/79] Fixing NPM package generation The generation was broken due to the refactoring in several classes (some of them where not properly exported). Also, trying to generate the NPM package on every build now (to detect issues). --- .github/workflows/push-to-npm.yml | 5 ++ front/package.json | 2 +- front/src/Api/iframe/chat.ts | 40 ++++++------ front/src/Api/iframe/controls.ts | 11 ++-- front/src/Api/iframe/nav.ts | 51 +++++++-------- front/src/Api/iframe/player.ts | 26 ++++---- front/src/Api/iframe/room.ts | 2 +- front/src/Api/iframe/sound.ts | 16 ++--- front/src/Api/iframe/ui.ts | 104 +++++++++++++++--------------- 9 files changed, 129 insertions(+), 128 deletions(-) diff --git a/.github/workflows/push-to-npm.yml b/.github/workflows/push-to-npm.yml index 3b90e846..fd247b11 100644 --- a/.github/workflows/push-to-npm.yml +++ b/.github/workflows/push-to-npm.yml @@ -2,6 +2,7 @@ name: Push @workadventure/iframe-api-typings to NPM on: release: types: [created] + push: jobs: build: runs-on: ubuntu-latest @@ -52,6 +53,9 @@ jobs: - name: Copy typings to package dir run: cp front/dist/src/iframe_api.d.ts front/packages/iframe-api-typings/iframe_api.d.ts + - name: Copy typings to package dir (2) + run: cp -R front/dist/src/Api front/packages/iframe-api-typings/Api + - name: Install dependencies in package run: yarn install working-directory: "front/packages/iframe-api-typings" @@ -61,3 +65,4 @@ jobs: working-directory: "front/packages/iframe-api-typings" env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + if: ${{ github.event_name == 'release' }} diff --git a/front/package.json b/front/package.json index 20c6b073..0e0439ed 100644 --- a/front/package.json +++ b/front/package.json @@ -60,7 +60,7 @@ "templater": "cross-env ./templater.sh", "serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open", "build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack", - "build-typings": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production NODE_ENV=production BUILD_TYPINGS=1 webpack", + "build-typings": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production BUILD_TYPINGS=1 webpack", "test": "TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json", "lint": "node_modules/.bin/eslint src/ . --ext .ts", "fix": "node_modules/.bin/eslint --fix src/ . --ext .ts", diff --git a/front/src/Api/iframe/chat.ts b/front/src/Api/iframe/chat.ts index 7d8e6f71..5797df5a 100644 --- a/front/src/Api/iframe/chat.ts +++ b/front/src/Api/iframe/chat.ts @@ -1,30 +1,30 @@ -import type { ChatEvent } from '../Events/ChatEvent' -import { isUserInputChatEvent, UserInputChatEvent } from '../Events/UserInputChatEvent' -import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution' +import type { ChatEvent } from "../Events/ChatEvent"; +import { isUserInputChatEvent, UserInputChatEvent } from "../Events/UserInputChatEvent"; +import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; import { apiCallback } from "./registeredCallbacks"; -import {Subject} from "rxjs"; +import { Subject } from "rxjs"; const chatStream = new Subject(); -class WorkadventureChatCommands extends IframeApiContribution { - - callbacks = [apiCallback({ - callback: (event: UserInputChatEvent) => { - chatStream.next(event.message); - }, - type: "userInputChat", - typeChecker: isUserInputChatEvent - })] - +export class WorkadventureChatCommands extends IframeApiContribution { + callbacks = [ + apiCallback({ + callback: (event: UserInputChatEvent) => { + chatStream.next(event.message); + }, + type: "userInputChat", + typeChecker: isUserInputChatEvent, + }), + ]; sendChatMessage(message: string, author: string) { sendToWorkadventure({ - type: 'chat', + type: "chat", data: { - 'message': message, - 'author': author - } - }) + message: message, + author: author, + }, + }); } /** @@ -35,4 +35,4 @@ class WorkadventureChatCommands extends IframeApiContribution { - callbacks = [] +export class WorkadventureControlsCommands extends IframeApiContribution { + callbacks = []; disablePlayerControls(): void { - sendToWorkadventure({ 'type': 'disablePlayerControls', data: null }); + sendToWorkadventure({ type: "disablePlayerControls", data: null }); } restorePlayerControls(): void { - sendToWorkadventure({ 'type': 'restorePlayerControls', data: null }); + sendToWorkadventure({ type: "restorePlayerControls", data: null }); } } - export default new WorkadventureControlsCommands(); diff --git a/front/src/Api/iframe/nav.ts b/front/src/Api/iframe/nav.ts index 7c7d38b4..f051a7c0 100644 --- a/front/src/Api/iframe/nav.ts +++ b/front/src/Api/iframe/nav.ts @@ -1,59 +1,56 @@ -import type { GoToPageEvent } from '../Events/GoToPageEvent'; -import type { OpenTabEvent } from '../Events/OpenTabEvent'; -import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution'; -import type {OpenCoWebSiteEvent} from "../Events/OpenCoWebSiteEvent"; -import type {LoadPageEvent} from "../Events/LoadPageEvent"; - - -class WorkadventureNavigationCommands extends IframeApiContribution { - callbacks = [] +import type { GoToPageEvent } from "../Events/GoToPageEvent"; +import type { OpenTabEvent } from "../Events/OpenTabEvent"; +import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; +import type { OpenCoWebSiteEvent } from "../Events/OpenCoWebSiteEvent"; +import type { LoadPageEvent } from "../Events/LoadPageEvent"; +export class WorkadventureNavigationCommands extends IframeApiContribution { + callbacks = []; openTab(url: string): void { sendToWorkadventure({ - "type": 'openTab', - "data": { - url - } + type: "openTab", + data: { + url, + }, }); } goToPage(url: string): void { sendToWorkadventure({ - "type": 'goToPage', - "data": { - url - } + type: "goToPage", + data: { + url, + }, }); } goToRoom(url: string): void { sendToWorkadventure({ - "type": 'loadPage', - "data": { - url - } + type: "loadPage", + data: { + url, + }, }); } openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void { sendToWorkadventure({ - "type": 'openCoWebSite', - "data": { + type: "openCoWebSite", + data: { url, allowApi, allowPolicy, - } + }, }); } closeCoWebSite(): void { sendToWorkadventure({ - "type": 'closeCoWebSite', - data: null + type: "closeCoWebSite", + data: null, }); } } - export default new WorkadventureNavigationCommands(); diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts index 67c012f7..e130d3f2 100644 --- a/front/src/Api/iframe/player.ts +++ b/front/src/Api/iframe/player.ts @@ -1,29 +1,29 @@ -import {IframeApiContribution, sendToWorkadventure} from "./IframeApiContribution"; -import type {HasPlayerMovedEvent, HasPlayerMovedEventCallback} from "../Events/HasPlayerMovedEvent"; -import {Subject} from "rxjs"; -import {apiCallback} from "./registeredCallbacks"; -import {isHasPlayerMovedEvent} from "../Events/HasPlayerMovedEvent"; +import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; +import type { HasPlayerMovedEvent, HasPlayerMovedEventCallback } from "../Events/HasPlayerMovedEvent"; +import { Subject } from "rxjs"; +import { apiCallback } from "./registeredCallbacks"; +import { isHasPlayerMovedEvent } from "../Events/HasPlayerMovedEvent"; const moveStream = new Subject(); -class WorkadventurePlayerCommands extends IframeApiContribution { +export class WorkadventurePlayerCommands extends IframeApiContribution { callbacks = [ apiCallback({ - type: 'hasPlayerMoved', + type: "hasPlayerMoved", typeChecker: isHasPlayerMovedEvent, callback: (payloadData) => { moveStream.next(payloadData); - } + }, }), - ] + ]; onPlayerMove(callback: HasPlayerMovedEventCallback): void { moveStream.subscribe(callback); sendToWorkadventure({ - type: 'onPlayerMove', - data: null - }) + type: "onPlayerMove", + data: null, + }); } } -export default new WorkadventurePlayerCommands(); \ No newline at end of file +export default new WorkadventurePlayerCommands(); diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts index 817141f4..e6b64db1 100644 --- a/front/src/Api/iframe/room.ts +++ b/front/src/Api/iframe/room.ts @@ -48,7 +48,7 @@ function getDataLayer(): Promise { }); } -class WorkadventureRoomCommands extends IframeApiContribution { +export class WorkadventureRoomCommands extends IframeApiContribution { callbacks = [ apiCallback({ callback: (payloadData: EnterLeaveEvent) => { diff --git a/front/src/Api/iframe/sound.ts b/front/src/Api/iframe/sound.ts index 70430b46..1e5d6157 100644 --- a/front/src/Api/iframe/sound.ts +++ b/front/src/Api/iframe/sound.ts @@ -1,17 +1,15 @@ -import type { LoadSoundEvent } from '../Events/LoadSoundEvent'; -import type { PlaySoundEvent } from '../Events/PlaySoundEvent'; -import type { StopSoundEvent } from '../Events/StopSoundEvent'; -import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution'; -import {Sound} from "./Sound/Sound"; +import type { LoadSoundEvent } from "../Events/LoadSoundEvent"; +import type { PlaySoundEvent } from "../Events/PlaySoundEvent"; +import type { StopSoundEvent } from "../Events/StopSoundEvent"; +import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; +import { Sound } from "./Sound/Sound"; -class WorkadventureSoundCommands extends IframeApiContribution { - callbacks = [] +export class WorkadventureSoundCommands extends IframeApiContribution { + callbacks = []; loadSound(url: string): Sound { return new Sound(url); } - } - export default new WorkadventureSoundCommands(); diff --git a/front/src/Api/iframe/ui.ts b/front/src/Api/iframe/ui.ts index c7655b84..61c7076e 100644 --- a/front/src/Api/iframe/ui.ts +++ b/front/src/Api/iframe/ui.ts @@ -1,53 +1,55 @@ -import { isButtonClickedEvent } from '../Events/ButtonClickedEvent'; -import { isMenuItemClickedEvent } from '../Events/ui/MenuItemClickedEvent'; -import type { MenuItemRegisterEvent } from '../Events/ui/MenuItemRegisterEvent'; -import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution'; +import { isButtonClickedEvent } from "../Events/ButtonClickedEvent"; +import { isMenuItemClickedEvent } from "../Events/ui/MenuItemClickedEvent"; +import type { MenuItemRegisterEvent } from "../Events/ui/MenuItemRegisterEvent"; +import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; import { apiCallback } from "./registeredCallbacks"; import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescriptor"; import { Popup } from "./Ui/Popup"; let popupId = 0; const popups: Map = new Map(); -const popupCallbacks: Map> = new Map>(); +const popupCallbacks: Map> = new Map< + number, + Map +>(); -const menuCallbacks: Map void> = new Map() +const menuCallbacks: Map void> = new Map(); interface ZonedPopupOptions { - zone: string - objectLayerName?: string, - popupText: string, - delay?: number - popupOptions: Array + zone: string; + objectLayerName?: string; + popupText: string; + delay?: number; + popupOptions: Array; } - -class WorkAdventureUiCommands extends IframeApiContribution { - - callbacks = [apiCallback({ - type: "buttonClickedEvent", - typeChecker: isButtonClickedEvent, - callback: (payloadData) => { - const callback = popupCallbacks.get(payloadData.popupId)?.get(payloadData.buttonId); - const popup = popups.get(payloadData.popupId); - if (popup === undefined) { - throw new Error('Could not find popup with ID "' + payloadData.popupId + '"'); - } - if (callback) { - callback(popup); - } - } - }), - apiCallback({ - type: "menuItemClicked", - typeChecker: isMenuItemClickedEvent, - callback: event => { - const callback = menuCallbacks.get(event.menuItem); - if (callback) { - callback(event.menuItem) - } - } - })]; - +export class WorkAdventureUiCommands extends IframeApiContribution { + callbacks = [ + apiCallback({ + type: "buttonClickedEvent", + typeChecker: isButtonClickedEvent, + callback: (payloadData) => { + const callback = popupCallbacks.get(payloadData.popupId)?.get(payloadData.buttonId); + const popup = popups.get(payloadData.popupId); + if (popup === undefined) { + throw new Error('Could not find popup with ID "' + payloadData.popupId + '"'); + } + if (callback) { + callback(popup); + } + }, + }), + apiCallback({ + type: "menuItemClicked", + typeChecker: isMenuItemClickedEvent, + callback: (event) => { + const callback = menuCallbacks.get(event.menuItem); + if (callback) { + callback(event.menuItem); + } + }, + }), + ]; openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup { popupId++; @@ -66,40 +68,40 @@ class WorkAdventureUiCommands extends IframeApiContribution { return { label: button.label, - className: button.className + className: button.className, }; - }) - } + }), + }, }); - popups.set(popupId, popup) + popups.set(popupId, popup); return popup; } registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void) { menuCallbacks.set(commandDescriptor, callback); sendToWorkadventure({ - 'type': 'registerMenuCommand', - 'data': { - menutItem: commandDescriptor - } + type: "registerMenuCommand", + data: { + menutItem: commandDescriptor, + }, }); } displayBubble(): void { - sendToWorkadventure({ 'type': 'displayBubble', data: null }); + sendToWorkadventure({ type: "displayBubble", data: null }); } removeBubble(): void { - sendToWorkadventure({ 'type': 'removeBubble', data: null }); + sendToWorkadventure({ type: "removeBubble", data: null }); } } From 50fcc1caaab973e7306c3fd4623b9f11be5d0a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 30 Jun 2021 10:40:53 +0200 Subject: [PATCH 41/79] Fixing signature of openCoWebSite --- front/src/iframe_api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index 0264d0a6..9fa204b9 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -22,7 +22,7 @@ interface WorkAdventureApi { openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup; openTab(url : string): void; goToPage(url : string): void; - openCoWebSite(url : string, allowApi: boolean, allowPolicy: string): void; + openCoWebSite(url : string, allowApi?: boolean, allowPolicy?: string): void; closeCoWebSite(): void; disablePlayerControls(): void; restorePlayerControls(): void; From 65cefb35845ebc340b73f49806df468ae3058b05 Mon Sep 17 00:00:00 2001 From: jonny Date: Thu, 1 Jul 2021 15:50:40 +0200 Subject: [PATCH 42/79] fixed invalid unauathorized handler --- back/src/Controller/DebugController.ts | 2 +- pusher/src/Controller/DebugController.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/back/src/Controller/DebugController.ts b/back/src/Controller/DebugController.ts index b7f037fd..88287753 100644 --- a/back/src/Controller/DebugController.ts +++ b/back/src/Controller/DebugController.ts @@ -15,7 +15,7 @@ export class DebugController { const query = parse(req.getQuery()); if (query.token !== ADMIN_API_TOKEN) { - return res.status(401).send("Invalid token sent!"); + return res.writeStatus("401 Unauthorized").end("Invalid token sent!"); } return res diff --git a/pusher/src/Controller/DebugController.ts b/pusher/src/Controller/DebugController.ts index 0b0d188b..e9e3540d 100644 --- a/pusher/src/Controller/DebugController.ts +++ b/pusher/src/Controller/DebugController.ts @@ -16,7 +16,7 @@ export class DebugController { const query = parse(req.getQuery()); if (query.token !== ADMIN_API_TOKEN) { - return res.status(401).send("Invalid token sent!"); + return res.writeStatus("401 Unauthorized").end("Invalid token sent!"); } return res From c5b5326480c1587f9616225d3d5af55dd437199c Mon Sep 17 00:00:00 2001 From: GRL Date: Fri, 2 Jul 2021 14:40:18 +0200 Subject: [PATCH 43/79] setProperty function doesn't set an empty array if property doesn't exist --- CONTRIBUTING.md | 2 +- front/src/Phaser/Game/GameScene.ts | 6 ++++-- maps/tests/Metadata/setTiles.json | 5 ----- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b85d0a98..8bbbc93e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,7 +42,7 @@ Before committing, be sure to install the "Prettier" precommit hook that will re In order to enable the "Prettier" precommit hook, at the root of the project, run: ```console -$ yarn run install +$ yarn install $ yarn run prepare ``` diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 19da51a0..3fbf79a4 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1075,11 +1075,13 @@ ${escapedMessage} console.warn('Could not find layer "' + layerName + '" when calling setProperty'); return; } - const property = (layer.properties as ITiledMapLayerProperty[])?.find( + if (layer.properties === undefined) { + layer.properties = []; + } + const property = (layer.properties as ITiledMapLayerProperty[]).find( (property) => property.name === propertyName ); if (property === undefined) { - layer.properties = []; layer.properties.push({ name: propertyName, type: typeof propertyValue, value: propertyValue }); return; } diff --git a/maps/tests/Metadata/setTiles.json b/maps/tests/Metadata/setTiles.json index 5b281a15..7eb9791a 100644 --- a/maps/tests/Metadata/setTiles.json +++ b/maps/tests/Metadata/setTiles.json @@ -286,11 +286,6 @@ "name":"jitsiTrigger", "type":"string", "value":"onaction" - }, - { - "name":"jitsiUrl", - "type":"string", - "value":"meet.jit.si" }] }, { From 8644389d7efb3783aaae06175c9d155ba4fb6230 Mon Sep 17 00:00:00 2001 From: GRL Date: Fri, 2 Jul 2021 14:45:27 +0200 Subject: [PATCH 44/79] remove unnecessary conversion type --- front/src/Phaser/Game/GameScene.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 3fbf79a4..d137c9e0 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1078,9 +1078,7 @@ ${escapedMessage} if (layer.properties === undefined) { layer.properties = []; } - const property = (layer.properties as ITiledMapLayerProperty[]).find( - (property) => property.name === propertyName - ); + const property = layer.properties.find((property) => property.name === propertyName); if (property === undefined) { layer.properties.push({ name: propertyName, type: typeof propertyValue, value: propertyValue }); return; From 5b4a72ea1fa19a5600bc6ccb0f02d343746c2317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 2 Jul 2021 16:41:24 +0200 Subject: [PATCH 45/79] Add new "query/answer" utility functions for the scripting API So far, the scripting API was using events to communicate between WA and the iFrame. But often, the scripting API might actually want to "ask" WA a question and wait for an answer. We dealt with this by using 2 unrelated events (in a mostly painful way). This commit adds a "queryWorkadventure" utility function in the iFrame API that allows us to send a query, and to wait for an answer. The query and answer events have a unique ID to be sure the answer matches the correct query. On the WA side, a new `IframeListener.registerAnswerer` method can be used to register a possible answer. --- front/src/Api/Events/IframeEvent.ts | 47 ++++- front/src/Api/IframeListener.ts | 192 +++++++++++------- front/src/Api/iframe/IframeApiContribution.ts | 33 ++- front/src/Api/iframe/room.ts | 21 +- front/src/Phaser/Components/TextUtils.ts | 1 - front/src/Phaser/Game/GameScene.ts | 23 +-- front/src/iframe_api.ts | 44 +++- 7 files changed, 248 insertions(+), 113 deletions(-) diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 67775c03..fc3384f8 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -23,6 +23,9 @@ export interface TypedMessageEvent extends MessageEvent { data: T; } +/** + * List event types sent from an iFrame to WorkAdventure + */ export type IframeEventMap = { loadPage: LoadPageEvent; chat: ChatEvent; @@ -62,7 +65,6 @@ export interface IframeResponseEventMap { enterEvent: EnterLeaveEvent; leaveEvent: EnterLeaveEvent; buttonClickedEvent: ButtonClickedEvent; - gameState: GameStateEvent; hasPlayerMoved: HasPlayerMovedEvent; dataLayer: DataLayerEvent; menuItemClicked: MenuItemClickedEvent; @@ -76,3 +78,46 @@ export interface IframeResponseEvent { export const isIframeResponseEventWrapper = (event: { type?: string; }): event is IframeResponseEvent => typeof event.type === "string"; + + +/** + * List event types sent from an iFrame to WorkAdventure that expect a unique answer from WorkAdventure along the type for the answer from WorkAdventure to the iFrame + */ +export type IframeQueryMap = { + getState: { + query: undefined, + answer: GameStateEvent + }, +} + +export interface IframeQuery { + type: T; + data: IframeQueryMap[T]['query']; +} + +export interface IframeQueryWrapper { + id: number; + query: IframeQuery; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isIframeQuery = (event: any): event is IframeQuery => typeof event.type === 'string'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isIframeQueryWrapper = (event: any): event is IframeQueryWrapper => typeof event.id === 'number' && isIframeQuery(event.query); + +export interface IframeAnswerEvent { + id: number; + type: T; + data: IframeQueryMap[T]['answer']; +} + +export const isIframeAnswerEvent = (event: { type?: string, id?: number }): event is IframeAnswerEvent => typeof event.type === 'string' && typeof event.id === 'number'; + +export interface IframeErrorAnswerEvent { + id: number; + type: keyof IframeQueryMap; + error: string; +} + +export const isIframeErrorAnswerEvent = (event: { type?: string, id?: number, error?: string }): event is IframeErrorAnswerEvent => typeof event.type === 'string' && typeof event.id === 'number' && typeof event.error === 'string'; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index b97fc567..334535a5 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -10,11 +10,13 @@ import { scriptUtils } from "./ScriptUtils"; import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent"; import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent"; import { + IframeErrorAnswerEvent, IframeEvent, - IframeEventMap, + IframeEventMap, IframeQueryMap, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper, + isIframeQueryWrapper, TypedMessageEvent, } from "./Events/IframeEvent"; import type { UserInputChatEvent } from "./Events/UserInputChatEvent"; @@ -31,6 +33,8 @@ import { isLoadPageEvent } from "./Events/LoadPageEvent"; import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent"; import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent"; +type AnswererCallback = (query: IframeQueryMap[T]['query']) => Promise; + /** * Listens to messages from iframes and turn those messages into easy to use observables. * Also allows to send messages to those iframes. @@ -81,9 +85,6 @@ class IframeListener { private readonly _setPropertyStream: Subject = new Subject(); public readonly setPropertyStream = this._setPropertyStream.asObservable(); - private readonly _gameStateStream: Subject = new Subject(); - public readonly gameStateStream = this._gameStateStream.asObservable(); - private readonly _dataLayerChangeStream: Subject = new Subject(); public readonly dataLayerChangeStream = this._dataLayerChangeStream.asObservable(); @@ -110,6 +111,10 @@ class IframeListener { private readonly scripts = new Map(); private sendPlayerMove: boolean = false; + private answerers: { + [key in keyof IframeQueryMap]?: AnswererCallback + } = {}; + init() { window.addEventListener( "message", @@ -119,7 +124,7 @@ class IframeListener { // Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain). let foundSrc: string | undefined; - let iframe: HTMLIFrameElement; + let iframe: HTMLIFrameElement | undefined; for (iframe of this.iframes) { if (iframe.contentWindow === message.source) { foundSrc = iframe.src; @@ -129,7 +134,7 @@ class IframeListener { const payload = message.data; - if (foundSrc === undefined) { + if (foundSrc === undefined || iframe === undefined) { if (isIframeEventWrapper(payload)) { console.warn( "It seems an iFrame is trying to communicate with WorkAdventure but was not explicitly granted the permission to do so. " + @@ -143,65 +148,101 @@ class IframeListener { foundSrc = this.getBaseUrl(foundSrc, message.source); - if (isIframeEventWrapper(payload)) { - if (payload.type === "showLayer" && isLayerEvent(payload.data)) { - this._showLayerStream.next(payload.data); - } else if (payload.type === "hideLayer" && isLayerEvent(payload.data)) { - this._hideLayerStream.next(payload.data); - } else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) { - this._setPropertyStream.next(payload.data); - } else if (payload.type === "chat" && isChatEvent(payload.data)) { - this._chatStream.next(payload.data); - } else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) { - this._openPopupStream.next(payload.data); - } else if (payload.type === "closePopup" && isClosePopupEvent(payload.data)) { - this._closePopupStream.next(payload.data); - } else if (payload.type === "openTab" && isOpenTabEvent(payload.data)) { - scriptUtils.openTab(payload.data.url); - } else if (payload.type === "goToPage" && isGoToPageEvent(payload.data)) { - scriptUtils.goToPage(payload.data.url); - } else if (payload.type === "loadPage" && isLoadPageEvent(payload.data)) { - this._loadPageStream.next(payload.data.url); - } else if (payload.type === "playSound" && isPlaySoundEvent(payload.data)) { - this._playSoundStream.next(payload.data); - } else if (payload.type === "stopSound" && isStopSoundEvent(payload.data)) { - this._stopSoundStream.next(payload.data); - } else if (payload.type === "loadSound" && isLoadSoundEvent(payload.data)) { - this._loadSoundStream.next(payload.data); - } else if (payload.type === "openCoWebSite" && isOpenCoWebsite(payload.data)) { - scriptUtils.openCoWebsite( - payload.data.url, - foundSrc, - payload.data.allowApi, - payload.data.allowPolicy - ); - } else if (payload.type === "closeCoWebSite") { - scriptUtils.closeCoWebSite(); - } else if (payload.type === "disablePlayerControls") { - this._disablePlayerControlStream.next(); - } else if (payload.type === "restorePlayerControls") { - this._enablePlayerControlStream.next(); - } else if (payload.type === "displayBubble") { - this._displayBubbleStream.next(); - } else if (payload.type === "removeBubble") { - this._removeBubbleStream.next(); - } else if (payload.type == "getState") { - this._gameStateStream.next(); - } else if (payload.type == "onPlayerMove") { - this.sendPlayerMove = true; - } else if (payload.type == "getDataLayer") { - this._dataLayerChangeStream.next(); - } else if (isMenuItemRegisterIframeEvent(payload)) { - const data = payload.data.menutItem; - // @ts-ignore - this.iframeCloseCallbacks.get(iframe).push(() => { - this._unregisterMenuCommandStream.next(data); - }); - handleMenuItemRegistrationEvent(payload.data); - } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { - this._setTilesStream.next(payload.data); + if (isIframeQueryWrapper(payload)) { + const queryId = payload.id; + const query = payload.query; + + const answerer = this.answerers[query.type]; + if (answerer === undefined) { + const errorMsg = 'The iFrame sent a message of type "'+query.type+'" but there is no service configured to answer these messages.'; + console.error(errorMsg); + iframe.contentWindow?.postMessage({ + id: queryId, + type: query.type, + error: errorMsg + } as IframeErrorAnswerEvent, '*'); + return; + } + + answerer(query.data).then((value) => { + iframe?.contentWindow?.postMessage({ + id: queryId, + type: query.type, + data: value + }, '*'); + }).catch(reason => { + console.error('An error occurred while responding to an iFrame query.', reason); + let reasonMsg: string; + if (reason instanceof Error) { + reasonMsg = reason.message; + } else { + reasonMsg = reason.toString(); + } + + iframe?.contentWindow?.postMessage({ + id: queryId, + type: query.type, + error: reasonMsg + } as IframeErrorAnswerEvent, '*'); + }); + + } else if (isIframeEventWrapper(payload)) { + if (payload.type === "showLayer" && isLayerEvent(payload.data)) { + this._showLayerStream.next(payload.data); + } else if (payload.type === "hideLayer" && isLayerEvent(payload.data)) { + this._hideLayerStream.next(payload.data); + } else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) { + this._setPropertyStream.next(payload.data); + } else if (payload.type === "chat" && isChatEvent(payload.data)) { + this._chatStream.next(payload.data); + } else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) { + this._openPopupStream.next(payload.data); + } else if (payload.type === "closePopup" && isClosePopupEvent(payload.data)) { + this._closePopupStream.next(payload.data); + } else if (payload.type === "openTab" && isOpenTabEvent(payload.data)) { + scriptUtils.openTab(payload.data.url); + } else if (payload.type === "goToPage" && isGoToPageEvent(payload.data)) { + scriptUtils.goToPage(payload.data.url); + } else if (payload.type === "loadPage" && isLoadPageEvent(payload.data)) { + this._loadPageStream.next(payload.data.url); + } else if (payload.type === "playSound" && isPlaySoundEvent(payload.data)) { + this._playSoundStream.next(payload.data); + } else if (payload.type === "stopSound" && isStopSoundEvent(payload.data)) { + this._stopSoundStream.next(payload.data); + } else if (payload.type === "loadSound" && isLoadSoundEvent(payload.data)) { + this._loadSoundStream.next(payload.data); + } else if (payload.type === "openCoWebSite" && isOpenCoWebsite(payload.data)) { + scriptUtils.openCoWebsite( + payload.data.url, + foundSrc, + payload.data.allowApi, + payload.data.allowPolicy + ); + } else if (payload.type === "closeCoWebSite") { + scriptUtils.closeCoWebSite(); + } else if (payload.type === "disablePlayerControls") { + this._disablePlayerControlStream.next(); + } else if (payload.type === "restorePlayerControls") { + this._enablePlayerControlStream.next(); + } else if (payload.type === "displayBubble") { + this._displayBubbleStream.next(); + } else if (payload.type === "removeBubble") { + this._removeBubbleStream.next(); + } else if (payload.type == "onPlayerMove") { + this.sendPlayerMove = true; + } else if (payload.type == "getDataLayer") { + this._dataLayerChangeStream.next(); + } else if (isMenuItemRegisterIframeEvent(payload)) { + const data = payload.data.menutItem; + // @ts-ignore + this.iframeCloseCallbacks.get(iframe).push(() => { + this._unregisterMenuCommandStream.next(data); + }); + handleMenuItemRegistrationEvent(payload.data); + } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { + this._setTilesStream.next(payload.data); + } } - } }, false ); @@ -214,13 +255,6 @@ class IframeListener { }); } - sendGameStateEvent(gameStateEvent: GameStateEvent) { - this.postMessage({ - type: "gameState", - data: gameStateEvent, - }); - } - /** * Allows the passed iFrame to send/receive messages via the API. */ @@ -368,6 +402,22 @@ class IframeListener { iframe.contentWindow?.postMessage(message, "*"); } } + + /** + * Registers a callback that can be used to respond to some query (as defined in the IframeQueryMap type). + * + * Important! There can be only one "answerer" so registering a new one will unregister the old one. + * + * @param key The "type" of the query we are answering + * @param callback + */ + public registerAnswerer(key: T, callback: (query: IframeQueryMap[T]['query']) => Promise ): void { + this.answerers[key] = callback; + } + + public unregisterAnswerer(key: keyof IframeQueryMap): void { + delete this.answerers[key]; + } } export const iframeListener = new IframeListener(); diff --git a/front/src/Api/iframe/IframeApiContribution.ts b/front/src/Api/iframe/IframeApiContribution.ts index f3b25999..e4ba089e 100644 --- a/front/src/Api/iframe/IframeApiContribution.ts +++ b/front/src/Api/iframe/IframeApiContribution.ts @@ -1,9 +1,40 @@ import type * as tg from "generic-type-guard"; -import type { IframeEvent, IframeEventMap, IframeResponseEventMap } from '../Events/IframeEvent'; +import type { + IframeEvent, + IframeEventMap, IframeQuery, + IframeQueryMap, + IframeResponseEventMap +} from '../Events/IframeEvent'; +import type {IframeQueryWrapper} from "../Events/IframeEvent"; export function sendToWorkadventure(content: IframeEvent) { window.parent.postMessage(content, "*") } + +let queryNumber = 0; + +export const answerPromises = new Map)) => void, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reject: (reason?: any) => void +}>(); + +export function queryWorkadventure(content: IframeQuery): Promise { + return new Promise((resolve, reject) => { + window.parent.postMessage({ + id: queryNumber, + query: content + } as IframeQueryWrapper, "*"); + + answerPromises.set(queryNumber, { + resolve, + reject + }); + + queryNumber++; + }); +} + type GuardedType> = Guard extends tg.TypeGuard ? T : never export interface IframeCallback> { diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts index 78fad58b..c70d0aad 100644 --- a/front/src/Api/iframe/room.ts +++ b/front/src/Api/iframe/room.ts @@ -4,7 +4,7 @@ import { isDataLayerEvent } from "../Events/DataLayerEvent"; import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent"; import { isGameStateEvent } from "../Events/GameStateEvent"; -import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; +import {IframeApiContribution, queryWorkadventure, sendToWorkadventure} from "./IframeApiContribution"; import { apiCallback } from "./registeredCallbacks"; import type { ITiledMap } from "../../Phaser/Map/ITiledMap"; @@ -16,7 +16,7 @@ const leaveStreams: Map> = new Map(); const stateResolvers = new Subject(); -let immutableData: GameStateEvent; +let immutableDataPromise: Promise | undefined = undefined; interface Room { id: string; @@ -39,14 +39,10 @@ interface TileDescriptor { } function getGameState(): Promise { - if (immutableData) { - return Promise.resolve(immutableData); - } else { - return new Promise((resolver, thrower) => { - stateResolvers.subscribe(resolver); - sendToWorkadventure({ type: "getState", data: null }); - }); + if (immutableDataPromise === undefined) { + immutableDataPromise = queryWorkadventure({ type: "getState", data: undefined }); } + return immutableDataPromise; } function getDataLayer(): Promise { @@ -72,13 +68,6 @@ export class WorkadventureRoomCommands extends IframeApiContribution { - stateResolvers.next(payloadData); - }, - }), apiCallback({ type: "dataLayer", typeChecker: isDataLayerEvent, diff --git a/front/src/Phaser/Components/TextUtils.ts b/front/src/Phaser/Components/TextUtils.ts index db9a97fb..972c50c7 100644 --- a/front/src/Phaser/Components/TextUtils.ts +++ b/front/src/Phaser/Components/TextUtils.ts @@ -44,7 +44,6 @@ export class TextUtils { options.align = object.text.halign; } - console.warn(options); const textElem = scene.add.text(object.x, object.y, object.text.text, options); textElem.setAngle(object.rotation); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d137c9e0..6cb5284d 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1044,18 +1044,16 @@ ${escapedMessage} }) ); - this.iframeSubscriptionList.push( - iframeListener.gameStateStream.subscribe(() => { - iframeListener.sendGameStateEvent({ - mapUrl: this.MapUrlFile, - startLayerName: this.startPositionCalculator.startLayerName, - uuid: localUserStore.getLocalUser()?.uuid, - nickname: localUserStore.getName(), - roomId: this.RoomId, - tags: this.connection ? this.connection.getAllTags() : [], - }); - }) - ); + iframeListener.registerAnswerer('getState', () => { + return Promise.resolve({ + mapUrl: this.MapUrlFile, + startLayerName: this.startPositionCalculator.startLayerName, + uuid: localUserStore.getLocalUser()?.uuid, + nickname: localUserStore.getName(), + roomId: this.RoomId, + tags: this.connection ? this.connection.getAllTags() : [], + }); + }); this.iframeSubscriptionList.push( iframeListener.setTilesStream.subscribe((eventTiles) => { for (const eventTile of eventTiles) { @@ -1149,6 +1147,7 @@ ${escapedMessage} this.emoteManager.destroy(); this.peerStoreUnsubscribe(); this.biggestAvailableAreaStoreUnsubscribe(); + iframeListener.unregisterAnswerer('getState'); mediaManager.hideGameOverlay(); diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index a1949106..1915020e 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -1,7 +1,7 @@ import { registeredCallbacks } from "./Api/iframe/registeredCallbacks"; import { IframeResponseEvent, - IframeResponseEventMap, + IframeResponseEventMap, isIframeAnswerEvent, isIframeErrorAnswerEvent, isIframeResponseEventWrapper, TypedMessageEvent, } from "./Api/Events/IframeEvent"; @@ -16,7 +16,7 @@ import player from "./Api/iframe/player"; import type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor"; import type { Popup } from "./Api/iframe/Ui/Popup"; import type { Sound } from "./Api/iframe/Sound/Sound"; -import { sendToWorkadventure } from "./Api/iframe/IframeApiContribution"; +import { answerPromises, sendToWorkadventure} from "./Api/iframe/IframeApiContribution"; const wa = { ui, @@ -164,16 +164,38 @@ declare global { window.WA = wa; window.addEventListener( - "message", - (message: TypedMessageEvent>) => { - if (message.source !== window.parent) { - return; // Skip message in this event listener - } - const payload = message.data; - console.debug(payload); + "message", (message: TypedMessageEvent>) => { + if (message.source !== window.parent) { + return; // Skip message in this event listener + } + const payload = message.data; - if (isIframeResponseEventWrapper(payload)) { - const payloadData = payload.data; + console.debug(payload); + + if (isIframeAnswerEvent(payload)) { + const queryId = payload.id; + const payloadData = payload.data; + + const resolver = answerPromises.get(queryId); + if (resolver === undefined) { + throw new Error('In Iframe API, got an answer for a question that we have no track of.'); + } + resolver.resolve(payloadData); + + answerPromises.delete(queryId); + } else if (isIframeErrorAnswerEvent(payload)) { + const queryId = payload.id; + const payloadError = payload.error; + + const resolver = answerPromises.get(queryId); + if (resolver === undefined) { + throw new Error('In Iframe API, got an error answer for a question that we have no track of.'); + } + resolver.reject(payloadError); + + answerPromises.delete(queryId); + } else if (isIframeResponseEventWrapper(payload)) { + const payloadData = payload.data; const callback = registeredCallbacks[payload.type] as IframeCallback | undefined; if (callback?.typeChecker(payloadData)) { From 280c59e6b5734b9821a62cb438f3e5e5d9660f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 2 Jul 2021 17:26:28 +0200 Subject: [PATCH 46/79] Changing callback signature of registerAnswerer so that it can return a value and not necessarily a promise. --- docs/maps/api-room.md | 2 +- front/src/Api/IframeListener.ts | 6 +++--- front/src/Phaser/Game/GameScene.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index 17e3d48e..9d08ce1b 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -81,7 +81,7 @@ WA.room.getCurrentRoom(): Promise ``` Return a promise that resolves to a `Room` object with the following attributes : * **id (string) :** ID of the current room -* **map (ITiledMap) :** contains the JSON map file with the properties that were setted by the script if `setProperty` was called. +* **map (ITiledMap) :** contains the JSON map file with the properties that were set by the script if `setProperty` was called. * **mapUrl (string) :** Url of the JSON map file * **startLayer (string | null) :** Name of the layer where the current user started, only if different from `start` layer diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 334535a5..314d5d2e 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -33,7 +33,7 @@ import { isLoadPageEvent } from "./Events/LoadPageEvent"; import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent"; import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent"; -type AnswererCallback = (query: IframeQueryMap[T]['query']) => Promise; +type AnswererCallback = (query: IframeQueryMap[T]['query']) => IframeQueryMap[T]['answer']|Promise; /** * Listens to messages from iframes and turn those messages into easy to use observables. @@ -164,7 +164,7 @@ class IframeListener { return; } - answerer(query.data).then((value) => { + Promise.resolve(answerer(query.data)).then((value) => { iframe?.contentWindow?.postMessage({ id: queryId, type: query.type, @@ -411,7 +411,7 @@ class IframeListener { * @param key The "type" of the query we are answering * @param callback */ - public registerAnswerer(key: T, callback: (query: IframeQueryMap[T]['query']) => Promise ): void { + public registerAnswerer(key: T, callback: (query: IframeQueryMap[T]['query']) => IframeQueryMap[T]['answer']|Promise ): void { this.answerers[key] = callback; } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 6cb5284d..d767f0f4 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1045,14 +1045,14 @@ ${escapedMessage} ); iframeListener.registerAnswerer('getState', () => { - return Promise.resolve({ + return { mapUrl: this.MapUrlFile, startLayerName: this.startPositionCalculator.startLayerName, uuid: localUserStore.getLocalUser()?.uuid, nickname: localUserStore.getName(), roomId: this.RoomId, tags: this.connection ? this.connection.getAllTags() : [], - }); + }; }); this.iframeSubscriptionList.push( iframeListener.setTilesStream.subscribe((eventTiles) => { From 46e6917df6a26969d3a7e7a4ec7862bee61aa39d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 6 Jul 2021 17:13:08 +0200 Subject: [PATCH 47/79] Adding a playersStore The playerStore can be useful to get the details of a given player from its ID. --- back/src/Services/SocketManager.ts | 2 - front/src/Connexion/RoomConnection.ts | 1 - front/src/Phaser/Game/AddPlayerInterface.ts | 9 +---- front/src/Phaser/Game/GameScene.ts | 3 ++ front/src/Phaser/Game/PlayerInterface.ts | 9 +++++ front/src/Stores/PlayersStore.ts | 43 +++++++++++++++++++++ front/src/WebRtc/SimplePeer.ts | 16 ++------ messages/protos/messages.proto | 3 +- 8 files changed, 62 insertions(+), 24 deletions(-) create mode 100644 front/src/Phaser/Game/PlayerInterface.ts create mode 100644 front/src/Stores/PlayersStore.ts diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index e61763cd..fd812b44 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -425,7 +425,6 @@ export class SocketManager { // Let's send 2 messages: one to the user joining the group and one to the other user const webrtcStartMessage1 = new WebRtcStartMessage(); webrtcStartMessage1.setUserid(otherUser.id); - webrtcStartMessage1.setName(otherUser.name); webrtcStartMessage1.setInitiator(true); if (TURN_STATIC_AUTH_SECRET !== "") { const { username, password } = this.getTURNCredentials("" + otherUser.id, TURN_STATIC_AUTH_SECRET); @@ -443,7 +442,6 @@ export class SocketManager { const webrtcStartMessage2 = new WebRtcStartMessage(); webrtcStartMessage2.setUserid(user.id); - webrtcStartMessage2.setName(user.name); webrtcStartMessage2.setInitiator(false); if (TURN_STATIC_AUTH_SECRET !== "") { const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET); diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 1b080a55..4f2e9ef4 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -466,7 +466,6 @@ export class RoomConnection implements RoomConnection { this.onMessage(EventMessage.WEBRTC_START, (message: WebRtcStartMessage) => { callback({ userId: message.getUserid(), - name: message.getName(), initiator: message.getInitiator(), webRtcUser: message.getWebrtcusername() ?? undefined, webRtcPassword: message.getWebrtcpassword() ?? undefined, diff --git a/front/src/Phaser/Game/AddPlayerInterface.ts b/front/src/Phaser/Game/AddPlayerInterface.ts index 1a5176f0..d2f12013 100644 --- a/front/src/Phaser/Game/AddPlayerInterface.ts +++ b/front/src/Phaser/Game/AddPlayerInterface.ts @@ -1,11 +1,6 @@ import type {PointInterface} from "../../Connexion/ConnexionModels"; -import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; +import type {PlayerInterface} from "./PlayerInterface"; -export interface AddPlayerInterface { - userId: number; - name: string; - characterLayers: BodyResourceDescriptionInterface[]; +export interface AddPlayerInterface extends PlayerInterface { position: PointInterface; - visitCardUrl: string|null; - companion: string|null; } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d767f0f4..ce9ce5e4 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -91,6 +91,7 @@ import { soundManager } from "./SoundManager"; import { peerStore, screenSharingPeerStore } from "../../Stores/PeerStore"; import { videoFocusStore } from "../../Stores/VideoFocusStore"; import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStore"; +import {playersStore} from "../../Stores/PlayersStore"; export interface GameSceneInitInterface { initPosition: PointInterface | null; @@ -597,6 +598,8 @@ export class GameScene extends DirtyScene { .then((onConnect: OnConnectInterface) => { this.connection = onConnect.connection; + playersStore.connectToRoomConnection(this.connection); + this.connection.onUserJoins((message: MessageUserJoined) => { const userMessage: AddPlayerInterface = { userId: message.userId, diff --git a/front/src/Phaser/Game/PlayerInterface.ts b/front/src/Phaser/Game/PlayerInterface.ts new file mode 100644 index 00000000..ab881267 --- /dev/null +++ b/front/src/Phaser/Game/PlayerInterface.ts @@ -0,0 +1,9 @@ +import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; + +export interface PlayerInterface { + userId: number; + name: string; + characterLayers: BodyResourceDescriptionInterface[]; + visitCardUrl: string|null; + companion: string|null; +} diff --git a/front/src/Stores/PlayersStore.ts b/front/src/Stores/PlayersStore.ts new file mode 100644 index 00000000..ef19efce --- /dev/null +++ b/front/src/Stores/PlayersStore.ts @@ -0,0 +1,43 @@ +import { writable } from "svelte/store"; +import type {PlayerInterface} from "../Phaser/Game/PlayerInterface"; +import type {RoomConnection} from "../Connexion/RoomConnection"; + +/** + * A store that contains the list of players currently known. + */ +function createPlayersStore() { + let players = new Map(); + + const { subscribe, set, update } = writable(players); + + return { + subscribe, + connectToRoomConnection: (roomConnection: RoomConnection) => { + players = new Map(); + set(players); + roomConnection.onUserJoins((message) => { + update((users) => { + users.set(message.userId, { + userId: message.userId, + name: message.name, + characterLayers: message.characterLayers, + visitCardUrl: message.visitCardUrl, + companion: message.companion, + }); + return users; + }); + }); + roomConnection.onUserLeft((userId) => { + update((users) => { + users.delete(userId); + return users; + }); + }); + }, + getPlayerById(userId: number): PlayerInterface|undefined { + return players.get(userId); + } + }; +} + +export const playersStore = createPlayersStore(); diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index affcacd7..0d3c4745 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -11,10 +11,10 @@ import { get } from "svelte/store"; import { localStreamStore, LocalStreamStoreValue, obtainedMediaConstraintStore } from "../Stores/MediaStore"; import { screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore"; import { discussionManager } from "./DiscussionManager"; +import {playersStore} from "../Stores/PlayersStore"; export interface UserSimplePeerInterface { userId: number; - name?: string; initiator?: boolean; webRtcUser?: string | undefined; webRtcPassword?: string | undefined; @@ -153,10 +153,7 @@ export class SimplePeer { } } - let name = user.name; - if (!name) { - name = this.getName(user.userId); - } + const name = this.getName(user.userId); discussionManager.removeParticipant(user.userId); @@ -191,7 +188,7 @@ export class SimplePeer { //Create a notification for first user in circle discussion if (this.PeerConnectionArray.size === 0) { - mediaManager.createNotification(user.name ?? ""); + mediaManager.createNotification(name); } this.PeerConnectionArray.set(user.userId, peer); @@ -202,12 +199,7 @@ export class SimplePeer { } private getName(userId: number): string { - const userSearch = this.Users.find((userSearch: UserSimplePeerInterface) => userSearch.userId === userId); - if (userSearch) { - return userSearch.name || ""; - } else { - return ""; - } + return playersStore.getPlayerById(userId)?.name || ''; } /** diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 52d58d6d..27d7cb10 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -183,7 +183,6 @@ message RoomJoinedMessage { message WebRtcStartMessage { int32 userId = 1; - string name = 2; bool initiator = 3; string webrtcUserName = 4; string webrtcPassword = 5; @@ -257,7 +256,7 @@ message ServerToClientMessage { AdminRoomMessage adminRoomMessage = 14; WorldFullWarningMessage worldFullWarningMessage = 15; WorldFullMessage worldFullMessage = 16; - RefreshRoomMessage refreshRoomMessage = 17; + RefreshRoomMessage refreshRoomMessage = 17; WorldConnexionMessage worldConnexionMessage = 18; EmoteEventMessage emoteEventMessage = 19; } From 34cb0ebf39e2d21acc5ed9ddb6113259db85e7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 7 Jul 2021 11:24:51 +0200 Subject: [PATCH 48/79] Users blocking now rely on UUID rather than ID This way, if a user A blocks another user B, if user B refreshes the browser or leaves and re-enters the room, user B will still be blocked. As a side effect, this allows us to completely remove the "sockets" property in the SocketManager on the Pusher. --- CHANGELOG.md | 1 + back/src/Services/SocketManager.ts | 2 + front/src/Connexion/ConnexionModels.ts | 68 +++++++++++---------- front/src/Connexion/RoomConnection.ts | 5 +- front/src/Phaser/Game/GameScene.ts | 7 ++- front/src/Phaser/Game/PlayerInterface.ts | 7 ++- front/src/Phaser/Menu/MenuScene.ts | 7 ++- front/src/Phaser/Menu/ReportMenu.ts | 78 ++++++++++++------------ front/src/Stores/PlayersStore.ts | 9 +-- front/src/WebRtc/BlackListManager.ts | 37 +++++------ front/src/WebRtc/SimplePeer.ts | 10 +-- front/src/WebRtc/VideoPeer.ts | 15 +++-- messages/protos/messages.proto | 4 +- pusher/src/Model/Zone.ts | 3 + pusher/src/Services/SocketManager.ts | 31 +--------- 15 files changed, 143 insertions(+), 141 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa3dd293..a83e8213 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Use `WA.room.getCurrentRoom(): Promise` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started - Use `WA.ui.registerMenuCommand(): void` to add a custom menu - Use `WA.room.setTiles(): void` to change an array of tiles +- Users blocking now relies on UUID rather than ID. A blocked user that leaves a room and comes back will stay blocked. ## Version 1.4.3 - 1.4.4 - 1.4.5 diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index fd812b44..8d04e713 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -308,6 +308,7 @@ export class SocketManager { throw new Error("clientUser.userId is not an integer " + thing.id); } userJoinedZoneMessage.setUserid(thing.id); + userJoinedZoneMessage.setUseruuid(thing.uuid); userJoinedZoneMessage.setName(thing.name); userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers)); userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition())); @@ -612,6 +613,7 @@ export class SocketManager { if (thing instanceof User) { const userJoinedMessage = new UserJoinedZoneMessage(); userJoinedMessage.setUserid(thing.id); + userJoinedMessage.setUseruuid(thing.uuid); userJoinedMessage.setName(thing.name); userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers)); userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition())); diff --git a/front/src/Connexion/ConnexionModels.ts b/front/src/Connexion/ConnexionModels.ts index b5a66296..189aea7c 100644 --- a/front/src/Connexion/ConnexionModels.ts +++ b/front/src/Connexion/ConnexionModels.ts @@ -1,8 +1,8 @@ -import type {SignalData} from "simple-peer"; -import type {RoomConnection} from "./RoomConnection"; -import type {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures"; +import type { SignalData } from "simple-peer"; +import type { RoomConnection } from "./RoomConnection"; +import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures"; -export enum EventMessage{ +export enum EventMessage { CONNECT = "connect", WEBRTC_SIGNAL = "webrtc-signal", WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal", @@ -17,7 +17,7 @@ export enum EventMessage{ GROUP_CREATE_UPDATE = "group-create-update", GROUP_DELETE = "group-delete", SET_PLAYER_DETAILS = "set-player-details", // Send the name and character to the server (on connect), receive back the id. - ITEM_EVENT = 'item-event', + ITEM_EVENT = "item-event", CONNECT_ERROR = "connect_error", CONNECTING_ERROR = "connecting_error", @@ -36,7 +36,7 @@ export enum EventMessage{ export interface PointInterface { x: number; y: number; - direction : string; + direction: string; moving: boolean; } @@ -45,8 +45,9 @@ export interface MessageUserPositionInterface { name: string; characterLayers: BodyResourceDescriptionInterface[]; position: PointInterface; - visitCardUrl: string|null; - companion: string|null; + visitCardUrl: string | null; + companion: string | null; + userUuid: string; } export interface MessageUserMovedInterface { @@ -60,58 +61,59 @@ export interface MessageUserJoined { characterLayers: BodyResourceDescriptionInterface[]; position: PointInterface; visitCardUrl: string | null; - companion: string|null; + companion: string | null; + userUuid: string; } export interface PositionInterface { - x: number, - y: number + x: number; + y: number; } export interface GroupCreatedUpdatedMessageInterface { - position: PositionInterface, - groupId: number, - groupSize: number + position: PositionInterface; + groupId: number; + groupSize: number; } export interface WebRtcDisconnectMessageInterface { - userId: number + userId: number; } export interface WebRtcSignalReceivedMessageInterface { - userId: number, - signal: SignalData, - webRtcUser: string | undefined, - webRtcPassword: string | undefined + userId: number; + signal: SignalData; + webRtcUser: string | undefined; + webRtcPassword: string | undefined; } export interface ViewportInterface { - left: number, - top: number, - right: number, - bottom: number, + left: number; + top: number; + right: number; + bottom: number; } export interface ItemEventMessageInterface { - itemId: number, - event: string, - state: unknown, - parameters: unknown + itemId: number; + event: string; + state: unknown; + parameters: unknown; } export interface RoomJoinedMessageInterface { //users: MessageUserPositionInterface[], //groups: GroupCreatedUpdatedMessageInterface[], - items: { [itemId: number] : unknown } + items: { [itemId: number]: unknown }; } export interface PlayGlobalMessageInterface { - id: string - type: string - message: string + id: string; + type: string; + message: string; } export interface OnConnectInterface { - connection: RoomConnection, - room: RoomJoinedMessageInterface + connection: RoomConnection; + room: RoomJoinedMessageInterface; } diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 4f2e9ef4..189eabba 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -365,6 +365,7 @@ export class RoomConnection implements RoomConnection { visitCardUrl: message.getVisitcardurl(), position: ProtobufClientUtils.toPointInterface(position), companion: companion ? companion.getName() : null, + userUuid: message.getUseruuid(), }; } @@ -591,9 +592,9 @@ export class RoomConnection implements RoomConnection { this.socket.send(clientToServerMessage.serializeBinary().buffer); } - public emitReportPlayerMessage(reportedUserId: number, reportComment: string): void { + public emitReportPlayerMessage(reportedUserUuid: string, reportComment: string): void { const reportPlayerMessage = new ReportPlayerMessage(); - reportPlayerMessage.setReporteduserid(reportedUserId); + reportPlayerMessage.setReporteduseruuid(reportedUserUuid); reportPlayerMessage.setReportcomment(reportComment); const clientToServerMessage = new ClientToServerMessage(); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index ce9ce5e4..d6df242f 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -91,7 +91,7 @@ import { soundManager } from "./SoundManager"; import { peerStore, screenSharingPeerStore } from "../../Stores/PeerStore"; import { videoFocusStore } from "../../Stores/VideoFocusStore"; import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStore"; -import {playersStore} from "../../Stores/PlayersStore"; +import { playersStore } from "../../Stores/PlayersStore"; export interface GameSceneInitInterface { initPosition: PointInterface | null; @@ -608,6 +608,7 @@ export class GameScene extends DirtyScene { position: message.position, visitCardUrl: message.visitCardUrl, companion: message.companion, + userUuid: message.userUuid, }; this.addPlayer(userMessage); }); @@ -1047,7 +1048,7 @@ ${escapedMessage} }) ); - iframeListener.registerAnswerer('getState', () => { + iframeListener.registerAnswerer("getState", () => { return { mapUrl: this.MapUrlFile, startLayerName: this.startPositionCalculator.startLayerName, @@ -1150,7 +1151,7 @@ ${escapedMessage} this.emoteManager.destroy(); this.peerStoreUnsubscribe(); this.biggestAvailableAreaStoreUnsubscribe(); - iframeListener.unregisterAnswerer('getState'); + iframeListener.unregisterAnswerer("getState"); mediaManager.hideGameOverlay(); diff --git a/front/src/Phaser/Game/PlayerInterface.ts b/front/src/Phaser/Game/PlayerInterface.ts index ab881267..5a81c89a 100644 --- a/front/src/Phaser/Game/PlayerInterface.ts +++ b/front/src/Phaser/Game/PlayerInterface.ts @@ -1,9 +1,10 @@ -import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; +import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures"; export interface PlayerInterface { userId: number; name: string; characterLayers: BodyResourceDescriptionInterface[]; - visitCardUrl: string|null; - companion: string|null; + visitCardUrl: string | null; + companion: string | null; + userUuid: string; } diff --git a/front/src/Phaser/Menu/MenuScene.ts b/front/src/Phaser/Menu/MenuScene.ts index d0d6f982..da59cecb 100644 --- a/front/src/Phaser/Menu/MenuScene.ts +++ b/front/src/Phaser/Menu/MenuScene.ts @@ -18,6 +18,7 @@ import { registerMenuCommandStream } from "../../Api/Events/ui/MenuItemRegisterE import { sendMenuClickedEvent } from "../../Api/iframe/Ui/MenuItem"; import { consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore"; import { get } from "svelte/store"; +import { playersStore } from "../../Stores/PlayersStore"; export const MenuSceneName = "MenuScene"; const gameMenuKey = "gameMenu"; @@ -120,7 +121,11 @@ export class MenuScene extends Phaser.Scene { showReportScreenStore.subscribe((user) => { if (user !== null) { this.closeAll(); - this.gameReportElement.open(user.userId, user.userName); + const uuid = playersStore.getPlayerById(user.userId)?.userUuid; + if (uuid === undefined) { + throw new Error("Could not find UUID for user with ID " + user.userId); + } + this.gameReportElement.open(uuid, user.userName); } }); diff --git a/front/src/Phaser/Menu/ReportMenu.ts b/front/src/Phaser/Menu/ReportMenu.ts index e8b20531..effb92b2 100644 --- a/front/src/Phaser/Menu/ReportMenu.ts +++ b/front/src/Phaser/Menu/ReportMenu.ts @@ -1,15 +1,16 @@ -import {MenuScene} from "./MenuScene"; -import {gameManager} from "../Game/GameManager"; -import {blackListManager} from "../../WebRtc/BlackListManager"; +import { MenuScene } from "./MenuScene"; +import { gameManager } from "../Game/GameManager"; +import { blackListManager } from "../../WebRtc/BlackListManager"; +import { playersStore } from "../../Stores/PlayersStore"; -export const gameReportKey = 'gameReport'; -export const gameReportRessource = 'resources/html/gameReport.html'; +export const gameReportKey = "gameReport"; +export const gameReportRessource = "resources/html/gameReport.html"; export class ReportMenu extends Phaser.GameObjects.DOMElement { private opened: boolean = false; - private userId!: number; - private userName!: string|undefined; + private userUuid!: string; + private userName!: string | undefined; private anonymous: boolean; constructor(scene: Phaser.Scene, anonymous: boolean) { @@ -18,46 +19,46 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement { this.createFromCache(gameReportKey); if (this.anonymous) { - const divToHide = this.getChildByID('reportSection') as HTMLElement; + const divToHide = this.getChildByID("reportSection") as HTMLElement; divToHide.hidden = true; - const textToHide = this.getChildByID('askActionP') as HTMLElement; + const textToHide = this.getChildByID("askActionP") as HTMLElement; textToHide.hidden = true; } scene.add.existing(this); MenuScene.revealMenusAfterInit(this, gameReportKey); - this.addListener('click'); - this.on('click', (event:MouseEvent) => { + this.addListener("click"); + this.on("click", (event: MouseEvent) => { event.preventDefault(); - if ((event?.target as HTMLInputElement).id === 'gameReportFormSubmit') { + if ((event?.target as HTMLInputElement).id === "gameReportFormSubmit") { this.submitReport(); - } else if((event?.target as HTMLInputElement).id === 'gameReportFormCancel') { + } else if ((event?.target as HTMLInputElement).id === "gameReportFormCancel") { this.close(); - } else if((event?.target as HTMLInputElement).id === 'toggleBlockButton') { + } else if ((event?.target as HTMLInputElement).id === "toggleBlockButton") { this.toggleBlock(); } }); } - public open(userId: number, userName: string|undefined): void { + public open(userUuid: string, userName: string | undefined): void { if (this.opened) { this.close(); return; } - this.userId = userId; + this.userUuid = userUuid; this.userName = userName; - const mainEl = this.getChildByID('gameReport') as HTMLElement; + const mainEl = this.getChildByID("gameReport") as HTMLElement; this.x = this.getCenteredX(mainEl); this.y = this.getHiddenY(mainEl); - const gameTitleReport = this.getChildByID('nameReported') as HTMLElement; - gameTitleReport.innerText = userName || ''; + const gameTitleReport = this.getChildByID("nameReported") as HTMLElement; + gameTitleReport.innerText = userName || ""; - const blockButton = this.getChildByID('toggleBlockButton') as HTMLElement; - blockButton.innerText = blackListManager.isBlackListed(this.userId) ? 'Unblock this user' : 'Block this user'; + const blockButton = this.getChildByID("toggleBlockButton") as HTMLElement; + blockButton.innerText = blackListManager.isBlackListed(this.userUuid) ? "Unblock this user" : "Block this user"; this.opened = true; @@ -67,19 +68,19 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement { targets: this, y: this.getCenteredY(mainEl), duration: 1000, - ease: 'Power3' + ease: "Power3", }); } public close(): void { gameManager.getCurrentGameScene(this.scene).userInputManager.restoreControls(); this.opened = false; - const mainEl = this.getChildByID('gameReport') as HTMLElement; + const mainEl = this.getChildByID("gameReport") as HTMLElement; this.scene.tweens.add({ targets: this, y: this.getHiddenY(mainEl), duration: 1000, - ease: 'Power3' + ease: "Power3", }); } @@ -88,31 +89,32 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement { return window.innerWidth / 4 - mainEl.clientWidth / 2; } private getHiddenY(mainEl: HTMLElement): number { - return - mainEl.clientHeight - 50; + return -mainEl.clientHeight - 50; } private getCenteredY(mainEl: HTMLElement): number { return window.innerHeight / 4 - mainEl.clientHeight / 2; } private toggleBlock(): void { - !blackListManager.isBlackListed(this.userId) ? blackListManager.blackList(this.userId) : blackListManager.cancelBlackList(this.userId); + !blackListManager.isBlackListed(this.userUuid) + ? blackListManager.blackList(this.userUuid) + : blackListManager.cancelBlackList(this.userUuid); this.close(); } - private submitReport(): void{ - const gamePError = this.getChildByID('gameReportErr') as HTMLParagraphElement; - gamePError.innerText = ''; - gamePError.style.display = 'none'; - const gameTextArea = this.getChildByID('gameReportInput') as HTMLInputElement; - if(!gameTextArea || !gameTextArea.value){ - gamePError.innerText = 'Report message cannot to be empty.'; - gamePError.style.display = 'block'; + private submitReport(): void { + const gamePError = this.getChildByID("gameReportErr") as HTMLParagraphElement; + gamePError.innerText = ""; + gamePError.style.display = "none"; + const gameTextArea = this.getChildByID("gameReportInput") as HTMLInputElement; + if (!gameTextArea || !gameTextArea.value) { + gamePError.innerText = "Report message cannot to be empty."; + gamePError.style.display = "block"; return; } - gameManager.getCurrentGameScene(this.scene).connection?.emitReportPlayerMessage( - this.userId, - gameTextArea.value - ); + gameManager + .getCurrentGameScene(this.scene) + .connection?.emitReportPlayerMessage(this.userUuid, gameTextArea.value); this.close(); } } diff --git a/front/src/Stores/PlayersStore.ts b/front/src/Stores/PlayersStore.ts index ef19efce..6c21de7a 100644 --- a/front/src/Stores/PlayersStore.ts +++ b/front/src/Stores/PlayersStore.ts @@ -1,6 +1,6 @@ import { writable } from "svelte/store"; -import type {PlayerInterface} from "../Phaser/Game/PlayerInterface"; -import type {RoomConnection} from "../Connexion/RoomConnection"; +import type { PlayerInterface } from "../Phaser/Game/PlayerInterface"; +import type { RoomConnection } from "../Connexion/RoomConnection"; /** * A store that contains the list of players currently known. @@ -23,6 +23,7 @@ function createPlayersStore() { characterLayers: message.characterLayers, visitCardUrl: message.visitCardUrl, companion: message.companion, + userUuid: message.userUuid, }); return users; }); @@ -34,9 +35,9 @@ function createPlayersStore() { }); }); }, - getPlayerById(userId: number): PlayerInterface|undefined { + getPlayerById(userId: number): PlayerInterface | undefined { return players.get(userId); - } + }, }; } diff --git a/front/src/WebRtc/BlackListManager.ts b/front/src/WebRtc/BlackListManager.ts index 65efef3a..d2e7c390 100644 --- a/front/src/WebRtc/BlackListManager.ts +++ b/front/src/WebRtc/BlackListManager.ts @@ -1,24 +1,27 @@ -import {Subject} from 'rxjs'; +import { Subject } from "rxjs"; class BlackListManager { - private list: number[] = []; - public onBlockStream: Subject = new Subject(); - public onUnBlockStream: Subject = new Subject(); - - isBlackListed(userId: number): boolean { - return this.list.find((data) => data === userId) !== undefined; - } - - blackList(userId: number): void { - if (this.isBlackListed(userId)) return; - this.list.push(userId); - this.onBlockStream.next(userId); + private list: string[] = []; + public onBlockStream: Subject = new Subject(); + public onUnBlockStream: Subject = new Subject(); + + isBlackListed(userUuid: string): boolean { + return this.list.find((data) => data === userUuid) !== undefined; } - cancelBlackList(userId: number): void { - this.list.splice(this.list.findIndex(data => data === userId), 1); - this.onUnBlockStream.next(userId); + blackList(userUuid: string): void { + if (this.isBlackListed(userUuid)) return; + this.list.push(userUuid); + this.onBlockStream.next(userUuid); + } + + cancelBlackList(userUuid: string): void { + this.list.splice( + this.list.findIndex((data) => data === userUuid), + 1 + ); + this.onUnBlockStream.next(userUuid); } } -export const blackListManager = new BlackListManager(); \ No newline at end of file +export const blackListManager = new BlackListManager(); diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 0d3c4745..5045a5a3 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -11,7 +11,7 @@ import { get } from "svelte/store"; import { localStreamStore, LocalStreamStoreValue, obtainedMediaConstraintStore } from "../Stores/MediaStore"; import { screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore"; import { discussionManager } from "./DiscussionManager"; -import {playersStore} from "../Stores/PlayersStore"; +import { playersStore } from "../Stores/PlayersStore"; export interface UserSimplePeerInterface { userId: number; @@ -199,7 +199,7 @@ export class SimplePeer { } private getName(userId: number): string { - return playersStore.getPlayerById(userId)?.name || ''; + return playersStore.getPlayerById(userId)?.name || ""; } /** @@ -364,7 +364,8 @@ export class SimplePeer { } private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) { - if (blackListManager.isBlackListed(data.userId)) return; + const uuid = playersStore.getPlayerById(data.userId)?.userUuid || ""; + if (blackListManager.isBlackListed(uuid)) return; console.log("receiveWebrtcScreenSharingSignal", data); const streamResult = get(screenSharingLocalStreamStore); let stream: MediaStream | null = null; @@ -465,7 +466,8 @@ export class SimplePeer { } private sendLocalScreenSharingStreamToUser(userId: number, localScreenCapture: MediaStream): void { - if (blackListManager.isBlackListed(userId)) return; + const uuid = playersStore.getPlayerById(userId)?.userUuid || ""; + if (blackListManager.isBlackListed(uuid)) return; // If a connection already exists with user (because it is already sharing a screen with us... let's use this connection) if (this.PeerScreenSharingConnectionArray.has(userId)) { this.pushScreenSharingToRemoteUser(userId, localScreenCapture); diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index 30328c75..bde0bcde 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -8,6 +8,7 @@ import type { UserSimplePeerInterface } from "./SimplePeer"; import { get, readable, Readable } from "svelte/store"; import { obtainedMediaConstraintStore } from "../Stores/MediaStore"; import { discussionManager } from "./DiscussionManager"; +import { playersStore } from "../Stores/PlayersStore"; const Peer: SimplePeerNamespace.SimplePeer = require("simple-peer"); @@ -26,6 +27,7 @@ export class VideoPeer extends Peer { private remoteStream!: MediaStream; private blocked: boolean = false; public readonly userId: number; + public readonly userUuid: string; public readonly uniqueId: string; private onBlockSubscribe: Subscription; private onUnBlockSubscribe: Subscription; @@ -60,6 +62,7 @@ export class VideoPeer extends Peer { }); this.userId = user.userId; + this.userUuid = playersStore.getPlayerById(this.userId)?.userUuid || ""; this.uniqueId = "video_" + this.userId; this.streamStore = readable(null, (set) => { @@ -181,20 +184,20 @@ export class VideoPeer extends Peer { }); this.pushVideoToRemoteUser(localStream); - this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userId) => { - if (userId === this.userId) { + this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userUuid) => { + if (userUuid === this.userUuid) { this.toggleRemoteStream(false); this.sendBlockMessage(true); } }); - this.onUnBlockSubscribe = blackListManager.onUnBlockStream.subscribe((userId) => { - if (userId === this.userId) { + this.onUnBlockSubscribe = blackListManager.onUnBlockStream.subscribe((userUuid) => { + if (userUuid === this.userUuid) { this.toggleRemoteStream(true); this.sendBlockMessage(false); } }); - if (blackListManager.isBlackListed(this.userId)) { + if (blackListManager.isBlackListed(this.userUuid)) { this.sendBlockMessage(true); } } @@ -231,7 +234,7 @@ export class VideoPeer extends Peer { private stream(stream: MediaStream) { try { this.remoteStream = stream; - if (blackListManager.isBlackListed(this.userId) || this.blocked) { + if (blackListManager.isBlackListed(this.userUuid) || this.blocked) { this.toggleRemoteStream(false); } } catch (err) { diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 27d7cb10..a2e55bd8 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -62,7 +62,7 @@ message WebRtcSignalToServerMessage { } message ReportPlayerMessage { - int32 reportedUserId = 1; + string reportedUserUuid = 1; string reportComment = 2; } @@ -158,6 +158,7 @@ message UserJoinedMessage { PositionMessage position = 4; CompanionMessage companion = 5; string visitCardUrl = 6; + string userUuid = 7; } message UserLeftMessage { @@ -285,6 +286,7 @@ message UserJoinedZoneMessage { Zone fromZone = 5; CompanionMessage companion = 6; string visitCardUrl = 7; + string userUuid = 8; } message UserLeftZoneMessage { diff --git a/pusher/src/Model/Zone.ts b/pusher/src/Model/Zone.ts index 8eeeb3ef..501a2541 100644 --- a/pusher/src/Model/Zone.ts +++ b/pusher/src/Model/Zone.ts @@ -39,6 +39,7 @@ export type LeavesCallback = (thing: Movable, listener: User) => void;*/ export class UserDescriptor { private constructor( public readonly userId: number, + private userUuid: string, private name: string, private characterLayers: CharacterLayerMessage[], private position: PositionMessage, @@ -57,6 +58,7 @@ export class UserDescriptor { } return new UserDescriptor( message.getUserid(), + message.getUseruuid(), message.getName(), message.getCharacterlayersList(), position, @@ -84,6 +86,7 @@ export class UserDescriptor { userJoinedMessage.setVisitcardurl(this.visitCardUrl); } userJoinedMessage.setCompanion(this.companion); + userJoinedMessage.setUseruuid(this.userUuid); return userJoinedMessage; } diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index 8a0d3673..cfac5946 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -61,7 +61,6 @@ export interface AdminSocketData { export class SocketManager implements ZoneEventListener { private rooms: Map = new Map(); - private sockets: Map = new Map(); constructor() { clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => { @@ -191,8 +190,6 @@ export class SocketManager implements ZoneEventListener { .on("data", (message: ServerToClientMessage) => { if (message.hasRoomjoinedmessage()) { client.userId = (message.getRoomjoinedmessage() as RoomJoinedMessage).getCurrentuserid(); - // TODO: do we need this.sockets anymore? - this.sockets.set(client.userId, client); // If this is the first message sent, send back the viewport. this.handleViewport(client, viewport); @@ -302,14 +299,8 @@ export class SocketManager implements ZoneEventListener { async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) { try { - const reportedSocket = this.sockets.get(reportPlayerMessage.getReporteduserid()); - if (!reportedSocket) { - throw "reported socket user not found"; - } - //TODO report user on admin application - //todo: move to back because this fail if the reported player is in another pusher. await adminApi.reportPlayer( - reportedSocket.userUuid, + reportPlayerMessage.getReporteduseruuid(), reportPlayerMessage.getReportcomment(), client.userUuid, client.roomId.split("/")[2] @@ -334,14 +325,6 @@ export class SocketManager implements ZoneEventListener { socket.backConnection.write(pusherToBackMessage); } - private searchClientByIdOrFail(userId: number): ExSocketInterface { - const client: ExSocketInterface | undefined = this.sockets.get(userId); - if (client === undefined) { - throw new Error("Could not find user with id " + userId); - } - return client; - } - leaveRoom(socket: ExSocketInterface) { // leave previous room and world try { @@ -364,9 +347,8 @@ export class SocketManager implements ZoneEventListener { //Client.leave(Client.roomId); } finally { //delete Client.roomId; - this.sockets.delete(socket.userId); clientEventsEmitter.emitClientLeave(socket.userUuid, socket.roomId); - console.log("A user left (", this.sockets.size, " connected users)"); + console.log("A user left"); } } } finally { @@ -410,15 +392,6 @@ export class SocketManager implements ZoneEventListener { return this.rooms; } - searchClientByUuid(uuid: string): ExSocketInterface | null { - for (const socket of this.sockets.values()) { - if (socket.userUuid === uuid) { - return socket; - } - } - return null; - } - public handleQueryJitsiJwtMessage(client: ExSocketInterface, queryJitsiJwtMessage: QueryJitsiJwtMessage) { try { const room = queryJitsiJwtMessage.getJitsiroom(); From d51ac45079ef40f5304a63b8186596692c3d6feb Mon Sep 17 00:00:00 2001 From: GRL Date: Wed, 7 Jul 2021 14:26:53 +0200 Subject: [PATCH 49/79] Show/Hide Layer now unset collision and can show/hide all the layer in a group layer --- front/src/Api/Events/LayerEvent.ts | 1 + front/src/Api/iframe/room.ts | 10 ++++---- front/src/Phaser/Game/GameMap.ts | 4 +++ front/src/Phaser/Game/GameScene.ts | 39 +++++++++++++++++++++--------- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/front/src/Api/Events/LayerEvent.ts b/front/src/Api/Events/LayerEvent.ts index b56c3163..d3fdda22 100644 --- a/front/src/Api/Events/LayerEvent.ts +++ b/front/src/Api/Events/LayerEvent.ts @@ -3,6 +3,7 @@ import * as tg from "generic-type-guard"; export const isLayerEvent = new tg.IsInterface() .withProperties({ name: tg.isString, + group: tg.isBoolean, }) .get(); /** diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts index c70d0aad..8ff31375 100644 --- a/front/src/Api/iframe/room.ts +++ b/front/src/Api/iframe/room.ts @@ -4,7 +4,7 @@ import { isDataLayerEvent } from "../Events/DataLayerEvent"; import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent"; import { isGameStateEvent } from "../Events/GameStateEvent"; -import {IframeApiContribution, queryWorkadventure, sendToWorkadventure} from "./IframeApiContribution"; +import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution"; import { apiCallback } from "./registeredCallbacks"; import type { ITiledMap } from "../../Phaser/Map/ITiledMap"; @@ -93,11 +93,11 @@ export class WorkadventureRoomCommands extends IframeApiContribution layer.layer.name === layerName); } + public findPhaserLayers(groupName: string): TilemapLayer[] { + return this.phaserLayers.filter((l) => l.layer.name.includes(groupName)); + } + public addTerrain(terrain: Phaser.Tilemaps.Tileset): void { for (const phaserLayer of this.phaserLayers) { phaserLayer.tileset.push(terrain); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d767f0f4..5c3edfac 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1022,13 +1022,13 @@ ${escapedMessage} this.iframeSubscriptionList.push( iframeListener.showLayerStream.subscribe((layerEvent) => { - this.setLayerVisibility(layerEvent.name, true); + this.setLayerVisibility(layerEvent.name, true, layerEvent.group); }) ); this.iframeSubscriptionList.push( iframeListener.hideLayerStream.subscribe((layerEvent) => { - this.setLayerVisibility(layerEvent.name, false); + this.setLayerVisibility(layerEvent.name, false, layerEvent.group); }) ); @@ -1044,7 +1044,7 @@ ${escapedMessage} }) ); - iframeListener.registerAnswerer('getState', () => { + iframeListener.registerAnswerer("getState", () => { return { mapUrl: this.MapUrlFile, startLayerName: this.startPositionCalculator.startLayerName, @@ -1084,14 +1084,31 @@ ${escapedMessage} property.value = propertyValue; } - private setLayerVisibility(layerName: string, visible: boolean): void { - const phaserLayer = this.gameMap.findPhaserLayer(layerName); - if (phaserLayer === undefined) { - console.warn('Could not find layer "' + layerName + '" when calling WA.hideLayer / WA.showLayer'); - return; + private setLayerVisibility(layerName: string, visible: boolean, group: boolean): void { + if (group) { + const phaserLayers = this.gameMap.findPhaserLayers(layerName); + if (phaserLayers === []) { + console.warn( + 'Could not find layer with name that contains "' + + layerName + + '" when calling WA.hideLayer / WA.showLayer' + ); + return; + } + for (let i = 0; i < phaserLayers.length; i++) { + phaserLayers[i].setVisible(visible); + phaserLayers[i].setCollisionByProperty({ collides: true }, visible); + } + } else { + const phaserLayer = this.gameMap.findPhaserLayer(layerName); + if (phaserLayer === undefined) { + console.warn('Could not find layer "' + layerName + '" when calling WA.hideLayer / WA.showLayer'); + return; + } + phaserLayer.setVisible(visible); + phaserLayer.setCollisionByProperty({ collides: true }, visible); } - phaserLayer.setVisible(visible); - this.dirty = true; + this.markDirty(); } private getMapDirUrl(): string { @@ -1147,7 +1164,7 @@ ${escapedMessage} this.emoteManager.destroy(); this.peerStoreUnsubscribe(); this.biggestAvailableAreaStoreUnsubscribe(); - iframeListener.unregisterAnswerer('getState'); + iframeListener.unregisterAnswerer("getState"); mediaManager.hideGameOverlay(); From bef5e139c0d5fbcada2925bc5a3a54836986fdf7 Mon Sep 17 00:00:00 2001 From: GRL Date: Wed, 7 Jul 2021 14:42:17 +0200 Subject: [PATCH 50/79] SetTiles can now set a tile to null so that there is no more tile. --- front/src/Api/Events/SetTilesEvent.ts | 2 +- front/src/Api/iframe/room.ts | 4 ++-- front/src/Phaser/Game/GameMap.ts | 33 +++++++++++++++++++-------- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/front/src/Api/Events/SetTilesEvent.ts b/front/src/Api/Events/SetTilesEvent.ts index c7f8f16d..371f0884 100644 --- a/front/src/Api/Events/SetTilesEvent.ts +++ b/front/src/Api/Events/SetTilesEvent.ts @@ -5,7 +5,7 @@ export const isSetTilesEvent = tg.isArray( .withProperties({ x: tg.isNumber, y: tg.isNumber, - tile: tg.isUnion(tg.isNumber, tg.isString), + tile: tg.isUnion(tg.isUnion(tg.isNumber, tg.isString), tg.isNull), layer: tg.isString, }) .get() diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts index c70d0aad..a9ee52ce 100644 --- a/front/src/Api/iframe/room.ts +++ b/front/src/Api/iframe/room.ts @@ -4,7 +4,7 @@ import { isDataLayerEvent } from "../Events/DataLayerEvent"; import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent"; import { isGameStateEvent } from "../Events/GameStateEvent"; -import {IframeApiContribution, queryWorkadventure, sendToWorkadventure} from "./IframeApiContribution"; +import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution"; import { apiCallback } from "./registeredCallbacks"; import type { ITiledMap } from "../../Phaser/Map/ITiledMap"; @@ -34,7 +34,7 @@ interface User { interface TileDescriptor { x: number; y: number; - tile: number | string; + tile: number | string | null; layer: string; } diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index a616cf4a..1f232265 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -152,7 +152,10 @@ export class GameMap { } private getTileProperty(index: number): Array { - return this.tileSetPropertyMap[index]; + if (this.tileSetPropertyMap[index]) { + return this.tileSetPropertyMap[index]; + } + return []; } private trigger( @@ -198,37 +201,49 @@ export class GameMap { private putTileInFlatLayer(index: number, x: number, y: number, layer: string): void { const fLayer = this.findLayer(layer); if (fLayer == undefined) { - console.error("The layer that you want to change doesn't exist."); + console.error("The layer '" + layer + "' that you want to change doesn't exist."); return; } if (fLayer.type !== "tilelayer") { - console.error("The layer that you want to change is not a tilelayer. Tile can only be put in tilelayer."); + console.error( + "The layer '" + + layer + + "' that you want to change is not a tilelayer. Tile can only be put in tilelayer." + ); return; } if (typeof fLayer.data === "string") { - console.error("Data of the layer that you want to change is only readable."); + console.error("Data of the layer '" + layer + "' that you want to change is only readable."); return; } - fLayer.data[x + y * fLayer.height] = index; + fLayer.data[x + y * fLayer.width] = index; } - public putTile(tile: string | number, x: number, y: number, layer: string): void { + public putTile(tile: string | number | null, x: number, y: number, layer: string): void { const phaserLayer = this.findPhaserLayer(layer); if (phaserLayer) { + if (tile === null) { + phaserLayer.putTileAt(-1, x, y); + return; + } const tileIndex = this.getIndexForTileType(tile); if (tileIndex !== undefined) { this.putTileInFlatLayer(tileIndex, x, y, layer); const phaserTile = phaserLayer.putTileAt(tileIndex, x, y); for (const property of this.getTileProperty(tileIndex)) { - if (property.name === "collides" && property.value === "true") { + if (property.name === "collides" && property.value) { phaserTile.setCollision(true); } } } else { - console.error("The tile that you want to place doesn't exist."); + console.error("The tile '" + tile + "' that you want to place doesn't exist."); } } else { - console.error("The layer that you want to change is not a tilelayer. Tile can only be put in tilelayer."); + console.error( + "The layer '" + + layer + + "' that you want to change is not a tilelayer. Tile can only be put in tilelayer." + ); } } From 24811e0a31a70a4d32fe3fda16d0f71274d6f872 Mon Sep 17 00:00:00 2001 From: GRL Date: Wed, 7 Jul 2021 14:59:40 +0200 Subject: [PATCH 51/79] SetProperty delete a property where tha value is undefined and load the map of exitUrl property --- front/src/Phaser/Game/GameScene.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d6df242f..6427e0c0 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1077,14 +1077,24 @@ ${escapedMessage} console.warn('Could not find layer "' + layerName + '" when calling setProperty'); return; } + if (propertyName === "exitUrl" && typeof propertyValue === "string") { + this.loadNextGame(propertyValue); + } if (layer.properties === undefined) { layer.properties = []; } const property = layer.properties.find((property) => property.name === propertyName); if (property === undefined) { + if (propertyValue === undefined) { + return; + } layer.properties.push({ name: propertyName, type: typeof propertyValue, value: propertyValue }); return; } + if (propertyValue === undefined) { + const index = layer.properties.indexOf(property); + layer.properties.splice(index, 1); + } property.value = propertyValue; } From 17525e1e158256023efda437e88a1f04f4d2bef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?gr=C3=A9goire=20parant?= Date: Wed, 7 Jul 2021 16:42:26 +0200 Subject: [PATCH 52/79] Return at the new line into the Pop-up (#1267) Add regex to replace "\r\n" or "\r" or "\n" by
--- front/src/WebRtc/HtmlUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/WebRtc/HtmlUtils.ts b/front/src/WebRtc/HtmlUtils.ts index 942e553f..569abd07 100644 --- a/front/src/WebRtc/HtmlUtils.ts +++ b/front/src/WebRtc/HtmlUtils.ts @@ -25,7 +25,7 @@ export class HtmlUtils { } public static escapeHtml(html: string): string { - const text = document.createTextNode(html); + const text = document.createTextNode(html.replace(/(\r\n|\r|\n)/g,'
')); const p = document.createElement('p'); p.appendChild(text); return p.innerHTML; From e50292a2ba42814fd6759df68abef10b26a3723b Mon Sep 17 00:00:00 2001 From: GRL Date: Wed, 7 Jul 2021 16:58:54 +0200 Subject: [PATCH 53/79] Add documentation No second parameter --- docs/maps/api-room.md | 4 ++++ front/src/Api/Events/LayerEvent.ts | 1 - front/src/Api/iframe/room.ts | 8 ++++---- front/src/Phaser/Game/GameScene.ts | 22 +++++++++------------- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index 9d08ce1b..86886567 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -54,6 +54,7 @@ WA.room.showLayer(layerName : string): void WA.room.hideLayer(layerName : string) : void ``` These 2 methods can be used to show and hide a layer. +if `layerName` is the name of a group layer, show/hide all the layer in that group layer. Example : ```javascript @@ -70,6 +71,9 @@ WA.room.setProperty(layerName : string, propertyName : string, propertyValue : s Set the value of the `propertyName` property of the layer `layerName` at `propertyValue`. If the property doesn't exist, create the property `propertyName` and set the value of the property at `propertyValue`. +Note : +To unset a property form a layer, use `setProperty` with `propertyValue` set to `undefined`. + Example : ```javascript WA.room.setProperty('wikiLayer', 'openWebsite', 'https://www.wikipedia.org/'); diff --git a/front/src/Api/Events/LayerEvent.ts b/front/src/Api/Events/LayerEvent.ts index d3fdda22..b56c3163 100644 --- a/front/src/Api/Events/LayerEvent.ts +++ b/front/src/Api/Events/LayerEvent.ts @@ -3,7 +3,6 @@ import * as tg from "generic-type-guard"; export const isLayerEvent = new tg.IsInterface() .withProperties({ name: tg.isString, - group: tg.isBoolean, }) .get(); /** diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts index 8ff31375..deee0e2a 100644 --- a/front/src/Api/iframe/room.ts +++ b/front/src/Api/iframe/room.ts @@ -93,11 +93,11 @@ export class WorkadventureRoomCommands extends IframeApiContribution { - this.setLayerVisibility(layerEvent.name, true, layerEvent.group); + this.setLayerVisibility(layerEvent.name, true); }) ); this.iframeSubscriptionList.push( iframeListener.hideLayerStream.subscribe((layerEvent) => { - this.setLayerVisibility(layerEvent.name, false, layerEvent.group); + this.setLayerVisibility(layerEvent.name, false); }) ); @@ -1088,9 +1088,13 @@ ${escapedMessage} property.value = propertyValue; } - private setLayerVisibility(layerName: string, visible: boolean, group: boolean): void { - if (group) { - const phaserLayers = this.gameMap.findPhaserLayers(layerName); + private setLayerVisibility(layerName: string, visible: boolean): void { + const phaserLayer = this.gameMap.findPhaserLayer(layerName); + if (phaserLayer != undefined) { + phaserLayer.setVisible(visible); + phaserLayer.setCollisionByProperty({ collides: true }, visible); + } else { + const phaserLayers = this.gameMap.findPhaserLayers(layerName + "/"); if (phaserLayers === []) { console.warn( 'Could not find layer with name that contains "' + @@ -1103,14 +1107,6 @@ ${escapedMessage} phaserLayers[i].setVisible(visible); phaserLayers[i].setCollisionByProperty({ collides: true }, visible); } - } else { - const phaserLayer = this.gameMap.findPhaserLayer(layerName); - if (phaserLayer === undefined) { - console.warn('Could not find layer "' + layerName + '" when calling WA.hideLayer / WA.showLayer'); - return; - } - phaserLayer.setVisible(visible); - phaserLayer.setCollisionByProperty({ collides: true }, visible); } this.markDirty(); } From 64c569c42f6bf0b1edd1ee039551c7aff0132bb8 Mon Sep 17 00:00:00 2001 From: GRL Date: Wed, 7 Jul 2021 17:06:23 +0200 Subject: [PATCH 54/79] Add documentation and CHANGELOG Modify error message --- CHANGELOG.md | 4 ++-- docs/maps/api-room.md | 1 + front/src/Phaser/Game/GameMap.ts | 6 +----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a83e8213..ff7496ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,12 @@ - New scripting API features : - Use `WA.room.showLayer(): void` to show a layer - Use `WA.room.hideLayer(): void` to hide a layer - - Use `WA.room.setProperty() : void` to add or change existing property of a layer + - Use `WA.room.setProperty() : void` to add, delete or change existing property of a layer - Use `WA.player.onPlayerMove(): void` to track the movement of the current player - Use `WA.room.getCurrentUser(): Promise` to get the ID, name and tags of the current player - Use `WA.room.getCurrentRoom(): Promise` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started - Use `WA.ui.registerMenuCommand(): void` to add a custom menu - - Use `WA.room.setTiles(): void` to change an array of tiles + - Use `WA.room.setTiles(): void` to add, delete or change an array of tiles - Users blocking now relies on UUID rather than ID. A blocked user that leaves a room and comes back will stay blocked. ## Version 1.4.3 - 1.4.4 - 1.4.5 diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index 9d08ce1b..22735f6c 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -134,6 +134,7 @@ If `tile` is a string, it's not the id of the tile but the value of the property **Important !** : If you use `tile` as a number, be sure to add the `firstgid` of the tileset of the tile that you want to the id of the tile in Tiled Editor. +Note: If you want to unset a tile, use `setTiles` with `tile` set to `null`. Example : ```javascript diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 1f232265..99a1edad 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -239,11 +239,7 @@ export class GameMap { console.error("The tile '" + tile + "' that you want to place doesn't exist."); } } else { - console.error( - "The layer '" + - layer + - "' that you want to change is not a tilelayer. Tile can only be put in tilelayer." - ); + console.error("The layer '" + layer + "' does not exist (or is not a tilelaye)."); } } From cb5bdb5fea0d800bfecc235e78995d9650facf0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 7 Jul 2021 17:15:22 +0200 Subject: [PATCH 55/79] Fixing typo --- docs/maps/api-room.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index 86886567..93cb732a 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -72,7 +72,7 @@ WA.room.setProperty(layerName : string, propertyName : string, propertyValue : s Set the value of the `propertyName` property of the layer `layerName` at `propertyValue`. If the property doesn't exist, create the property `propertyName` and set the value of the property at `propertyValue`. Note : -To unset a property form a layer, use `setProperty` with `propertyValue` set to `undefined`. +To unset a property from a layer, use `setProperty` with `propertyValue` set to `undefined`. Example : ```javascript From 3cfbcc6b020b615e2b768295ff0d58c856ea7b0e Mon Sep 17 00:00:00 2001 From: kharhamel Date: Wed, 7 Jul 2021 18:07:58 +0200 Subject: [PATCH 56/79] FEATURE: migrated the chat window to svelte --- CHANGELOG.md | 5 + back/src/Model/GameRoom.ts | 6 - back/src/Services/SocketManager.ts | 6 - front/dist/index.tmpl.html | 3 +- front/dist/static/images/send.png | Bin 0 -> 8523 bytes front/src/Components/App.svelte | 13 +- front/src/Components/Chat/Chat.svelte | 97 ++++++++ front/src/Components/Chat/ChatElement.svelte | 74 ++++++ .../Components/Chat/ChatMessageForm.svelte | 55 +++++ .../src/Components/Chat/ChatPlayerName.svelte | 37 +++ front/src/Phaser/Components/OpenChatIcon.ts | 12 +- .../Entity/PlayerTexturesLoadingManager.ts | 1 - front/src/Phaser/Game/GameScene.ts | 4 +- front/src/Phaser/Game/PlayerInterface.ts | 1 + front/src/Stores/ChatStore.ts | 102 ++++++++ front/src/Stores/PlayersStore.ts | 2 + front/src/Stores/UserInputStore.ts | 13 +- front/src/WebRtc/ColorGenerator.ts | 48 ++++ front/src/WebRtc/DiscussionManager.ts | 226 +----------------- front/src/WebRtc/MediaManager.ts | 21 +- front/src/WebRtc/SimplePeer.ts | 17 +- front/src/WebRtc/VideoPeer.ts | 27 ++- front/style/fonts.scss | 4 - front/webpack.config.ts | 1 - 24 files changed, 470 insertions(+), 305 deletions(-) create mode 100644 front/dist/static/images/send.png create mode 100644 front/src/Components/Chat/Chat.svelte create mode 100644 front/src/Components/Chat/ChatElement.svelte create mode 100644 front/src/Components/Chat/ChatMessageForm.svelte create mode 100644 front/src/Components/Chat/ChatPlayerName.svelte create mode 100644 front/src/Stores/ChatStore.ts create mode 100644 front/src/WebRtc/ColorGenerator.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a83e8213..018848b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,11 @@ - Use `WA.ui.registerMenuCommand(): void` to add a custom menu - Use `WA.room.setTiles(): void` to change an array of tiles - Users blocking now relies on UUID rather than ID. A blocked user that leaves a room and comes back will stay blocked. +- The text chat was redesigned to be prettier and to use more features : + - The chat is now persistent bewteen discussions and always accesible + - The chat now tracks incoming and outcoming users in your conversation + - The chat allows your to see the visit card of users + - You can close the chat window with the escape key ## Version 1.4.3 - 1.4.4 - 1.4.5 diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 020f4c29..71d2124e 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -15,12 +15,6 @@ import { Admin } from "../Model/Admin"; export type ConnectCallback = (user: User, group: Group) => void; export type DisconnectCallback = (user: User, group: Group) => void; -export enum GameRoomPolicyTypes { - ANONYMOUS_POLICY = 1, - MEMBERS_ONLY_POLICY, - USE_TAGS_POLICY, -} - export class GameRoom { private readonly minDistance: number; private readonly groupRadius: number; diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 8d04e713..8d1659df 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -436,10 +436,7 @@ export class SocketManager { const serverToClientMessage1 = new ServerToClientMessage(); serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1); - //if (!user.socket.disconnecting) { user.socket.write(serverToClientMessage1); - //console.log('Sending webrtcstart initiator to '+user.socket.userId) - //} const webrtcStartMessage2 = new WebRtcStartMessage(); webrtcStartMessage2.setUserid(user.id); @@ -453,10 +450,7 @@ export class SocketManager { const serverToClientMessage2 = new ServerToClientMessage(); serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2); - //if (!otherUser.socket.disconnecting) { otherUser.socket.write(serverToClientMessage2); - //console.log('Sending webrtcstart to '+otherUser.socket.userId) - //} } } diff --git a/front/dist/index.tmpl.html b/front/dist/index.tmpl.html index aa63229f..30ea8353 100644 --- a/front/dist/index.tmpl.html +++ b/front/dist/index.tmpl.html @@ -37,8 +37,7 @@
-
-
+
diff --git a/front/dist/static/images/send.png b/front/dist/static/images/send.png new file mode 100644 index 0000000000000000000000000000000000000000..1f75634a8ec2156ec4dfdf51353aa7f6f760143e GIT binary patch literal 8523 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 TvtN}2_6tB3)~5
{/if} - - {#if $gameOverlayVisibilityStore}
@@ -94,4 +88,7 @@
{/if} + {#if $chatVisibilityStore} + + {/if}
diff --git a/front/src/Components/Chat/Chat.svelte b/front/src/Components/Chat/Chat.svelte new file mode 100644 index 00000000..c4f9075d --- /dev/null +++ b/front/src/Components/Chat/Chat.svelte @@ -0,0 +1,97 @@ + + + + + + + + \ No newline at end of file diff --git a/front/src/Components/Chat/ChatElement.svelte b/front/src/Components/Chat/ChatElement.svelte new file mode 100644 index 00000000..95a050eb --- /dev/null +++ b/front/src/Components/Chat/ChatElement.svelte @@ -0,0 +1,74 @@ + + +
+
+ {#if message.type === ChatMessageTypes.userIncoming} + ➡️: {#each targets as target}{/each} ({renderDate(message.date)}) + {:else if message.type === ChatMessageTypes.userOutcoming} + ⬅️: {#each targets as target}{/each} ({renderDate(message.date)}) + {:else if message.type === ChatMessageTypes.me} +

Me: ({renderDate(message.date)})

+ {#each texts as text} +

{@html urlifyText(text)}

+ {/each} + {:else} +

: ({renderDate(message.date)})

+ {#each texts as text} +

{@html urlifyText(text)}

+ {/each} + {/if} +
+
+ + \ No newline at end of file diff --git a/front/src/Components/Chat/ChatMessageForm.svelte b/front/src/Components/Chat/ChatMessageForm.svelte new file mode 100644 index 00000000..acfd68ae --- /dev/null +++ b/front/src/Components/Chat/ChatMessageForm.svelte @@ -0,0 +1,55 @@ + + +
+ + +
+ + \ No newline at end of file diff --git a/front/src/Components/Chat/ChatPlayerName.svelte b/front/src/Components/Chat/ChatPlayerName.svelte new file mode 100644 index 00000000..f0fbe8cd --- /dev/null +++ b/front/src/Components/Chat/ChatPlayerName.svelte @@ -0,0 +1,37 @@ + + + showMenu = !showMenu}> + {player.name} + + +{#if showMenu} +
    +
  • +
+{/if} + + + \ No newline at end of file diff --git a/front/src/Phaser/Components/OpenChatIcon.ts b/front/src/Phaser/Components/OpenChatIcon.ts index ab07a80c..8c648bc1 100644 --- a/front/src/Phaser/Components/OpenChatIcon.ts +++ b/front/src/Phaser/Components/OpenChatIcon.ts @@ -1,7 +1,7 @@ -import {discussionManager} from "../../WebRtc/DiscussionManager"; -import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes"; +import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes"; +import { chatVisibilityStore } from "../../Stores/ChatStore"; -export const openChatIconName = 'openChatIcon'; +export const openChatIconName = "openChatIcon"; export class OpenChatIcon extends Phaser.GameObjects.Image { constructor(scene: Phaser.Scene, x: number, y: number) { super(scene, x, y, openChatIconName, 3); @@ -9,9 +9,9 @@ export class OpenChatIcon extends Phaser.GameObjects.Image { this.setScrollFactor(0, 0); this.setOrigin(0, 1); this.setInteractive(); - this.setVisible(false); + //this.setVisible(false); this.setDepth(DEPTH_INGAME_TEXT_INDEX); - this.on("pointerup", () => discussionManager.showDiscussionPart()); + this.on("pointerup", () => chatVisibilityStore.set(true)); } -} \ No newline at end of file +} diff --git a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts index d2a659ec..3c47c9d9 100644 --- a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts +++ b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts @@ -101,7 +101,6 @@ export const createLoadingPromise = ( frameConfig: FrameConfig ) => { return new Promise((res, rej) => { - console.log("count", loadPlugin.listenerCount("loaderror")); if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { return res(playerResourceDescriptor); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d6df242f..3ccc9fd9 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -692,12 +692,12 @@ export class GameScene extends DirtyScene { const self = this; this.simplePeer.registerPeerConnectionListener({ onConnect(peer) { - self.openChatIcon.setVisible(true); + //self.openChatIcon.setVisible(true); audioManager.decreaseVolume(); }, onDisconnect(userId: number) { if (self.simplePeer.getNbConnections() === 0) { - self.openChatIcon.setVisible(false); + //self.openChatIcon.setVisible(false); audioManager.restoreVolume(); } }, diff --git a/front/src/Phaser/Game/PlayerInterface.ts b/front/src/Phaser/Game/PlayerInterface.ts index 5a81c89a..6ab439df 100644 --- a/front/src/Phaser/Game/PlayerInterface.ts +++ b/front/src/Phaser/Game/PlayerInterface.ts @@ -7,4 +7,5 @@ export interface PlayerInterface { visitCardUrl: string | null; companion: string | null; userUuid: string; + color?: string; } diff --git a/front/src/Stores/ChatStore.ts b/front/src/Stores/ChatStore.ts new file mode 100644 index 00000000..344a424e --- /dev/null +++ b/front/src/Stores/ChatStore.ts @@ -0,0 +1,102 @@ +import { writable } from "svelte/store"; +import { playersStore } from "./PlayersStore"; +import type { PlayerInterface } from "../Phaser/Game/PlayerInterface"; + +export const chatVisibilityStore = writable(false); +export const chatInputFocusStore = writable(false); + +export const newChatMessageStore = writable(null); + +export enum ChatMessageTypes { + text = 1, + me, + userIncoming, + userOutcoming, +} + +export interface ChatMessage { + type: ChatMessageTypes; + date: Date; + author?: PlayerInterface; + targets?: PlayerInterface[]; + text?: string[]; +} + +function getAuthor(authorId: number): PlayerInterface { + const author = playersStore.getPlayerById(authorId); + if (!author) { + throw "Could not find data for author " + authorId; + } + return author; +} + +function createChatMessagesStore() { + const { subscribe, update } = writable([]); + + return { + subscribe, + addIncomingUser(authorId: number) { + update((list) => { + const lastMessage = list[list.length - 1]; + if (lastMessage && lastMessage.type === ChatMessageTypes.userIncoming && lastMessage.targets) { + lastMessage.targets.push(getAuthor(authorId)); + } else { + list.push({ + type: ChatMessageTypes.userIncoming, + targets: [getAuthor(authorId)], + date: new Date(), + }); + } + return list; + }); + }, + addOutcomingUser(authorId: number) { + update((list) => { + const lastMessage = list[list.length - 1]; + if (lastMessage && lastMessage.type === ChatMessageTypes.userOutcoming && lastMessage.targets) { + lastMessage.targets.push(getAuthor(authorId)); + } else { + list.push({ + type: ChatMessageTypes.userOutcoming, + targets: [getAuthor(authorId)], + date: new Date(), + }); + } + return list; + }); + }, + addPersonnalMessage(text: string) { + newChatMessageStore.set(text); + update((list) => { + const lastMessage = list[list.length - 1]; + if (lastMessage && lastMessage.type === ChatMessageTypes.me && lastMessage.text) { + lastMessage.text.push(text); + } else { + list.push({ + type: ChatMessageTypes.me, + text: [text], + date: new Date(), + }); + } + return list; + }); + }, + addExternalMessage(authorId: number, text: string) { + update((list) => { + const lastMessage = list[list.length - 1]; + if (lastMessage && lastMessage.type === ChatMessageTypes.text && lastMessage.text) { + lastMessage.text.push(text); + } else { + list.push({ + type: ChatMessageTypes.text, + text: [text], + author: getAuthor(authorId), + date: new Date(), + }); + } + return list; + }); + }, + }; +} +export const chatMessagesStore = createChatMessagesStore(); diff --git a/front/src/Stores/PlayersStore.ts b/front/src/Stores/PlayersStore.ts index 6c21de7a..2ea988bb 100644 --- a/front/src/Stores/PlayersStore.ts +++ b/front/src/Stores/PlayersStore.ts @@ -1,6 +1,7 @@ import { writable } from "svelte/store"; import type { PlayerInterface } from "../Phaser/Game/PlayerInterface"; import type { RoomConnection } from "../Connexion/RoomConnection"; +import { getRandomColor } from "../WebRtc/ColorGenerator"; /** * A store that contains the list of players currently known. @@ -24,6 +25,7 @@ function createPlayersStore() { visitCardUrl: message.visitCardUrl, companion: message.companion, userUuid: message.userUuid, + color: getRandomColor(), }); return users; }); diff --git a/front/src/Stores/UserInputStore.ts b/front/src/Stores/UserInputStore.ts index cbb7f0c3..993d8795 100644 --- a/front/src/Stores/UserInputStore.ts +++ b/front/src/Stores/UserInputStore.ts @@ -1,10 +1,11 @@ -import {derived} from "svelte/store"; -import {consoleGlobalMessageManagerFocusStore} from "./ConsoleGlobalMessageManagerStore"; +import { derived } from "svelte/store"; +import { consoleGlobalMessageManagerFocusStore } from "./ConsoleGlobalMessageManagerStore"; +import { chatInputFocusStore } from "./ChatStore"; //derived from the focus on Menu, ConsoleGlobal, Chat and ... export const enableUserInputsStore = derived( - consoleGlobalMessageManagerFocusStore, - ($consoleGlobalMessageManagerFocusStore) => { - return !$consoleGlobalMessageManagerFocusStore; + [consoleGlobalMessageManagerFocusStore, chatInputFocusStore], + ([$consoleGlobalMessageManagerFocusStore, $chatInputFocusStore]) => { + return !$consoleGlobalMessageManagerFocusStore && !$chatInputFocusStore; } -); \ No newline at end of file +); diff --git a/front/src/WebRtc/ColorGenerator.ts b/front/src/WebRtc/ColorGenerator.ts new file mode 100644 index 00000000..a42aee85 --- /dev/null +++ b/front/src/WebRtc/ColorGenerator.ts @@ -0,0 +1,48 @@ +export function getRandomColor(): string { + return hsv_to_rgb(Math.random(), 0.5, 0.95); +} + +//todo: test this. +function hsv_to_rgb(hue: number, saturation: number, brightness: number): string { + const h_i = Math.floor(hue * 6); + const f = hue * 6 - h_i; + const p = brightness * (1 - saturation); + const q = brightness * (1 - f * saturation); + const t = brightness * (1 - (1 - f) * saturation); + let r: number, g: number, b: number; + switch (h_i) { + case 0: + r = brightness; + g = t; + b = p; + break; + case 1: + r = q; + g = brightness; + b = p; + break; + case 2: + r = p; + g = brightness; + b = t; + break; + case 3: + r = p; + g = q; + b = brightness; + break; + case 4: + r = t; + g = p; + b = brightness; + break; + case 5: + r = brightness; + g = p; + b = q; + break; + default: + throw "h_i cannot be " + h_i; + } + return "#" + Math.floor(r * 256).toString(16) + Math.floor(g * 256).toString(16) + Math.floor(b * 256).toString(16); +} diff --git a/front/src/WebRtc/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts index ae351f76..a3c928f4 100644 --- a/front/src/WebRtc/DiscussionManager.ts +++ b/front/src/WebRtc/DiscussionManager.ts @@ -1,232 +1,12 @@ -import { HtmlUtils } from "./HtmlUtils"; -import type { UserInputManager } from "../Phaser/UserInput/UserInputManager"; -import { connectionManager } from "../Connexion/ConnectionManager"; -import { GameConnexionTypes } from "../Url/UrlManager"; import { iframeListener } from "../Api/IframeListener"; -import { showReportScreenStore } from "../Stores/ShowReportScreenStore"; - -export type SendMessageCallback = (message: string) => void; +import { chatMessagesStore, chatVisibilityStore } from "../Stores/ChatStore"; export class DiscussionManager { - private mainContainer: HTMLDivElement; - - private divDiscuss?: HTMLDivElement; - private divParticipants?: HTMLDivElement; - private nbpParticipants?: HTMLParagraphElement; - private divMessages?: HTMLParagraphElement; - - private participants: Map = new Map(); - - private activeDiscussion: boolean = false; - - private sendMessageCallBack: Map = new Map< - number | string, - SendMessageCallback - >(); - - private userInputManager?: UserInputManager; - constructor() { - this.mainContainer = HtmlUtils.getElementByIdOrFail("main-container"); - this.createDiscussPart(""); //todo: why do we always use empty string? - iframeListener.chatStream.subscribe((chatEvent) => { - this.addMessage(chatEvent.author, chatEvent.message, false); - this.showDiscussion(); + chatMessagesStore.addExternalMessage(parseInt(chatEvent.author), chatEvent.message); + chatVisibilityStore.set(true); }); - this.onSendMessageCallback("iframe_listener", (message) => { - iframeListener.sendUserInputChat(message); - }); - } - - private createDiscussPart(name: string) { - this.divDiscuss = document.createElement("div"); - this.divDiscuss.classList.add("discussion"); - - const buttonCloseDiscussion: HTMLButtonElement = document.createElement("button"); - buttonCloseDiscussion.classList.add("close-btn"); - buttonCloseDiscussion.innerHTML = ``; - buttonCloseDiscussion.addEventListener("click", () => { - this.hideDiscussion(); - }); - this.divDiscuss.appendChild(buttonCloseDiscussion); - - const myName: HTMLParagraphElement = document.createElement("p"); - myName.innerText = name.toUpperCase(); - this.nbpParticipants = document.createElement("p"); - this.nbpParticipants.innerText = "PARTICIPANTS (1)"; - - this.divParticipants = document.createElement("div"); - this.divParticipants.classList.add("participants"); - - this.divMessages = document.createElement("div"); - this.divMessages.classList.add("messages"); - this.divMessages.innerHTML = "

Local messages

"; - - this.divDiscuss.appendChild(myName); - this.divDiscuss.appendChild(this.nbpParticipants); - this.divDiscuss.appendChild(this.divParticipants); - this.divDiscuss.appendChild(this.divMessages); - - const sendDivMessage: HTMLDivElement = document.createElement("div"); - sendDivMessage.classList.add("send-message"); - const inputMessage: HTMLInputElement = document.createElement("input"); - inputMessage.onfocus = () => { - if (this.userInputManager) { - this.userInputManager.disableControls(); - } - }; - inputMessage.onblur = () => { - if (this.userInputManager) { - this.userInputManager.restoreControls(); - } - }; - inputMessage.type = "text"; - inputMessage.addEventListener("keyup", (event: KeyboardEvent) => { - if (event.key === "Enter") { - event.preventDefault(); - if (inputMessage.value === null || inputMessage.value === "" || inputMessage.value === undefined) { - return; - } - this.addMessage(name, inputMessage.value, true); - for (const callback of this.sendMessageCallBack.values()) { - callback(inputMessage.value); - } - inputMessage.value = ""; - } - }); - sendDivMessage.appendChild(inputMessage); - this.divDiscuss.appendChild(sendDivMessage); - - //append in main container - this.mainContainer.appendChild(this.divDiscuss); - - this.addParticipant("me", "Moi", undefined, true); - } - - public addParticipant( - userId: number | "me", - name: string | undefined, - img?: string | undefined, - isMe: boolean = false - ) { - const divParticipant: HTMLDivElement = document.createElement("div"); - divParticipant.classList.add("participant"); - divParticipant.id = `participant-${userId}`; - - const divImgParticipant: HTMLImageElement = document.createElement("img"); - divImgParticipant.src = "resources/logos/boy.svg"; - if (img !== undefined) { - divImgParticipant.src = img; - } - const divPParticipant: HTMLParagraphElement = document.createElement("p"); - if (!name) { - name = "Anonymous"; - } - divPParticipant.innerText = name; - - divParticipant.appendChild(divImgParticipant); - divParticipant.appendChild(divPParticipant); - - if ( - !isMe && - connectionManager.getConnexionType && - connectionManager.getConnexionType !== GameConnexionTypes.anonymous && - userId !== "me" - ) { - const reportBanUserAction: HTMLButtonElement = document.createElement("button"); - reportBanUserAction.classList.add("report-btn"); - reportBanUserAction.innerText = "Report"; - reportBanUserAction.addEventListener("click", () => { - showReportScreenStore.set({ userId: userId, userName: name ? name : "" }); - }); - divParticipant.appendChild(reportBanUserAction); - } - - this.divParticipants?.appendChild(divParticipant); - - this.participants.set(userId, divParticipant); - - this.updateParticipant(this.participants.size); - } - - public updateParticipant(nb: number) { - if (!this.nbpParticipants) { - return; - } - this.nbpParticipants.innerText = `PARTICIPANTS (${nb})`; - } - - public addMessage(name: string, message: string, isMe: boolean = false) { - const divMessage: HTMLDivElement = document.createElement("div"); - divMessage.classList.add("message"); - if (isMe) { - divMessage.classList.add("me"); - } - - const pMessage: HTMLParagraphElement = document.createElement("p"); - const date = new Date(); - if (isMe) { - name = "Me"; - } else { - name = HtmlUtils.escapeHtml(name); - } - pMessage.innerHTML = `${name} - - ${date.getHours()}:${date.getMinutes()} - `; - divMessage.appendChild(pMessage); - - const userMessage: HTMLParagraphElement = document.createElement("p"); - userMessage.innerHTML = HtmlUtils.urlify(message); - userMessage.classList.add("body"); - divMessage.appendChild(userMessage); - this.divMessages?.appendChild(divMessage); - - //automatic scroll when there are new message - setTimeout(() => { - this.divMessages?.scroll({ - top: this.divMessages?.scrollTop + divMessage.getBoundingClientRect().y, - behavior: "smooth", - }); - }, 200); - } - - public removeParticipant(userId: number | string) { - const element = this.participants.get(userId); - if (element) { - element.remove(); - this.participants.delete(userId); - } - //if all participant leave, hide discussion button - - this.sendMessageCallBack.delete(userId); - } - - public onSendMessageCallback(userId: string | number, callback: SendMessageCallback): void { - this.sendMessageCallBack.set(userId, callback); - } - - get activatedDiscussion() { - return this.activeDiscussion; - } - - private showDiscussion() { - this.activeDiscussion = true; - this.divDiscuss?.classList.add("active"); - } - - private hideDiscussion() { - this.activeDiscussion = false; - this.divDiscuss?.classList.remove("active"); - } - - public setUserInputManager(userInputManager: UserInputManager) { - this.userInputManager = userInputManager; - } - - public showDiscussionPart() { - this.showDiscussion(); } } diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index d9847f44..126bf1a8 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -1,16 +1,11 @@ -import { DivImportance, layoutManager } from "./LayoutManager"; +import { layoutManager } from "./LayoutManager"; import { HtmlUtils } from "./HtmlUtils"; -import { discussionManager, SendMessageCallback } from "./DiscussionManager"; import type { UserInputManager } from "../Phaser/UserInput/UserInputManager"; -import { localUserStore } from "../Connexion/LocalUserStore"; -import type { UserSimplePeerInterface } from "./SimplePeer"; -import { SoundMeter } from "../Phaser/Components/SoundMeter"; import { DISABLE_NOTIFICATIONS } from "../Enum/EnvironmentVariable"; import { localStreamStore } from "../Stores/MediaStore"; import { screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore"; import { helpCameraSettingsVisibleStore } from "../Stores/HelpCameraSettingsStore"; -export type UpdatedLocalStreamCallback = (media: MediaStream | null) => void; export type StartScreenSharingCallback = (media: MediaStream) => void; export type StopScreenSharingCallback = (media: MediaStream) => void; @@ -182,22 +177,8 @@ export class MediaManager { } } - public addNewMessage(name: string, message: string, isMe: boolean = false) { - discussionManager.addMessage(name, message, isMe); - - //when there are new message, show discussion - if (!discussionManager.activatedDiscussion) { - discussionManager.showDiscussionPart(); - } - } - - public addSendMessageCallback(userId: string | number, callback: SendMessageCallback) { - discussionManager.onSendMessageCallback(userId, callback); - } - public setUserInputManager(userInputManager: UserInputManager) { this.userInputManager = userInputManager; - discussionManager.setUserInputManager(userInputManager); } public getNotification() { diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 5045a5a3..e30f1b1f 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -12,6 +12,7 @@ import { localStreamStore, LocalStreamStoreValue, obtainedMediaConstraintStore } import { screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore"; import { discussionManager } from "./DiscussionManager"; import { playersStore } from "../Stores/PlayersStore"; +import { newChatMessageStore } from "../Stores/ChatStore"; export interface UserSimplePeerInterface { userId: number; @@ -155,27 +156,11 @@ export class SimplePeer { const name = this.getName(user.userId); - discussionManager.removeParticipant(user.userId); - this.lastWebrtcUserName = user.webRtcUser; this.lastWebrtcPassword = user.webRtcPassword; const peer = new VideoPeer(user, user.initiator ? user.initiator : false, name, this.Connection, localStream); - //permit to send message - mediaManager.addSendMessageCallback(user.userId, (message: string) => { - peer.write( - new Buffer( - JSON.stringify({ - type: MESSAGE_TYPE_MESSAGE, - name: this.myName.toUpperCase(), - userId: this.userId, - message: message, - }) - ) - ); - }); - peer.toClose = false; // When a connection is established to a video stream, and if a screen sharing is taking place, // the user sharing screen should also initiate a connection to the remote user! diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index bde0bcde..45118b5f 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -5,10 +5,11 @@ import type { RoomConnection } from "../Connexion/RoomConnection"; import { blackListManager } from "./BlackListManager"; import type { Subscription } from "rxjs"; import type { UserSimplePeerInterface } from "./SimplePeer"; -import { get, readable, Readable } from "svelte/store"; +import { get, readable, Readable, Unsubscriber } from "svelte/store"; import { obtainedMediaConstraintStore } from "../Stores/MediaStore"; import { discussionManager } from "./DiscussionManager"; import { playersStore } from "../Stores/PlayersStore"; +import { chatMessagesStore, chatVisibilityStore, newChatMessageStore } from "../Stores/ChatStore"; const Peer: SimplePeerNamespace.SimplePeer = require("simple-peer"); @@ -34,6 +35,7 @@ export class VideoPeer extends Peer { public readonly streamStore: Readable; public readonly statusStore: Readable; public readonly constraintsStore: Readable; + private newMessageunsubscriber: Unsubscriber | null = null; constructor( public user: UserSimplePeerInterface, @@ -147,6 +149,20 @@ export class VideoPeer extends Peer { this.on("connect", () => { this._connected = true; + chatMessagesStore.addIncomingUser(this.userId); + + this.newMessageunsubscriber = newChatMessageStore.subscribe((newMessage) => { + if (!newMessage) return; + this.write( + new Buffer( + JSON.stringify({ + type: MESSAGE_TYPE_MESSAGE, + message: newMessage, + }) + ) + ); //send more data + newChatMessageStore.set(null); //This is to prevent a newly created SimplePeer to send an old message a 2nd time. Is there a better way? + }); }); this.on("data", (chunk: Buffer) => { @@ -164,8 +180,9 @@ export class VideoPeer extends Peer { mediaManager.disabledVideoByUserId(this.userId); } } else if (message.type === MESSAGE_TYPE_MESSAGE) { - if (!blackListManager.isBlackListed(message.userId)) { - mediaManager.addNewMessage(message.name, message.message); + if (!blackListManager.isBlackListed(this.userUuid)) { + chatMessagesStore.addExternalMessage(this.userId, message.message); + chatVisibilityStore.set(true); } } else if (message.type === MESSAGE_TYPE_BLOCKED) { //FIXME when A blacklists B, the output stream from A is muted in B's js client. This is insecure since B can manipulate the code to unmute A stream. @@ -253,7 +270,9 @@ export class VideoPeer extends Peer { } this.onBlockSubscribe.unsubscribe(); this.onUnBlockSubscribe.unsubscribe(); - discussionManager.removeParticipant(this.userId); + if (this.newMessageunsubscriber) this.newMessageunsubscriber(); + chatMessagesStore.addOutcomingUser(this.userId); + //discussionManager.removeParticipant(this.userId); // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. super.destroy(error); diff --git a/front/style/fonts.scss b/front/style/fonts.scss index a49d3967..526f6615 100644 --- a/front/style/fonts.scss +++ b/front/style/fonts.scss @@ -1,9 +1,5 @@ @import "~@fontsource/press-start-2p/index.css"; -*{ - font-family: PixelFont-7,monospace; -} - .nes-btn { font-family: "Press Start 2P"; } diff --git a/front/webpack.config.ts b/front/webpack.config.ts index b6efb389..37362baf 100644 --- a/front/webpack.config.ts +++ b/front/webpack.config.ts @@ -7,7 +7,6 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin"; import sveltePreprocess from "svelte-preprocess"; import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin"; import NodePolyfillPlugin from "node-polyfill-webpack-plugin"; -import { DISPLAY_TERMS_OF_USE } from "./src/Enum/EnvironmentVariable"; const mode = process.env.NODE_ENV ?? "development"; const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS; From b9a2433283766e2ff61a1f753d956a41abf37b86 Mon Sep 17 00:00:00 2001 From: GRL Date: Mon, 12 Jul 2021 11:59:05 +0200 Subject: [PATCH 57/79] Upgrade graphic of the chat --- front/src/Components/Chat/Chat.svelte | 12 ++++++++++-- front/src/Components/Chat/ChatElement.svelte | 4 ++-- front/src/Components/Chat/ChatMessageForm.svelte | 2 ++ front/src/WebRtc/ColorGenerator.ts | 6 +++++- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/front/src/Components/Chat/Chat.svelte b/front/src/Components/Chat/Chat.svelte index c4f9075d..093d01a5 100644 --- a/front/src/Components/Chat/Chat.svelte +++ b/front/src/Components/Chat/Chat.svelte @@ -32,7 +32,7 @@