From f6f2ce539eb16127a2e2e734d92fb00fb08eb422 Mon Sep 17 00:00:00 2001 From: narendra-drupal <87118318+narendra-drupal@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:24:53 +0530 Subject: [PATCH 01/23] Initial commit --- sveltejs/public/build/bundle.js | Bin 275427 -> 276854 bytes sveltejs/public/build/bundle.js.map | Bin 252546 -> 255677 bytes sveltejs/src/ProjectBrowser.svelte | 43 ++++----------- sveltejs/src/QueryManager.js | 82 ++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 31 deletions(-) create mode 100644 sveltejs/src/QueryManager.js diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index 2dc635852ab161656e8e26b7c0bd72abc61f7084..82181ef54e9900ea2b53169a00406fe4f372db4f 100644 GIT binary patch delta 3104 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 delta 1508 zcmezNN#OBwfemcsjE0-p%Uzg-4fPW9^72dbl2bKYib@L-b5sl`$5q{8G~8@ieVCci zaP!}~M3(7!0*t(yOPlYpZw~D?V&2@@_ltKr|5axG$@kVPG;f>JzHJU;`?fhu+qcbO zer7QJ!h1%J?LpU>IT@MEET_lbV7|?0vfc6~^D9OsL$m2Bx0#KYO=7L5e|*I(%ciBT zU}S2xz2-La2}UL}i|KB6nU6DCZ0Ebje3OaU$lPeUTspHfPf>n(v6j9<a!EzB#dg7m z%tp-2Mh0fn-JUQnnO^aUk$3y|C(Nac%!Wo5)AOD&J2IMY-|&oiCKHpP<#g|t%q5JL z+Yh{CZe(P%oPOXXbNBSKubC^Dj4ZahzhOSa!e}x*{yVeSbcK)1o>2DohL6nK85vEc zTYP4YXEfP9<1_PgCXm|=zA=X|noMu~#@r0zJ}6?AoIc?PGuw2H@62Wp&2}J(dEc2s zcuZmyY;A27)ZL3RQ>IV&#w<Bq{0DP9NE)WkhS6mD*&oa+nT-t0rwcH%NN%6>lbMlG z$<QEHhf6_0L4lLAIKQZ**h)cP3(SCqfuYg#Ex(vQn;OL`sHuVFN{f<HVUmUrd-b(a z5=#=Hf=01=1v#bZnR%Wm(;wO}i){b&n|UGg^wmtveD)~HNH=i0!)j*9sppyFrZa{x zOH7~3#3V4i;1v_c^ms;Q_Q|IY2~L;iVv?OM=gsW1m6L^)naSLEy8ttbFe|f>f$4NL z4wh$(CewL2SyZN5++^kv0Yx>)zaSdwc#G+QoGjMU<!3R8O@Af8!Zm#jCyO(q#q<xH zEZWRQre@RSxLBgl_|ntA2(V~Of5pWjk1SIq$WlE$pn-{f`Yvvkx1hLK&&wh<eIE~t z0;9?F`#dbFj26?uLejh}Qq#k3Gb@8_01FlJvWPHSKvJ&cG(Hwq&fv@{P_Ubsfb^I! znoW~u5}E#im4$aYD<6v_3n*l#3kb7F^XMo*{bOQmIo(j0#dLcGAImHjxC%LDQ$xe; zhQcf>m>JC{$DNm-{Odf=^nbZbO4Azz7}>V#in9bUGn#F0kYaITVl>%)T!v*QBX>zg zszOp?a&~H7%4GiuhST@Tv7|{G>nP;pC#GcPr7PGfloXYwYFcwa6Vvpb3(VF`oSf57 zUtrdmzG5Du^K{#b%xWy0oSdA~voA8MP3Mqh<eko!$;dW+<wa&06Hd-rgzn<h#G>Sk zfW)H2++w}r)DjK#g2ePxbsYs25Uptqv*hnZW+_Xs7$|t);+Wb&NgAXbM8lO$54yyx zA_z(R$@zK3B?_fQISRJZd9N_*1fZBvk{?`Bl$n>Vp;?;%vmBzMD79F@Rw1!GF|$M= zEwv;$L!-1P2jP*-GzE>K)MCB-?CA=7nI)u+bQC}_1=j+S%PP*#)6mqMenE#tZ2G-h zj9gqGC3-oT#U)U|3-T=dGGM`y{F20+pw!~hoRVUMEu7P@Tw&IRge~h;X4~l!kC=I; zzr4l7KV5q-GcVXs=IB@?(-;k=^CvRPGa666ctLWzk^;+j7DkipQmQOx*cnZyKi6T2 pW;B`ZtjkitXtI5eE=w~bE694C>6v#KHKr%%vs~V8Z@^;C2><|D*^>YO diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index b43efeff6ef60e0e41ec035178f8fc9cb8272d0c..7dcb3ae346a366d37877cb93706b8625d3b10301 100644 GIT binary patch delta 3842 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 OiZF|97rV)v!w3M3GPXwm delta 1399 zcmdmcmcMB$|Aruz<~)}6JQl|7c`QsnrKYdg#VESHRfFj=Bd3#Nuxos1kdMxE#q*59 z)3dag)TYOsXEfOUPK$|;m8&EpRUs)cIXg9Py3Z>{iRtnNOtsS;m6)2QuV2cjJN>;9 z(;`Nd=@XTi)HqcN64O(IGpkak7br7HaTMnlm8k0|s7!yL%w)@2T9l(;JH1(zNk^tQ zHL)l;BOtLTF}GN+BtN*MC^IizL$fx)Iwo&=oGO!?d}&dRrgco7f`WoVW}1RVQEIVX zezvAUHAF~Zx|AA|yuE@#QGR)`f~`VIVo9Q2PG)flR0~AHIlnZo1SVUOUy_&;lv-Sx zQ&MallLs<qx~UqI(ezX`CWq-a3K%8Tp@v1rVl~A8Dq6ce#*oQeV*4Ubrg`;zu8xkb zI=+sMzB(Y*^uPy9!rPNhGTmmIp4Q96KmEdFCbsDboy;85_b*~%o6eQT#I-&90#hRs zlaBB9_m`N=I2awLueivhKYjW|CV}bK!kGA`7vE+&HGRraW|isu;Y_U4*F0sC+}?PH z=?nLCt|iQ3+q+*d`CpsfAj>SmTj}I%;+Lr7?&#>Q<2-%i4<_kpX3Xr{nNBdfGP38o zJ30pGxKB?MXBMCS!IqhIy1xgr^mKzDX4dUpCz*G#GP+K8J<q%rLN!f4=g2HIz5fDp z9;5U0e;1f_7(KUZUu0%xoUVI`Sqdt~;_2w<Jl$_Dv-tLnmzXayvK9DeIO+IKx4*$G zv3<c6=Dn;2*4Cbm?m?i?a0G>mqfVrwW2BD1qoco0s-t5nM8et8(b3x4*U`DcdHTWw z%%am1447G`^Xf92x@0(m{Nt$O>FDSP(&nh+?da$YA%YzpgTV$^dpbIsIziNcrNKb} zjs|Ct3orUJ2@5;wK&-L$baaK9I{m{zX3^;zN|{&{K+y#;6{HGDsksx_3W!or49H<q z>gnigiBKpn33eL7Bu_`@JZBQkNjSu;?hkder=zPe#CS*`g8k<XN@id?TtR^o=;#;- za(BA3j+qlk04xd)ZcrfTfaIqyxWFtb66xq#;tC2@cMt)Nm*DA!JD7y0>$fwrCOCrP z%()C|A2{H__JK_Sdmd!6b*Yo1a}X%ZJrSmWSneQ4I71{r3PD_`H$Ym7oTfjx!6Z7J zr<<9}9^`tkK^aau;2;BA<mu?>3DV^ZNh}~mATHQlu2oJTOCSkideBj3Lj_0cNJnRL zCs4@vg48*K(uqGfv_V|=>1&TN%j!CU1U%u+az#}Hb)YrKG43TU(<d%s7M=e7Clf1+ zr=zpi^n_Susp&a8n7O9=-eBhB0U7IP4GJdLwCRltnB}+I-)2tZWGeEVzWfPu4@ZGZ U6v#H$=?fn*iEa;g%ACsx0J9#Xpa1{> diff --git a/sveltejs/src/ProjectBrowser.svelte b/sveltejs/src/ProjectBrowser.svelte index 77f62ce98..dcdec7259 100644 --- a/sveltejs/src/ProjectBrowser.svelte +++ b/sveltejs/src/ProjectBrowser.svelte @@ -8,7 +8,8 @@ import { numberFormatter } from './util'; import { updated, installList } from './InstallListProcessor'; import MediaQuery from './MediaQuery.svelte'; - import { BASE_URL, FULL_MODULE_PATH, MAX_SELECTIONS } from './constants'; + import { FULL_MODULE_PATH, MAX_SELECTIONS } from './constants'; + import QueryManager from './QueryManager'; const { Drupal, drupalSettings } = window; const { announce } = Drupal; @@ -62,42 +63,22 @@ let searchComponent; /** - * Load data from the backend. + * Load data from QueryManager. * * @return {Promise<void>} * Empty promise that resolves on content load.* */ async function load() { loading = true; - - // Encode the current filter values as URL parameters. - const searchParams = new URLSearchParams(); - Object.entries($filters).forEach(([key, value]) => { - if (typeof value === 'boolean') { - value = Number(value).toString(); - } - searchParams.set(key, value); - }); - searchParams.set('page', $page); - searchParams.set('limit', $pageSize); - searchParams.set('sort', $sort); - searchParams.set('source', source); - - const url = `${BASE_URL}project-browser/data/project?${searchParams.toString()}`; - - const res = await fetch(url); - if (res.ok) { - data = await res.json(); - rows = data.list; - rowsCount = data.totalResults; - - if (data.error && data.error.length) { - new Drupal.Message().add(data.error, { type: 'error' }); - } - } else { - rows = []; - rowsCount = 0; - } + const result = await QueryManager.load( + $filters, + $page, + $pageSize, + $sort, + source, + ); + rows = result.list; + rowsCount = result.totalResults; loading = false; } diff --git a/sveltejs/src/QueryManager.js b/sveltejs/src/QueryManager.js new file mode 100644 index 000000000..fc437ebca --- /dev/null +++ b/sveltejs/src/QueryManager.js @@ -0,0 +1,82 @@ +import { BASE_URL } from './constants'; + +class QueryManager { + constructor() { + if (!window.QueryManager) { + this.cache = {}; + this.lastQueryParams = null; + window.QueryManager = this; + } + return window.QueryManager; + } + + /** + * Fetch projects from the backend and store them in memory. + * + * @param {Object} filters - The filters to apply in the request. + * @param {Number} page - The current page number. + * @param {Number} pageSize - Number of items per page. + * @param {String} sort - Sorting method. + * @param {String} source - Data source. + * @return {Promise<Object>} - The list of project objects. + */ + async load(filters, page, pageSize, sort, source) { + // Encode the current filter values as URL parameters. + const searchParams = new URLSearchParams(); + Object.entries(filters).forEach(([key, value]) => { + if (typeof value === 'boolean') { + value = Number(value).toString(); + } + searchParams.set(key, value); + }); + searchParams.set('page', page); + searchParams.set('limit', pageSize); + searchParams.set('sort', sort); + searchParams.set('source', source); + + const queryString = searchParams.toString(); + + if ( + this.lastQueryParams === queryString && + Object.keys(this.cache).length > 0 + ) { + return { + list: Object.values(this.cache), + totalResults: Object.keys(this.cache).length, + }; + } + + this.lastQueryParams = queryString; + const res = await fetch( + `${BASE_URL}project-browser/data/project?${queryString}`, + ); + if (!res.ok) { + return { list: [], totalResults: 0 }; + } + + const data = await res.json(); + if (data.error && data.error.length) { + new Drupal.Message().add(data.error, { type: 'error' }); + } + + this.cache = {}; + data.list.forEach((project) => { + this.cache[project.id] = project; + }); + + return data; + } + + /** + * Retrieve a project from the cache. + * + * @param {String} id - The project ID. + * @return {Object|null} - The project object or null if not found. + */ + get(id) { + return this.cache[id] || null; + } +} + +window.QueryManager = new QueryManager(); +export default window.QueryManager; -- GitLab From 6ef796c2878046e4d8bf2d5069736939e760acd6 Mon Sep 17 00:00:00 2001 From: narendra-drupal <87118318+narendra-drupal@users.noreply.github.com> Date: Mon, 24 Feb 2025 17:05:09 +0530 Subject: [PATCH 02/23] Code update --- sveltejs/public/build/bundle.js | Bin 276854 -> 277132 bytes sveltejs/public/build/bundle.js.map | Bin 255677 -> 255652 bytes sveltejs/src/ProjectBrowser.svelte | 6 +++--- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index 82181ef54e9900ea2b53169a00406fe4f372db4f..15f43a01c00144dfd21a20d013ebf3cf64d177ee 100644 GIT binary patch delta 944 zcmezNNucMeKtl^-3)2>6i<^ul+bwT0zhY!EG@Gt+o7srjB-VQR$5+g<Y+CvXMy6)l zYi=`7WMZ_~&UcUbCKI!fxzTjFbY^LuqWtn=Eq#ULl8R`H?Sc=PZ!%A>_{7M&{reN< zQbuM&Ba7*I&zKz<&9`rO#>~&mXu18sOXfyKM$733UNU!YKl_^5hLzD|di-~0vFQpQ znLVNG?F}E9w=*)DOt<*V9M5R7ea2_z=}aJ}8GK_7VKkZE`i;36#C=f2EIEC`4`#OM z8sC}CAe!w!67#+@hwzxhD%jfEDyX{`Wu{D@@Qqn=y7&*~c#t$qpADnQ_Om~jk1;A4 z8pP^wDJUo?aB>#s7nKxSDd=l~8Bos|8cpBwi}|yuQLKWR8d$EhC^;1-X$Y}NUn?cC zBoQiT6suQ|Q<|Qc=b1A7p$)Uh_D{c=7cx&@&BV-SkD`op1E)KzW|o|Ko;hwhV+gav z^tnt-0@Dj#F>y?fXJlrdeEN{!ba^f&+39lL%syK=S(rH)O{Vj5vZzeAxXH{T0*Wz^ z??5!vsTR`%Ia#cy%g<sGoBm3Gg=_j6P8MfIi|HRYS+tprOwFdtaj`_9@ujDK5n$1n z{)&r59$BVJkfnNhKm!x|^j+L6Z$V+bo|i>z`aT{O1xAzU_jy=U87-!Rg`{~|q^5`6 zW>yB<02V6bWf5VvfFwN0X?!fKoWYq@pg=Y=0qHSeG@B;RBr^R2D+}**Rz4O<7Em}& z7Z7HV=Fw4r`p3lBa=M`~i|O_XK9*T5a20aQriO;w4TV`&Ff*D@jyo?u`PX@#>Hl(> zl%_WbFtTme6=w-zW;EO0AjRUw#AvepxC~1Slc2GVLQZ~SN@iZVf~`VHQEBS*`4)^~ z>_$2YV4FSVS=_-Q)9>A4<l+R0>E&b=mq5fW$g}W6?0+iHqRePK`Qio1?MezP-&q(< zwo9q9oMC4)o&H>hC7RJ>y0b1z1*6IKJ-RH-jI1DYbf#zCWz?9SpwDu7yS)L6IVS*a C$0t$% delta 828 zcmeDAD)8--Ktl^-3)2>6i<^wb+bwT0zhY#xn67%8*=YL5JVue}etaza%!Wo5+iPz# zPh?^=-_C!J`6d&yp|RO?xpZb}=A!)a=^y?vicFVeW)hfw;Tbc>^ek3J?(Gf_nO8G2 znr(mcgt?KC$;@PW!82w@X0uq$>4JgGGTSdaWB$m*XffU8HFNiL-&f4v7%jHDyk<Vm z%4j^D=Oc4Gqw)5fkIZKo8I7k~eFk%9eP*7{#A2bKrZ#=wH)iSSf!~<T7>%cQd}D5A zw3z;&h*@&F{zqmTUgKB=TU%QNb)U@Q67}gH6PaYE^WA0^h3M4#!7M%f;CHajAKy`Q zvQ3}!gV~G^rro_LGeuo<dgDJvsp%UySS%<1dF?vg;R!R__PU?U(u|Cj(`Wx;mS;4a zzWW#RXC_1Q>Fa+pi;5XQEZ5gcNi0d!(pN|>sfac--+uEq^M2;(ou16x{Af~OZA#d+ z#p)I0l%{9qd8TM8Oux&>q_~}vk%gU;(RjKb7mEs`(R4E|7CA=q>7iUK){LgpCvveQ zF`7^3<6+STMcDM!+$_@5ZMj+O(FBndZMWfJ$p=Mv0w0Umbo1NH3XoXmyUolo{XZ`Y z7o*8^u#^HHixi{LbOUx4c{VM51w$hXkhm73IY>yFPo*F+JvBJ9DpgBg!Pp2SB{uyO zAB!BL=`?vJk?HgJSa_#%@v}%yR~BKB<knFr$}cb0(pNAt-0sWIa)AXTEW>PMYC7GI zpT%^0xd=-!^YjVQEG&}~&&zD*l3;0LW;ESCQJTe#iP3ob4Ox~LCN3i#g`E7vl+3*J z=@XO~#n_5ci%WA#rY9+|xKCeT!6+(V4zWhTRskw?R)IwertG%@i!!6p<ck+1w_7N( zd}o=i|CW(;yNMdh33f)4>3?-uq8W{+N9wUuFdA>aq{q_CI6cvT<qHcaTDSWeve<J1 E09gb5LI3~& diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index 7dcb3ae346a366d37877cb93706b8625d3b10301..8dde87344fced52a12fe4d4a46a027d55e278aa7 100644 GIT binary patch delta 248 zcmdmcmVe1v{)QIDEldjxSW*&85~r^*V5)<#3=Ekhz^ryZL#FM1hRi>wv*)@yItJ;u zPfrwQ7N7pXmYH?BzX!AQbb}yf*6m#<nRl@=x=wdJ&%72wHBCR~$SgIz{{nL!qx1BC z7npSzJ-2ILWM*cZu6v1D3M$6p>FDS@-ES_l`1XyLnAfv2rc7^Kz$^t4_MBdDi&>7z z)M@&JTg>Lu58YxGoBsL+6W4T}Zf36OZ*DPjYGpX-1Uotg>$p2Qy6bp4I(q6jJ32b+ dL^?V~f>@4V!rIf(*?fBA8zz(O!MB;SxB-^LQ=R|- delta 301 zcmZ2-mVfVA{)QIDEldjxCaXW-pT69HsamBdwYW5=L@y_^xWqapPeDOJp(ww+*g3y6 zuSCIC0V3P(WXQDL$&mTybk=NlN5`P)iJzIIrrUck%TG^u&cwRC?j-XrRz~OPmgkw* zLa3(cdcn+M(?2|7Vwv82fjN)Sb-L{(W*tWF?eZ6ynHi@mUt*SoiV1i-Iy&nFIywgG zxH>vI>I6@p_=-t*djA||sqHP7nAfu#1VJQxL9)J%j=nnXj*h`PAg;eovZG_9jx)#v zPe(^j9cM@90_W*Px0ubjt&5!;U4wK$!qeAlF-uJE=VE4OD|T{p&T*O^zk^wH`sZ8B dLX7&;?HHM*r%!mpBsqQmK4#YK_P3c+xdC%pW2^uG diff --git a/sveltejs/src/ProjectBrowser.svelte b/sveltejs/src/ProjectBrowser.svelte index dcdec7259..b7f3e6a62 100644 --- a/sveltejs/src/ProjectBrowser.svelte +++ b/sveltejs/src/ProjectBrowser.svelte @@ -70,15 +70,15 @@ */ async function load() { loading = true; - const result = await QueryManager.load( + data = await QueryManager.load( $filters, $page, $pageSize, $sort, source, ); - rows = result.list; - rowsCount = result.totalResults; + rows = data.list; + rowsCount = data.totalResults; loading = false; } -- GitLab From a036580085ca8620e6bdf346a6f97b5d15828732 Mon Sep 17 00:00:00 2001 From: narendra-drupal <87118318+narendra-drupal@users.noreply.github.com> Date: Mon, 24 Feb 2025 17:13:39 +0530 Subject: [PATCH 03/23] lint --- sveltejs/public/build/bundle.js | Bin 277132 -> 277132 bytes sveltejs/public/build/bundle.js.map | Bin 255652 -> 255609 bytes sveltejs/src/ProjectBrowser.svelte | 8 +------- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index 15f43a01c00144dfd21a20d013ebf3cf64d177ee..c4192ca04f12c004538082fe685a414f770236ec 100644 GIT binary patch delta 90 zcmeDAD$w&)prM7ag=q_O^bIBxlkKrLm|wCm8gF;M%lw~>(Qv!_6J};sW<yJp?Y=LW we={)}Z})t|Y|6`Mw7vc(^JFd-6ALSo>4n=E#kcFSu>4_RG}x}i$&zFU0N?E%TL1t6 delta 89 zcmeDAD$w&)prM7ag=q_O^bICcgYB_5m|wCmT5fm0%lw~>(R{o66J}=CN#9wv`@LlT v#l&d2-SZ8zDKDeN_WGa9let(-O{`3&7j9z|->%ET@`r`dY`YdGOOhb~0X!dH diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index 8dde87344fced52a12fe4d4a46a027d55e278aa7..e2b867ef1268cac89fd581707e42afbffced0e40 100644 GIT binary patch delta 162 zcmZ2-mjCA&{)QIDElm3jG*!|vb4pT+iggrJ3KG*(p;T~Y6^L1!UsR%_P@G>{l$_cw zY{;}-*pPYqG{)TRr;jst@h}<sZV$i0EWsw|=&Tdy=oqNu>gedI<L&6^J-ufRla#!# zqoc2mv!kOUg0L=ja&)e6)$w$6^qhV_oLOReKNmCm_8YgDw{S8UdvEW1!YsxF0C99Q AhX4Qo delta 195 zcmex)hJVRf{)QIDElm3jLSph1z(6G}Gp8iAs8|OsP>`6OipmSltU?kg&Mzu~%NOUD z7A3<Z+g}(kZGU0FynPyD<@N=~nY(zHGW@pdTw#`A({?s>(g}2Q4AgOTbPm$-c69Ux rv3zub9UX%~tPq6QbOR%1QPyH7N7tb3owt~8a5822Y)^i|EXD)?0dYF` diff --git a/sveltejs/src/ProjectBrowser.svelte b/sveltejs/src/ProjectBrowser.svelte index b7f3e6a62..c6e2fece2 100644 --- a/sveltejs/src/ProjectBrowser.svelte +++ b/sveltejs/src/ProjectBrowser.svelte @@ -70,13 +70,7 @@ */ async function load() { loading = true; - data = await QueryManager.load( - $filters, - $page, - $pageSize, - $sort, - source, - ); + data = await QueryManager.load($filters, $page, $pageSize, $sort, source); rows = data.list; rowsCount = data.totalResults; loading = false; -- GitLab From fe1bb6585e4038bed400538369bed9a2b9008711 Mon Sep 17 00:00:00 2001 From: narendra-drupal <87118318+narendra-drupal@users.noreply.github.com> Date: Tue, 25 Feb 2025 10:04:24 +0530 Subject: [PATCH 04/23] Feedback addressed --- sveltejs/public/build/bundle.js | Bin 277132 -> 277781 bytes sveltejs/public/build/bundle.js.map | Bin 255609 -> 256236 bytes sveltejs/src/QueryManager.js | 26 +++++++++++++++++++------- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index c4192ca04f12c004538082fe685a414f770236ec..be176a978374720459fd549681b4ebbd9d1d13fc 100644 GIT binary patch delta 796 zcmeDADlqkzKtl^-3)2?nIcMwjwY0btKtM|&I5RIjC$%I$Pa!!cvA9^FD7CmCKd(45 zDJNASEx$-1Ewv;$BQq~uAu%sSp(Hi8AipTFC^M%Lraw6mB3)3FpOu<iqL7kUlBkeY zl%K0ml98&Al$e~InwO%d;8~)OnOl&Pnwy$eQe3Z)s8E)llUS0Old7YToSIisl$euQ zm70QXXl`n9Mq*xOajuR+YF=?^5y%}S8Hptdi8(n6nR&$}iFwJX#R~apV9NraPH-y9 zFE36lQpnF!NQ9YK4ECl%L1KEULUDaYVo|C>qCzsrqs1kuc_j+P`K3k4sbG_eN=q`p zUPtzl9@HQxr4W*lSqu%af}+$ikgpVqQd3IvQWEn@6dVIQ6_OKka*7p_Diw-L^1%+z z$xlp4O;IQ)%C857RIx&4o<eSFZhjFwEJ5K73bmBdqQsKS{5-I8GEyO~&dE$mO|DGN zN!3&EOjAe%nFBILAtSL^Au*>YH8G`9At^OAPoXHaq$o4BEHy<3=Ge><h2+FMg``x4 z#N_1E;^O+$6otg}#LPT}vdlz<z|z#BO5eo1#Prl6z4X))jm#8Hh4RdjjQr9Pg}l_% z6mY2LD-@*`mZlb$zzhXB6c)WuF({=Fl98$aN*AdL1*t{FnZ+f=3i)|CmEb@HX|qi( zEh++qIwU$$i;F>?1tl<Wpr$4krKTv97Ubtad|s3a3OZN}=ue-1mf2?dm9xx>)AcVi zvro@C$1FYBlZ9vc*R#x$(~n$Y7Mreij+uYD`8j5#?d|87(>R365{pp0sbV<oI<xq8 J*Xzvg$pDB75X}Gp delta 149 zcmbRGOQ7egKtl^-3)2?nIcKLwUSKw7RGdEH0<+?D-y6(a(_da-=AT}5m04ms-$iEr z>CzXORVI6~@JvqNVV}O^0<-k=={K0gwufG14&cx%&&*57FV_n!O)aYQP0UM7Pc2fg eRme*%S3nlh(6pYu;3l(_Fc#(8&)j78Oa=hK%sAZu diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index e2b867ef1268cac89fd581707e42afbffced0e40..2651523b4eb321ed083b3ff0ba850423578bd6c9 100644 GIT binary patch delta 954 zcmex)hX2h){)QIDEliW->-Dv?V)7KU6oNDJ(sNQv^79mua}tY-6^c@e3-a@dGm~;s z71HvH6w*>lk~1>%(iIZ(QWQ#3a|`l|5{oi(D#4nQ6CrX1Mfq8&$t4OYi6w~&X+`<D z3MCn-3Q38{*{OLcdJ3K;3YobDIjOm+c_qaPi3(-;IrWJpnK`LC3dyN?B}IujnN_JN zC?@8nCTAq(Wfte^D5T~UmllCsP?C{YqL7%AqmY?bT#}fVoLa1qp9Z!m0BXNeQGR)G zYLP;Io<btnxMHyH6bcg4Qx%Fc5{pt55*3m`-m5PzNzE%!D9$e}N=^kEQ&d`#0roMn zXY@d(fUrVHMrJWIhzg2Q%fLP<N=+%vOG(TtQE&|KR7g(D$thMys#GX0$p^bPCqFSI zHAMkxUa>-Eo<eSFZhjFc?CQ0^c7cK^rL-uqBr`t`?23$3h+}gy(^8WwlXFt_6g<-u z5@CiYWF!_VB<2*QCZ<#>B&DY2DHNra6lJECrKaeBotaspkeryOkd&&Bn4FwiT%4Ms zkeHsBnWs>enWzw0np#xpo0ylFo?2g|m!4Xpk(r{YP@Y+mkzZP(ke8a80uJVUg`(8L z($wM-u!$fi!eSC60Ky6(8L0}O$WK)$NG&SPEG{Wl$j{5Eg!?JEw5SLazL0=VEiMN6 z50rwyftZ?DB$=9`P+E|m2k~uDDk!YLfvrD%f;^M4RC#7zN`AQ>l8cKp6{=(M6cnaA zDKIIsDk#*(<W0XP&m=YdmpqfwbU6hk`R%C+Ogq?>QY#Abi%JwyQqvMkb3ooINA`G; zhNkuOi{*?`?X4P2+gml5Ri;fBtYp%iu29J>p9{iEaA&ea!kszK)K*I?j%c&N`8f zj*&X9j*hO@o{rAuPC8&2e@91u2n()edd_}k>FM#QOkC6N9A(y=Zn~dYd^&p<6YuoM z{me4k7w>1D!!GIQ=<KLd>E!5`s^jVC=!r0Vy5e&tF}6x4=Ta}<=@*VOi%kEQ&n&wA J^l|2Oasa1`OzHpt delta 325 zcmaEJk^koz{)QIDEliW-r)wxN88RwPk5pokn?7HONo@KvH71Gar<Is^re9TJQk=Zi zMPPcJ5|hOCZ%Rx%*e%O5^HTE5^#V&%iz<B+^Agijixg}X@>0tckcBiftz+_1D+=<9 zN)%F3(-KQ_N~YH;F^NyVT+S%b{z{8!`ztMGm1)!c4>FrhR}W+27m0LqjMQ;;bab`$ zbab|K((!e4^qqd;AhQyC36zt6j9GL#-yvpR_8e!3w9+AFCC04jjgy$frZ0?T65j4` zh<O3KyQibGBUpisv!kQ44%lROM@M%ZPe(^j2p41#Sk~In(YeZ5r_#yMF%?bE^v0J= cV!V}3&bi*+I$-_I(?70dR@iQHig|+^07N8c!2kdN diff --git a/sveltejs/src/QueryManager.js b/sveltejs/src/QueryManager.js index fc437ebca..bc411e9f9 100644 --- a/sveltejs/src/QueryManager.js +++ b/sveltejs/src/QueryManager.js @@ -1,13 +1,26 @@ import { BASE_URL } from './constants'; +/** + * Singleton class responsible for fetching and temporarily + * caching project data from the backend. It implements a volatile, centralized + * caching mechanism, ensuring that all instances of the Project Browser on a + * single page share a consistent source of truth for project data. + * + * This class prevents redundant API calls by storing loaded projects in memory + * for the duration of the page lifecycle. If a project has already been retrieved, + * it can be accessed again via QueryManager.get(id) without needing to request + * the backend. + * + * The cache persists only for the current page session and is cleared upon page reload. + */ class QueryManager { constructor() { - if (!window.QueryManager) { - this.cache = {}; - this.lastQueryParams = null; - window.QueryManager = this; + if (window.QueryManager) { + return window.QueryManager; } - return window.QueryManager; + this.cache = {}; + this.lastQueryParams = null; + window.QueryManager = this; } /** @@ -78,5 +91,4 @@ class QueryManager { } } -window.QueryManager = new QueryManager(); -export default window.QueryManager; +export default new QueryManager(); -- GitLab From 4b3b51f3be53f203e30543af0a5c683a64186937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Tue, 25 Feb 2025 15:47:58 -0500 Subject: [PATCH 05/23] Don't be a singleton --- sveltejs/public/build/bundle.js | Bin 277781 -> 277693 bytes sveltejs/public/build/bundle.js.map | Bin 256236 -> 256131 bytes sveltejs/src/ProjectBrowser.svelte | 4 +++- sveltejs/src/QueryManager.js | 32 +++++++++++++--------------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index be176a978374720459fd549681b4ebbd9d1d13fc..849bb486e71e692d87a9fb095b95e7a344ff330b 100644 GIT binary patch delta 608 zcmbRGOJMI$frb{w7N#xC%g-u!B<7{$q!ufrrIsXTWagzSB<7_kl%(bs<QF9tW#&{) ze|DBxefo>@%z{jr#nT1OF)L5EI>)TY&ZPhXTGOM?F>6ilImavy6WPA{9CH|>OipH5 zYOz9Qo<eSFZhlcE*x1Zsg`(8r)RO5|7np;l|G2=cJbn5NX0h#|7n!qJxbpLgONvU9 zOY)1R@4Lzzfy@`V#%zkj-|l{m`2-`9z;xal%x*~h?SVI#^-&bm-DFl54=hbBs`O3F zOH5BK(o0TE&PY|TRj96=e�gX-4zumC4MqY?iSKwhEb(FMQNsHZn1r{_Ga>UncX| z=?D86HMjHKVOC^fG~aG;k9j^Lqv`Yu_nE&inos+}=&+sjA@eC#M&s=lo-wnsGMa6_ z_L})O6Qk+&OYfLXd6`YDOr{rZV-(-6_=mZdiP3C&p(CTnwD-)c+fV#szR$vB7CYT> zHM7QK{Syk)ycs2@Kk#7|21!q63t<+X9>>fqKYcbMi{Ny5E+*OOb-v8J+xuBqc$ipB z4PXXuV`KTl!f3R8GdGJn8=I-Axq+$KHX)XaOuS}R3b~0T$r-7|3bmTkH;AyvPX8^! z!pCZCWNv1$ollhI0V7oX_Ag>A?JNp~NTH-)tB{vku7DhH8q*zSF-mN2m1fCj=RsDx Kok^KRSPB5`#?>kS delta 626 zcmdn{Q()>Zfrb{w7N#xC%g@ROXXd5nq?Y98DJ17478ffNr4|?D=M`rr<)kX4<rhs~ zca~X_ol5}(w5A_F%dF0<;8`-A@f@=<TvX*8v*L80bIe+Bk+O5l^4sT}V-91qEJ!UX z&MYn|R>;rGsRWs*P?C|VkX%|+l$uwfP>`6Os!*I-T%4Jor;wPJqL5jvkerj6Sd^MF zo$ms3kbP#Ff<}2}UP^wsUSMfzQKfHUUSfJ`k)}d5#7!V~DHNralosVFU{N>Okwt{1 zBqOtUdgFCw(dh{nn3ZJ|uqjlqRRAmFzrifGedYz`Y?kQ)SDC}67hPpG-oEiF^D)Nh z&DWV-wqL%^tjjo^=O(kNdRbzTLLicxR16ht74lNcQ3N$Kr+eIFKFw%0y)v0umdzqo z!B!!2@`aBY%tj_A)01v7|7A4Wo^YGln~BkE`<lDV^BEb9r~BMz{=#Us-SGi47aODD zcAsa=tgMVC+k;*+|7Kz|-tPB~*_4;r$jW4T;WkF`?Q?%K_cAe>PA_z16xnY7kNFA9 z^u$y~sp)n;%<9t@)G%>O|L?^pHT{7Pv+#8Lf6VOL7cjGwGqIRhzzi^AWBJ3vXs}(M zo5h`t&B)xs)W~9cjUdZKkg0wmEat3+X2upK(;fL(s<*R>vZOM?6cvcEw6je2o5jer UU0<3dpPdgiJhrb=ViA@C0G?~nCIA2c diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index 2651523b4eb321ed083b3ff0ba850423578bd6c9..a4780a86320d09caa8500b21ab15460611d604a2 100644 GIT binary patch delta 748 zcmaEJk-zyOe?tr77N%M93Lc4hDLJXd3Tde&$r+h>=?aN?DGDX2xdr(}iA9+?mD8`u zGpX~&<SA%@utG>iX7MxyCgths3QUSDAff3V3QStl%M_U8!QAar6_^|uWpXmhQi~Nb z^AvJZbMuQT!KP*wD-@*`r<P3TS7LITzEz1yVf!~FCSw+^{Ji3lqSEA&{G#dV>P-H~ z{1SC0BP9OzCF)EI8Ic60J8LpIAn~_1YBC9+DCpN>lD7yfO)aYQP0UM7Pc70*PE5{7 zRj^g4uC<QIi^)r^D9A4=QAkNmODxSPQ9xE|J^f-iqty0ST1=(PiiJpKDA+3GrIw?p z)zI7?q{noCg|TqEurbp*iS`TLOxrJbGuM|+Z>VNso$h~-*>rm8F=qbhe216?INco` z-K`xRojs-(Rx=qvloXs~X4~F-i1|MgOSq$B`1HnfCei7$k1`7}6}U`af0Wr>5+tnS z?C9vM<L&6^4Q5$;Iy&dMOlLjDtcTFW<?HC^3zErj-R^UY*^r43Rp)faNG9RwIXz4q z(<km^5}Cf?1YGxhCei8k%a~ZFE0i&@PJgh3iFNvih0J_xAk*?(rVE^8o(!>P?n!1H z6H8|uuunW4ojoAVaddQc0jYJ=iF9;~1dBl&3sR@!>geb?z40ZJ7+<B6vzb@A6Ii>B z^Yo8vnH9F%oMP_dVJw<{`Wo{;Ik0wrM@N5%L9ULDjv&(<tvwxG^_{lwyUzTKg%vC} fU2!Fo==S}0m=|*~m$^hum%q*|wY~m1b1owQ;p6x! delta 831 zcmZp^$p7Xde?tr77N%M9^1+#T={cz-`FRS-If=!^3Pq{K1^Ic!nMpaR3TgR8(-+D! zNwUP`DQHdKCC{YJtl(KP{hd6MGDJvFfk|<?nF5m*gqxtiB)`2`fyt54vLLmnIJ3B< zSRp?zrxIk4LP<udLUL(QQEFa^LP27BszPyUadBpTo<d??ib7_wLUK-OVo_?!^aqMe zZc&+O3L52^c`5njdV!^>MU}pZd5P(%MVboLF?kALP?TCyT9l`NMUgc`NiBpb$;d3$ zOHNGANL8>^sIIl1uAs!ECZm8&qk^phNcDC{B_<1&>8)x^zSHliF&S)^QfHdaIQ^&w zlihZHO(s6Z>6%(hvP!8H1^GoK3Mr{+iKRIu3VEsJ3dl~?(6pX@v7AwA`wT6nQs(W} zdQ6*H7z4MzH)2{R!Kb5yth9ZOH`Dex-pmc9(+jGZ*rux=WY)Lwb#(OAadvcc)`@g< zjMQ;;bab`$baXa%(gDl(J39J9SO_&B7D!Fj^oDdM>FNBdm{_O#FJ$JOu3yQ-I^8y! ziFbO~L1y{sHa$#SoFK(uqo+5{VG`!ichYfpbac0NbaeKZ&UlSUXL>?46ZiBBCz#o` z?>@-<pK1C99cI?)_Ek)r(@Qro@w0&}s&JnE{0Or<Sc(f|AV{#xdAj>iW<9VVM1Aj3 zW=^I8m+do-G8-~sb=LRe%(m05s+hQ@zuw5iHNB>qnPd9@OeS`Ius4xRE;zwF8KO}9 zB(sb`jx)s7o{r8Q;J^a8-vvZC>Qp*8I;QG)Iy!oSy$KG%>47hq#CR*6oJ+lY!8&!E zr*HhtB)@&tN#=eY#=PlP*O>opPrS~2n}yk5CwaTw9p>F!%tbCy)BCP5OKlf>&Ya5# E0C^G&_5c6? diff --git a/sveltejs/src/ProjectBrowser.svelte b/sveltejs/src/ProjectBrowser.svelte index c6e2fece2..c845f4bf4 100644 --- a/sveltejs/src/ProjectBrowser.svelte +++ b/sveltejs/src/ProjectBrowser.svelte @@ -24,6 +24,8 @@ sortBy, } = drupalSettings.project_browser.instances[id]; + const queryManager = new QueryManager(); + const filters = writable({}); Object.entries(filterDefinitions).forEach(([key, definition]) => { $filters[key] = definition.value; @@ -70,7 +72,7 @@ */ async function load() { loading = true; - data = await QueryManager.load($filters, $page, $pageSize, $sort, source); + data = await queryManager.load($filters, $page, $pageSize, $sort, source); rows = data.list; rowsCount = data.totalResults; loading = false; diff --git a/sveltejs/src/QueryManager.js b/sveltejs/src/QueryManager.js index bc411e9f9..be0388108 100644 --- a/sveltejs/src/QueryManager.js +++ b/sveltejs/src/QueryManager.js @@ -1,26 +1,22 @@ import { BASE_URL } from './constants'; /** - * Singleton class responsible for fetching and temporarily - * caching project data from the backend. It implements a volatile, centralized - * caching mechanism, ensuring that all instances of the Project Browser on a - * single page share a consistent source of truth for project data. + * Handles fetching and temporarily caching project data from the backend. + * + * This implements a volatile, centralized caching mechanism, ensuring that + * all instances of the Project Browser on a single page share a consistent + * source of truth for project data. * * This class prevents redundant API calls by storing loaded projects in memory * for the duration of the page lifecycle. If a project has already been retrieved, * it can be accessed again via QueryManager.get(id) without needing to request * the backend. * - * The cache persists only for the current page session and is cleared upon page reload. + * The cache lives in memory and is reset upon page reload. */ class QueryManager { constructor() { - if (window.QueryManager) { - return window.QueryManager; - } - this.cache = {}; this.lastQueryParams = null; - window.QueryManager = this; } /** @@ -51,11 +47,11 @@ class QueryManager { if ( this.lastQueryParams === queryString && - Object.keys(this.cache).length > 0 + Object.keys(this.constructor.cache).length > 0 ) { return { - list: Object.values(this.cache), - totalResults: Object.keys(this.cache).length, + list: Object.values(this.constructor.cache), + totalResults: Object.keys(this.constructor.cache).length, }; } @@ -72,9 +68,9 @@ class QueryManager { new Drupal.Message().add(data.error, { type: 'error' }); } - this.cache = {}; + this.constructor.cache = {}; data.list.forEach((project) => { - this.cache[project.id] = project; + this.constructor.cache[project.id] = project; }); return data; @@ -87,8 +83,10 @@ class QueryManager { * @return {Object|null} - The project object or null if not found. */ get(id) { - return this.cache[id] || null; + return this.constructor.cache[id] || null; } } -export default new QueryManager(); +QueryManager.cache = {}; + +export default QueryManager; -- GitLab From 2d1a5801dac91e9676b0540b7c60101041daf1cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Tue, 25 Feb 2025 15:53:21 -0500 Subject: [PATCH 06/23] Minor cleanup --- sveltejs/public/build/bundle.js | Bin 277693 -> 277859 bytes sveltejs/public/build/bundle.js.map | Bin 256131 -> 256278 bytes sveltejs/src/QueryManager.js | 11 ++++++----- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index 849bb486e71e692d87a9fb095b95e7a344ff330b..45add54fdd04e9dad3aa0f3a8d6c5fb20e6518bb 100644 GIT binary patch delta 305 zcmdn{Q{eG0frb{w7N#xC8?Oi^=jRodC?qE)XQT#aR;4P~PXBm?S)1RQi%S6tGSd_^ zrXRS%tj>p|%uc~zy5?2pEN-|?9j?iVDdN*Jt~2vZPrbk_wEf#vW^3kpeSL+HjLc$% z%wmO-j8ui<%)IoRRE6UF(xT*4h5R&ylA_X*4287(B89}99EF0S{H)aE5|G-&5`~P! zvQ&kn)YLqMoczR;)D(rZqWoO2F-eKZ*{OLcdSFND>nnJcfGo&PtxQc(NUBswE6vHN zR46P>%(2c)%S=synXBOGqN9+Qm!eRdkyr##3bHk@G_|PGH!&|UJ+(+7Gq1QLF)ul_ MSZ}+|P3DQo08wdcc>n+a delta 146 zcmaF-OJMI$frb{w7N#xC8?UfurYUG}DJV?eca>RA!`4=zur#%(GPtBDGcR31O^r(d z4*Zj{Qj<&cvQsOIHKsqj!7L`Ksh5+QmtK;gV5eXJS2*49DsvVOhGH(5VjZr@i7DdS Q_g-a=X5N1NCUZ|R00r?eJ^%m! diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index a4780a86320d09caa8500b21ab15460611d604a2..673cca975220672e999c0ce9d551833f1487baae 100644 GIT binary patch delta 428 zcmZp^$Up58e?tr77A74v!Q}kB;u3}A#N>?B;LNI21>5Pr)tIz-tz+_H@)Q&l6f)Bk zG^RVKGl@<2Q)3boMp9{~V4$f`9g{ct;d6=UJJgt5IN_#jS5s$FV6NBKR|v_-ELO-Y zRw&6xRVdEPOV3GFD9$e}N={YCPg5u<DlN%SNXsu$NX*GmC@9L$N=+^SsZA_V$Ve<p zRY*!r%~Qz9PfSToQAjJw&jlNkl$e~InwO#%lc%q*;8_APJv+5BHANw*QX#E0C#O=O zurx6zGcD61H3ep<f~Sj)LSkNuLUBf75kx7-%D~dpqDtSyyu|d>B8AMn;*!L?<kVul zb{8$C?Jio(nzN>}RWJ!mUuDJ2HT{<l6W8<&N0>GEogE#WtsNbmvs|q`9i1Jgtzi<; z(@Ax7Ox5vrboAB<cXSNb@pN?b)CqQU4AyaXbadANY0+_YbOh_rcbWe22y+@2NE&2< ezSH&@N13-Vvw}E|(>+fzn=_hhZ#>D&Bo6@4D2y!t delta 276 zcmbPsiNE<Ge?tr77A74vj?6R#jhH+Ih3Q)AOj6Uk)tCg?L0km|h3U3xOxpYkb_xa% z0ZoPKn7qjkpG!<XuEyjt-A;{3i%SP0rJztdeWC`F=yYu*CZX-F>P+vM+TUt1ZGWr9 ztUgQ6+SAe5OUKvI(O1XQ(a}@K+0oHiC)Lq0Rma=W(OW0n(J>sP#LyY6W_m&ZljwB* zT})il-yLNZp1vTIiEa9FZe~6KkfH(?up$uCZ~Dfg%xTjT1DHh6lyiaX0x8LG-L80y pc^mWehbNhhxXrxMopjtC9o==Drze^*i%fqI%FMUj;S}>VIRLH_QWpRK diff --git a/sveltejs/src/QueryManager.js b/sveltejs/src/QueryManager.js index be0388108..5031ac3c7 100644 --- a/sveltejs/src/QueryManager.js +++ b/sveltejs/src/QueryManager.js @@ -45,13 +45,12 @@ class QueryManager { const queryString = searchParams.toString(); - if ( - this.lastQueryParams === queryString && - Object.keys(this.constructor.cache).length > 0 - ) { + const cacheSize = Object.keys(this.constructor.cache).length; + + if (this.lastQueryParams === queryString && cacheSize > 0) { return { list: Object.values(this.constructor.cache), - totalResults: Object.keys(this.constructor.cache).length, + totalResults: cacheSize, }; } @@ -87,6 +86,8 @@ class QueryManager { } } +// This is the single source of truth for all projects that have been loaded from the backend. +// It is keyed by fully qualified project ID, and shared by all QueryManager instances. QueryManager.cache = {}; export default QueryManager; -- GitLab From 07083b1d1075c7d47a6347e269d1eca0cceebe8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Tue, 25 Feb 2025 16:03:16 -0500 Subject: [PATCH 07/23] Alter, do not overwrite, QueryManager cache --- sveltejs/public/build/bundle.js | Bin 277859 -> 277822 bytes sveltejs/public/build/bundle.js.map | Bin 256278 -> 256193 bytes sveltejs/src/QueryManager.js | 1 - 3 files changed, 1 deletion(-) diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index 45add54fdd04e9dad3aa0f3a8d6c5fb20e6518bb..d01e9d4bc98bd8bd156dc8b37b5fbce517f816da 100644 GIT binary patch delta 21 dcmaF-OJLtGfrb{w7N#xC5!biZUT1!m3IKP43V;9r delta 33 pcmdn@OW^S@frb{w7N#xC5!a_TUuPC$Q?OO2uC?Cof1UY#Dggbu4jTXf diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index 673cca975220672e999c0ce9d551833f1487baae..1722684dfd34abf2207ba44804b7186f0cc8fe18 100644 GIT binary patch delta 57 zcmbPsiT~h5{)QIDEldkF+7D|mZ9lBR+&_PN>j~yQMs72&bSE8mM@M%Z=jn-N%p%ht OgfjDOcR0m-O%4E%x)n13 delta 86 zcmX?jk$>7H{)QIDEldkFWJ)qJi}jN8^NLG~N|Q_Si}aEclQU8kY!#|&t=ng7Fm0c$ s!8~>T^fO19`KQl0$;>%DI+BTV`o10}j_qnEm}f9Dnrz>Hl9@>!0L5@1kN^Mx diff --git a/sveltejs/src/QueryManager.js b/sveltejs/src/QueryManager.js index 5031ac3c7..c295679c4 100644 --- a/sveltejs/src/QueryManager.js +++ b/sveltejs/src/QueryManager.js @@ -67,7 +67,6 @@ class QueryManager { new Drupal.Message().add(data.error, { type: 'error' }); } - this.constructor.cache = {}; data.list.forEach((project) => { this.constructor.cache[project.id] = project; }); -- GitLab From cb5315d481701b57036c9fdf4772f94c154e3fb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Tue, 25 Feb 2025 16:25:11 -0500 Subject: [PATCH 08/23] Remove one stupid thing --- sveltejs/public/build/bundle.js | Bin 277822 -> 277678 bytes sveltejs/public/build/bundle.js.map | Bin 256193 -> 256044 bytes sveltejs/src/ProjectBrowser.svelte | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index d01e9d4bc98bd8bd156dc8b37b5fbce517f816da..c9a92266dfb13714f1cf51e6ececb75adf829fbb 100644 GIT binary patch delta 304 zcmdn@OJLnkfrb{w7N#xC4_{AD*u*5iUFHq*Gj2xn=^s59MW$CJG73$9{hgVA`szeR zPG%#E*h#M(CO3SQoBrShGspBfcbWOO@BG26&%$I9JH5`BMRGdxUuIiI!|9HHnWGua zrqBJ$Y|Lml{o-F{8z!UJ>5bEwMW!qNV-}xY=E%rD{Q(mT-*lTpOg!7C{bOFtJl%0M zv&7W%%yHA}{xS1xf5*fk!pvwk{cahf(sorgmYIx9hL+P0u(P;K-_Op%I+<mQ()0_G z%m&+|Iasy{GMY@@dtPz6k0OiAc7G|B4UCLN+hka57#R(x8_Z&qm@XmD!m~Y5mSqa# z^#9T<tkbvdW)|4aCC{S7#ArHQSCJ)w(O`R@B1@wHqv>`5BbJGb)Ag1!a&O;d%<`U* M(RBMNQ<f?00Dh`wX8-^I delta 382 zcmZ4YQ()gOfrb{w7N#xC4`0hDsHrIw=a&{Grz+Um+A5?ZmL%#G<dmjo=6R-UfB2f2 zm50${`bQ5&k?Fs`Gix(h#!h<WFg@Tcv)cB)AIx?vjHc7i{$UPhG@7pZmpPize0t+w zW@AR9>0AFY+b|i&PH&vfEHa(_AG5f=z81t9Eq#ULl8R_U!&tC|`dT0>p@K%SaJ#2J zv|$#R_J)~ly6GV%;qC7Km=`k}fgDSkIny0hGfPfA&m6Zsfr&+dnbCZEBP+{pMkXVJ z>2~ZaE=-28lNq<DPQM_@Y_R<%JIgjfM$_#RBw5}vG8#|cFpE)a`xI#wHAY6G=?1eH zC8pn(W#QR=UxsB0BcsXmjk}pews**}C^0dbO<%3RlE7%Vokx+SQGn5Gd!Hf8L`D`+ UsBRZEW?^AsG}|s^$})u=0DCQa`v3p{ diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index 1722684dfd34abf2207ba44804b7186f0cc8fe18..9aeb065708da3bbb8b36dfae3e4aab2241233af3 100644 GIT binary patch delta 105 zcmX?jk$=qv{)QIDElk}GthF(D3JTNZ#F%v3Z#pn-zv;mIaw40BlVf0o)AYaqX3^<I zGnwUCGo2lS{ifGnVHTYpewq0oOTLq1!1RCsX0ho8Z<#ozE2uJavX(kI2K#NVzrws# LXuIii<{m}>vFju= delta 221 zcmZ2;f&bt|{)QIDElk}GG74&H3dQ-QMaiiOwzjqkDTyVCdIdS9>6v++Dec`3OxwF1 zn58GNdO8MHI88t3!Yn1=?C9vJ<Lv0@2q99Z_xLeO2q%LC+(A;Fj*iYcfsT%W(+g)X zD@=cIiJ65d$9ekGOU&0)JRJj^b=*Np6F~%6Jy!Fyv6!dh>gecbjSNbi9D{S5x4*v3 Nyj5uX{AbL)i~udFL|p&? diff --git a/sveltejs/src/ProjectBrowser.svelte b/sveltejs/src/ProjectBrowser.svelte index c845f4bf4..fd2ae675d 100644 --- a/sveltejs/src/ProjectBrowser.svelte +++ b/sveltejs/src/ProjectBrowser.svelte @@ -193,7 +193,7 @@ <div class="pb-layout__header"> <div class="pb-search-results"> - {#if data && source === data.pluginId} + {#if data} {Drupal.formatPlural( rowsCount, `${numberFormatter.format(1)} Result`, -- GitLab From 69663aa78aea254929865db861457a0713f656fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Tue, 25 Feb 2025 21:53:59 -0500 Subject: [PATCH 09/23] Make the QueryManager class implement the store contract so it can update all subscribers dynamically --- sveltejs/public/build/bundle.js | Bin 277678 -> 278654 bytes sveltejs/public/build/bundle.js.map | Bin 256044 -> 257584 bytes sveltejs/src/ProjectBrowser.svelte | 15 +++++--- sveltejs/src/QueryManager.js | 54 +++++++++++++++++++--------- 4 files changed, 47 insertions(+), 22 deletions(-) diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index c9a92266dfb13714f1cf51e6ececb75adf829fbb..93d66340ab1269f65bef537902861253c70acce3 100644 GIT binary patch delta 2544 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 OFfp2JmojCU!VUnZcr5V% delta 1615 zcmey@Ah_<QKtl^-3)2?n;0r7onhMp^6)!QHZdbX&{D*OS;5FvGOq2J<@(1N)7MEBl z_$OthCYR`yCFYc-7HgDbWESfs=jRod6qP2I<QM5BCnjg4YU*$)Ac2znlEj>#)Z)^d zl42_bu!`W!s#G1W$%!fA)1_`QACXh2R)82E9jl{&s@_1McKf}X%u<ZuwVKvkaD$6d zOG=CK6jBmP60M<vwJ=$IEiI^!f|f#1YDrOMYFVm6qC!DYKFIM3X+`<D3MCn-5VuUv zyuvIa9b8hBnU`LxkeQ;Os}Pcr3RmpuGJV2TW)VM#$7}LRb8>3Yl;nfML?ORO0VJ!C znWm7JU!stfUz(Sq2X}}*l$D-ZqLG<0@uBeaL${d4r^jDr=HCA1HuE+{M)T>F$;`5h zmeUX3W!7UdF`NGJF7s_hlj&#gF)K2e$4)=kXw@;>uCMkYhE={p`U8!?&0PM`3A zS!#N|Fbg}gk*V2qfrreRjON>|9)eVvm`=a-i1|39#q>pwnROV=r+r~`*v|iiIfsea z$lPdp-7{u2W{X(s=>p75lG_hFW0quQG@gFzCG(Q$6`vS+w|Bo{E@fmkG_sh^^oH4y z**sQrx?mu)%=81#m^rp*y<sk55;U|lvobW$RH#<YOasTo^n^`J^3&}ZS=gqFzh_ou zw4A>G19SKE6YrV7F<Nfl|ACpAk;%Yx`;m{#hgcX*rni4%j%PI4{`?#BSw=>a>05t* zxuQRrAAy47qX(nN^mD(M*`_xnGV)B9`^_xLWDz^*mBZwQuX581{xFMz0%GlN<`7<! zSOr^KTLpEW%;FOD$pI%NrmOv7jt5CUC}NhJKK~E14Wr5QJAat%Kw{E=nL`*&rWgEW zZU%8-O4z2~`@w9+XfoaFAM<Kv6DyPHh1(d#r}O`3J_QQtGiEH3)4%*@mSHk9m@3F3 z&uBQ+kR_VYY-%ryF{9zsgDf^oMzPZyr!$KP87SB)=xc!zr<T4#a!EzB(ey$`Mv>_P z@0r=ByE3w9Fq%OG*0Hg$Prv<&S!w%fMiysgUSut?(;ZhcOH4h_95>y8k%ebEZwNEb z^gdRWRZM1JDUHecClsc6GfGZ>;KMAueH|Oi3r0q>>37Q*m8S3HU@2iXfcjv&J0}Yd zBa3CMruB3wE*7cjg<LFZ%ti*WlNq<DPQM_@Y_R=37fT2;qtSF1K9*;UCeyX~SyUK} zrw8z}$T3<>&*x{co-RL&No@K7eimOwi|Kp<EZX4YW+T87g~pej{y>1m9a(UOI7{{P zfCeV^={E#f-hu-Bm=KE@C`3VNmpdmvF(orET}xlVV!E<0i^24dd5j{{6P_`1Ad+>Z zFpCzG1vIhm7iQsMGBcTeR+z<v(QKML6IAtnVHVcu$|5Y1ETB-Ft|!SN&8?$QlwV$~ zrLSOOJUu~#MTO1O)ZD<-Y<rgo%S9$ev*|J7Eat4nM&@P~+h>Tg%wU9Tm18zFG@PC& z!lDe*QX$Erz|3eedGC3}$<^m&CR?9ZWHg@~cV2$-uk$?9|K&0%O>YojWZUj7%VN#Q zXf%DpEJm^IbLCjn7#R(x8_Z&qm_A>Tg=hK;d6vhFX4B_iXOw{md{JQOU^Je-aW}Kb z^ofcr$01JeRbp|TzDb#-n9*dqnhMJTM$_rXRaoMgj19JHsIo*c3L5JuKtoQ!R-vS* zG<EuX3q~<6BOP!)RIpW;K1Yqk2`o7M-YrHhPEerefl67h7>6MwXq=}T+-8<w(zKo~ zsKO#X`SJzv?Jnvp-&q(<w%h2ioMC4)oz7{(63u8bJ=KJzg3)C9JrkC0M$_q8rYzsV UF5Mn!#xjA4(RBMO3zjMD0O6Yc!T<mO diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index 9aeb065708da3bbb8b36dfae3e4aab2241233af3..27b752d087632e7ced1d6082404eca48e3025de4 100644 GIT binary patch delta 2233 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 delta 1029 zcmdmRhkwll{)QIDEle^>EE<{$)zdF1GwDu0YtF>AeTyp7MaJzL)tM$UO@8~FM<^$= zxWr1qKPf9UxkRrlF{d<j`bAA9NfVuzJOwBy$uCLF2}&(4%_%9iQb<lr&PWZ;tV)He zsI`Wut(|^OnMr<nwieS=IfZHkh{@5hItr);7%0?k(`I5}6sy&=j>&^)ElMpZEy`0! zNi0d6e$bprtUf5Uq$o4BEL9;<p`a)q<T!=2qWoNil8jV{3-rJmwIHN|LqTFuVy;4U za7j^SUV5!UW{QHYLP$m`T(hSORD*&8)UIlXpKJ0;b8>3YROefR!$TpzNC70RkeQ~C zmtUfgmS38e0yRe;WOI6IiAH9Mrb0D{qo4pWdHMqtCgttVb(lW0v4A~v(U>W7yR#`1 zBNH2luV6df*__GGK*1L5Up-JbK|`=8zr5HvzcjA|CWjny+gF=2&E)|b*&gV^v^~&; zS-z3W-O<tA+SAcF!)dzgF=mVDzm7B4Z9jj4S(2G0(9tn)x<L%H{IsXctkX@8GfPb0 z(80u_o#&zhlG2HEbd1z-b#!#q33hZ01{tF7q~q`C=&$4K=;#dM2Tgx|n)$|b*AvVF zd|-uO0;F&H#%d;!=>n6PSeT5Rw;wsf+|Fp^?da$Y)(R%99UYwmK!!N#cse?ILJS78 zV5Vm{O@DoySyuz%29QU<8X;VeO(5N22;I~5k26~{SvpS-KhGS>WahPf-FfB$M#kLj z@)w!^urj(%m$}Zo7D6>mf2YkXH9hSHvlpv{lVf1T^aDSc#HI@rGqX;=aD!Qn(Qo?u z8_d^O@|_$5rWd9&i%p*(#w<Af!%b!uh?MAdu3OC87?})xrysh_tj$=o{oQTmTg;r! zj*gDj!H&*e(+}Newq^Bnbj@;}u78kObbHkU<~fYh*9tRpO%Ff9EW!u&s=K3Ou#T^z zqvLd`N6g;S7g#W}2^2dyI#;+tyan>~bj4yOVO~d&-yN+%k{M3Z1>%@RML^*U76lWa wz%X~39(aUVYWw;}%-Nib#?z(WFn4lWhdY<KM1ezX`bJ4+(d~!dFy}G?0QM3~v;Y7A diff --git a/sveltejs/src/ProjectBrowser.svelte b/sveltejs/src/ProjectBrowser.svelte index fd2ae675d..3a9ed182e 100644 --- a/sveltejs/src/ProjectBrowser.svelte +++ b/sveltejs/src/ProjectBrowser.svelte @@ -46,11 +46,17 @@ setContext('mediaQueryValues', mediaQueryValues); let rowsCount = 0; - let data; let rows = []; const pageIndex = 0; // first row const preferredView = writable('Grid'); + // Set up a callback function that will be called whenever project data + // is refreshed or loaded from the backend. + queryManager.subscribe((projects) => { + // `rows` is reactive, so just update it to trigger a re-render. + rows = projects; + }); + let loading = true; let toggleView = 'Grid'; preferredView.subscribe((value) => { @@ -72,9 +78,8 @@ */ async function load() { loading = true; - data = await queryManager.load($filters, $page, $pageSize, $sort, source); - rows = data.list; - rowsCount = data.totalResults; + await queryManager.load($filters, $page, $pageSize, $sort, source); + rowsCount = queryManager.count; loading = false; } @@ -193,7 +198,7 @@ <div class="pb-layout__header"> <div class="pb-search-results"> - {#if data} + {#if rowsCount} {Drupal.formatPlural( rowsCount, `${numberFormatter.format(1)} Result`, diff --git a/sveltejs/src/QueryManager.js b/sveltejs/src/QueryManager.js index c295679c4..ed45a1469 100644 --- a/sveltejs/src/QueryManager.js +++ b/sveltejs/src/QueryManager.js @@ -15,10 +15,35 @@ import { BASE_URL } from './constants'; * The cache lives in memory and is reset upon page reload. */ class QueryManager { - constructor() { + constructor () { + // A list of project IDs that were returned by the last query. These are + // only the project IDs; the most current data for each of them is stored + // in the static cache. + this.list = []; + // The subscribers that are listening for changes in the projects. + this.subscribers = []; + // The total (i.e., not paginated) number of results returned by the most + // recent query. + this.count = 0; + this.lastQueryParams = null; } + subscribe (callback) { + const index = this.subscribers.length; + this.subscribers.push(callback); + + // The store contract requires us to immediately call the new subscriber. + callback( + this.list.map(id => this.constructor.cache[id] || null), + ); + + // The store contract requires us to return an unsubscribe function. + return () => { + this.subscribers.splice(index, 1); + }; + } + /** * Fetch projects from the backend and store them in memory. * @@ -30,6 +55,9 @@ class QueryManager { * @return {Promise<Object>} - The list of project objects. */ async load(filters, page, pageSize, sort, source) { + this.list = []; + this.count = 0; + // Encode the current filter values as URL parameters. const searchParams = new URLSearchParams(); Object.entries(filters).forEach(([key, value]) => { @@ -48,10 +76,7 @@ class QueryManager { const cacheSize = Object.keys(this.constructor.cache).length; if (this.lastQueryParams === queryString && cacheSize > 0) { - return { - list: Object.values(this.constructor.cache), - totalResults: cacheSize, - }; + return; } this.lastQueryParams = queryString; @@ -59,7 +84,7 @@ class QueryManager { `${BASE_URL}project-browser/data/project?${queryString}`, ); if (!res.ok) { - return { list: [], totalResults: 0 }; + return; } const data = await res.json(); @@ -69,19 +94,14 @@ class QueryManager { data.list.forEach((project) => { this.constructor.cache[project.id] = project; + this.list.push(project.id); }); + this.count = data.totalResults; - return data; - } - - /** - * Retrieve a project from the cache. - * - * @param {String} id - The project ID. - * @return {Object|null} - The project object or null if not found. - */ - get(id) { - return this.constructor.cache[id] || null; + // Update all of the subscribers with the data we just loaded. + this.subscribers.forEach((callback) => { + callback(data.list); + }); } } -- GitLab From ce897e19df91b2e20a82b4998e6da550c98b00bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Wed, 26 Feb 2025 00:22:26 -0500 Subject: [PATCH 10/23] Wire up subscribers to the cache --- sveltejs/public/build/bundle.js | Bin 278654 -> 278684 bytes sveltejs/public/build/bundle.js.map | Bin 257584 -> 257897 bytes sveltejs/src/QueryManager.js | 52 ++++++++++++++++------------ 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index 93d66340ab1269f65bef537902861253c70acce3..15bb8a34b665022831d92e776ae89bd9beadb2e2 100644 GIT binary patch delta 650 zcmey@AULN{u%U&qg=q`(+_Te}?l1?4a49G#DCp}elw_nTBqb(ir{<;TO;5YSY+DbL z2`o)5s`O3FOH5BKQpn6JE=kNwPA%30ODE^&6_+R^Cnjg4D%dJ0low@|BqrsgYE;*1 zT62L_q?P6+mt^MWDWs>CxE1B+I)l|}6cpuWr6!jYYbsQOl_@A78Re2#lBiH`3(^u_ zT#{du8ef)}Q<|y)wnx(%s-Y;gq_ikc0cH|ZR6#*6H?cq?GeyDH4&pG7Y0;S}v6=`K zX_+}CsYM!@C8@a}WhIpbsrhLN5U#DQf_i>ZRw`?9iMpos_6cX1?HRYfI?wFDl&Y_< z5T22mms*xu1P(8-dlZs067$kii**#rQx(b+^GXy-@)gqZi^>y=QWQ!u5=#{F^FTuR zr9}$GrAfufMVU#dMbPjA1wPmmJ-F25jTXXMkidX>O#vJf@X*sJ$;d3$%gHP*(X{58 ze&;f?%=Fc_nUyA2M|1HZ0u~fZlN-xKm})h*%U@#VViZS?0VFfFmtJOOWu1QT2D8TY zUpJV~GEV=0i#Y@mc6y}+DTyVi8jzp|g`_4Zz(8>}z5f=oF}DIl9Y|*Sja$r0lRL|J bgcKB@aRc@mSndJHf&U+|ZGU=)d1f*Ia~a-I delta 678 zcmbQ!DEO~Iu%U&qg=q`(+_T&L&NCY^)@K$gB<Ca+7b_GLrIw}Ul@u!!rKXhTr6lH+ zC^!aqDkLZ7<P<9;RVoyh<QHY;r7Ps*C#IyPC=?XsXQd{W6f0!rDdeW+<`-3RDS&{M zLRx;2LP<udLP}{-Vo7Fxo<e>am|u{Xo~n?OnU<PdnVgfVr{I~Ukf=}(HAf*Mu~;E7 zrzkZsrBWd&H8oG6D7B<0Gqo%=MF(bZW{E;_VxB@$szPFNa%ypLYKlT)dSYgtLRn^_ zLSSiXQKfHUUSfJ`kzRUgiAH9Mrb2mUNk)EYi9%j#Y6{5hCHV?PsfDGf#U(H^xj`;X zN=(j9%}d#ycY!&GY5L8}%>P{=@*oc>lw@QU>*Zt?m+0jt7HDLqDA?LT#FF##ic5-0 zlS}f8^pX>kGg6~7Q(_frY83KHb8<9wxF#2Li%hS*!mPSo;VSbL78|g71+dEC%&Jrc zTLu3lP#EfEr&bngfUN;5K{irTFDErGy(DA$%bU!`f(mMCNc!y*47SVMVm`|#h@oxz z>^sa#)1TaCj<nO)R|qXgNi0cKNX*HB#YJ&xQgL!oW>RWVF*uOG5tNcxlBiIgs*qJ$ zTmp?|z3E<8n5CxIU1nAi2Bid0NNIr7fdfQy`fZRkbI&pdOrLa~S%^tre>&qiW?NH~ k*igvKD=taQOHM7;1BWU~Oo2l~!B(NV)_VK5JIu?H0S^cA$p8QV diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index 27b752d087632e7ced1d6082404eca48e3025de4..e61aa58662d0e48ccd6de2f4c88b98952378dac5 100644 GIT binary patch delta 1297 zcmdmRhyUe0{)QIDEldUS)6;z!^{4OgW#pJ%A<rZ|{i_aBfM86XzP>_9Myf(mVsdtB zUW(rIFkL3wda!U{X=+iWZ(?3zdTNnEW?pegVqS7;v0hAGa(-TMi9&K>az?6xt%5>% zQD#YEQckKyb*-j#OkPZ0T4`Q#NoIbYLV9Y6TTy<lGgzfYK~a8IYH~@jrb2a0o`M3B zDK3d6i3+wL<r-j9(?RAaC={iZlosVF!1RGMDJbaWCKhO9rYP9jL0kaR8=aXFs|l4( z%giZBEz-y=NzDbRDXA<-%}-N+aBXcB)bo=-j!*~LP`kZPo=J&u`W{6lTana?g8ZTq zg_P8^#L}D+h2)&X;^OVxN=&C2>-F^&!ZT9yQp-|{z+nY;qe5~<VqSV`v5rD{szP~U zUWr0UzCv1lQF&rfib6?7Vu?b29!My^v`C@2G^sebC^IRw2o!t@3JMUX>%qk}G&Dhu z1&0e*8XUq<k1G{J!Ui6c8YLN-#d<lJ#U&tLfc!n(QH4o#`WhW3c@2=+IXR%{L<G1s zNDatba0EdZ)6Xh1{SZfvAtYnBbE`6~VwwI+lSyOxdo3o#>DQE*#J6W_F=aDO=hk8J zQ3MBpUTHx}Vo9n7B+@_;f$+ujrP@q7+#ts!XQYCpra#walGlYh03N$wlRyffj)bbH zjmfK>ezBZUs{Ow%)As+m%<R)yES(LVraK;G7UlGIboAEobaeEbe&8^Zl!&9Fv#FD{ zr=zo@POzh6uui0-W90P0*-XNcS<X7Hj*hN6zK)K*5IJ{8M|U@!)aeHkn54D!ogmWw zj*k91fsT%WAVad8bin3=RO&c8Izo(?Zm^I^YWjo<CJrWZr|E@pOrp~#L^4V8gB5@X zkXB3Q>51o=C8r;_!K5UZs*~*mawkl(XnM*~=AF~epJ3)@0a-9Tuz*QK6r|123F1Jo zD?!efzOfP%p9h#kxEvjw^_@YA9H;*~%Pckh`#EMA9&=}~Yr(|yfCWq<dLR|XPC8(Z zfGh+%93&V5@eD%D(a||z`p47E^3%<aF&~@0FcD&t@bvppOss6yj*iYH&fBYxGaqE0 zKIb&E92Y43Kn7VlZ@+Px*^FtrPB9bL^!U@vT+<(%W#*c$bB>t{6fT_8{koaNrY}0d zEIHjKnu&9|R5}ya^t0!fxtVl4r*AM~mYCik%gibQjv<i$L55~HgMB_-v6x8|WSk67 z5!7oSWzz%KF^Nn!IL9o(STX%$Hj{{S2v|A9n;;c%Z<aYtPfTYL<_^#SMH|RykLitX VnZ(#Dot*PMOP#lGIM3W52LLjfpAP^4 delta 1164 zcmaEPkAK4*{)QIDEldUS)91@GDR1{yWRhj9&n#9*&PgmTRwyV+ElbTSDOM;-O)1Sw zNz5xza18KNNKVYjDOO0TR46XVFUrhISIEgvOi4{qC@9L$N=+^)R>;g#$W6`7FRF~m zQ_xaK%P&$W$w*a5DJ@DY$;{7F$WH_F3lh^)6>>7uQj;r_b5ivbJkt~sL1xs03{l8P zELKR&DN0RDsZ>ZxP0dp%N-ZhMOf5@I(E*#9S)!1fn5U4Gs*sqRoLXF*nxc@Ho|u`Z zP?njf5LlX8ROy?TmzbVfq?evrqLG=RsZgF-l969pqL7!GngVilNxni+YGG+=aS7N! zkQ0*<le1H&Pg7*#o1US>WGe&-24vH=?^I$s%{aYFmFc}(Or8Q5lw@QU>*Zt?m+0jt z7HDLqDA?L5fW?yY^NLG~N|Q_Si}aEclQUAIGgD#}YHAenN^^2FLH<xsP}n{}jcFB& z4Op!LSXFRlRjPumf`1YyAoQ|RD~mO-8>gw4lbV-ak}=&-o5@g6K}`)wzny}?bU7U+ z#qG`7Oihe}VB?XUGJT~kll(MYra%XMeTC41l*E!$g~Xg3SmYF!CKV?aWhSK-6@vo~ z9AhboC5Z~<sR~)8#U;>4*Ne%UuByspq6$hBpy1O0DFTOvrZpshY9Wl;n7rEQd*zuz z1;J*4)TZX8=*8se>rYo!V6qKDi8Y1HyyB9?yyVnky_h`Y2!I5nf~`Vzt#wRZOkQe5 zL4Hw*LP}~{VrfnZiW%0^FP1Y(we#ySZRgixW}nWY?`-Ha{ooBI!|4<1nM9|ruV-T8 zi*$61)Nyunbhh?%bk3XJdW?DJbfXi@4_HC`sObV1n5Cw_k7weV?y!f6b$Z+dCP_|j zM@Mh4mg%{Bm?YUi3e8=nzrM>PzdiIM^G4?BZZ%B&EWVD8zSB1zV3J}9cXSM&Zn%g^ zbh=IqGY5~ewWFhRmMch`<MhOH%%amp&ocANc{)0J>I6GF2J5&xI=X|+*Ku`p1gq0` z*{*k%*^CKdl+-pRsp)2UO#J-*j*k9dl_10NTp+HUaGaTI`uYn@T+_|#nS`dtb~A}j zUwxk09PA^<>9H4>q^3VR#mp}f=;#=z1M&<=x3RMh*u{+%Orp~#9B1Zd^>hRUS7ADn zux$X?O(10+5bGQron1g&M+Bh*4@t0@U>#r&OrQ9cNsO=3$=Sj)-3cP>Jbhy|v-I?U OuT1RQg)TCO%K-o~^nwlm diff --git a/sveltejs/src/QueryManager.js b/sveltejs/src/QueryManager.js index ed45a1469..2c8a0390b 100644 --- a/sveltejs/src/QueryManager.js +++ b/sveltejs/src/QueryManager.js @@ -1,5 +1,18 @@ +import { get, writable } from 'svelte/store'; import { BASE_URL } from './constants'; +// This is the single source of truth for all projects that have been loaded from +// the backend. It is keyed by fully qualified project ID, and shared by all +// QueryManager instances. +const cache = writable({}); + +function getFromCache (projects) { + const cacheData = get(cache); + return projects + .map(id => cacheData[id]) + .filter(item => typeof item === 'object'); +} + /** * Handles fetching and temporarily caching project data from the backend. * @@ -7,14 +20,9 @@ import { BASE_URL } from './constants'; * all instances of the Project Browser on a single page share a consistent * source of truth for project data. * - * This class prevents redundant API calls by storing loaded projects in memory - * for the duration of the page lifecycle. If a project has already been retrieved, - * it can be accessed again via QueryManager.get(id) without needing to request - * the backend. - * * The cache lives in memory and is reset upon page reload. */ -class QueryManager { +export default class { constructor () { // A list of project IDs that were returned by the last query. These are // only the project IDs; the most current data for each of them is stored @@ -26,6 +34,15 @@ class QueryManager { // recent query. this.count = 0; + // Whenever the cache changes, we want to forward that on to our subscribers. + cache.subscribe(() => { + const projects = getFromCache(this.list); + + this.subscribers.forEach((callback) => { + callback(projects); + }); + }); + this.lastQueryParams = null; } @@ -34,9 +51,7 @@ class QueryManager { this.subscribers.push(callback); // The store contract requires us to immediately call the new subscriber. - callback( - this.list.map(id => this.constructor.cache[id] || null), - ); + callback(getFromCache(this.list)); // The store contract requires us to return an unsubscribe function. return () => { @@ -73,9 +88,7 @@ class QueryManager { const queryString = searchParams.toString(); - const cacheSize = Object.keys(this.constructor.cache).length; - - if (this.lastQueryParams === queryString && cacheSize > 0) { + if (this.lastQueryParams === queryString) { return; } @@ -93,20 +106,15 @@ class QueryManager { } data.list.forEach((project) => { - this.constructor.cache[project.id] = project; this.list.push(project.id); }); this.count = data.totalResults; - // Update all of the subscribers with the data we just loaded. - this.subscribers.forEach((callback) => { - callback(data.list); + cache.update((cacheData) => { + data.list.forEach((project) => { + cacheData[project.id] = project; + }); + return cacheData; }); } } - -// This is the single source of truth for all projects that have been loaded from the backend. -// It is keyed by fully qualified project ID, and shared by all QueryManager instances. -QueryManager.cache = {}; - -export default QueryManager; -- GitLab From a95cf1a19c639aee159a0593305b93e3d3ad3a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Wed, 26 Feb 2025 07:47:14 -0500 Subject: [PATCH 11/23] Add updateProjectsInCache --- sveltejs/public/build/bundle.js | Bin 278684 -> 278768 bytes sveltejs/public/build/bundle.js.map | Bin 257897 -> 258059 bytes sveltejs/src/QueryManager.js | 18 +++++++++++------- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index 15bb8a34b665022831d92e776ae89bd9beadb2e2..ae4fa03654677c45191f17a8e7f4a2e8ebf00aac 100644 GIT binary patch delta 152 zcmbQ!DEOgKu%U&qg=q`3@A=8=S~(}5GhverD9X=DO)e?+%yUjm&PY|zD1ZuRDpYe# zH^0NI!vPbSo_B{?Y5Jr)%$k!s%Y-?TLF!!+OA;rqYZ2e>d!G3Nk49-hN@7VWnwc6Y di6x18Ihn;Jn$}zjU@-aPBa!XQcbR7<0|0f6IiUam delta 55 zcmey+C^)B4u%U&qg=q`3@A>W87nncrOs_xBtTuh>9cJC>XU{WhPi`;co6dBBS&CUf Mp?15~UFL1c0ECeiH2?qr diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index e61aa58662d0e48ccd6de2f4c88b98952378dac5..d18671ad2b11dfb6b42bef01256ef7fbbe6c777e 100644 GIT binary patch delta 316 zcmaEPkH7l?e?tr77A7x6$+XhE<dV$%JcZJNl*E$MfTH}Y)Z~(4&%EjD6qv-P2k9~? zaTGuWr$5wTlApdnmr0GaR?|8rZ~A#%CP~KH?T(5}0zC5Q1~?}sXQXPRB$g!V<zyC@ zw7=G6+WuOXId;bM^Lv@t*b1E-9SfbNH_m1fW;b-!admWbojy^7S$Mkt8zy$iU`NMb z9dAcRZ);CSXG<p?Pe(^joj^y&!086(nU&acoOOI19et<!USKw4Dsi4(n9d|To#QAI z4?kF=v!kOkNMn)H^o5U^gs1mjV&dQKevEk`=XAYBCb8)gjx+O5e^A84!tJc%?&#<a oQtL5&;#(#$zDg(Obk9<Vu#WTejn&N3(*wRTv2Pc;$Q&*Q0K=YWb^rhX delta 236 zcmeBv!2j|de?tr77A7yn?Se{7>^##e6`7Q$&(&qpnto7`NrO{Cp;ps6CQm_O`a4A? zNoECw+IDk2rtRi>%&{}J&pOV$fOGon6U<!GV|balraM?Nvx)>eItJ@_J34w>dpbI2 zIP3U2I{Hq3|Aa|&y8byP)@c`+IHym$z|6-}<g63u=oqL2Qa{~sDwD``fmSBA>HQH* ztkZp$F>|tk^jA1f|5(T*EFA(iM#taL(I2cq$Jx=*8Kj`hX}aT4CQ(jzM@M%M-(&j0 bH%wyel}^t2o~6#y1urp+Y@dITIY$lvX6#5t diff --git a/sveltejs/src/QueryManager.js b/sveltejs/src/QueryManager.js index 2c8a0390b..339fb6253 100644 --- a/sveltejs/src/QueryManager.js +++ b/sveltejs/src/QueryManager.js @@ -13,6 +13,15 @@ function getFromCache (projects) { .filter(item => typeof item === 'object'); } +function updateProjectsInCache (projects) { + cache.update((cacheData) => { + projects.forEach((project) => { + cacheData[project.id] = project; + }); + return cacheData; + }); +} + /** * Handles fetching and temporarily caching project data from the backend. * @@ -34,7 +43,7 @@ export default class { // recent query. this.count = 0; - // Whenever the cache changes, we want to forward that on to our subscribers. + // Whenever the cache changes, we want to notify our subscribers. cache.subscribe(() => { const projects = getFromCache(this.list); @@ -110,11 +119,6 @@ export default class { }); this.count = data.totalResults; - cache.update((cacheData) => { - data.list.forEach((project) => { - cacheData[project.id] = project; - }); - return cacheData; - }); + updateProjectsInCache(data.list); } } -- GitLab From 16ce3bf0da418e7b1f0d2d1cd7670312bfd2a534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Wed, 26 Feb 2025 07:52:31 -0500 Subject: [PATCH 12/23] Add refresh_project AJAX command callback --- sveltejs/public/build/bundle.js | Bin 278768 -> 278887 bytes sveltejs/public/build/bundle.js.map | Bin 258059 -> 258319 bytes sveltejs/src/QueryManager.js | 4 ++++ 3 files changed, 4 insertions(+) diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index ae4fa03654677c45191f17a8e7f4a2e8ebf00aac..56b511bb69fe5939aa8f175e34aa259748724427 100644 GIT binary patch delta 166 zcmey+DEPcdu%U&qg=q`3_Jw+vqSAuI96iUZ#0uy9+}y;xlw!StqWqHllFEWqy`t2# zqSWGy_=2MRtkmQZ1zQD;#H_>$9fhLQ;)49V;#5rqTRVknE(I_sEl5c$NezIgE%wZF yPE5{7)rf|v1(^#nC05fKtfAJLi)(UQKmT^K3(S+4+4AyBGSe!z-@42!mJ0v}yF26n delta 46 zcmaFfB>16Gu%U&qg=q`3_J!^1E-=q!7D~%6Do-p*Q7FksEK$hMQz*&b9(aXWEEfR5 C@)Aq{ diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index d18671ad2b11dfb6b42bef01256ef7fbbe6c777e..d96ebd864fe3e5eac3aec7f841509642f6ffd99a 100644 GIT binary patch delta 277 zcmeBvz~BFfzoCV33zMKyy-QJPL1K=cV^(5?bAE1aVqQwIUO`cQNq$LXL8@L+YFbfh zaYlSWQGQlxa*2Yif<|IiVug-EQEG8PeqM2^rh=`VLUl}@f<kFQN@7WB07PN2XP$Fn zaz?5~G)y7LP>>O^n$|ISwbn6tF?sr0S~1&2m6#Hk+4AyBGSe#C+f|vix2rM>FPdIo z%fvRlD4j{tGS$&BRma`Y(Ot*c(a~AQ)6vmW$JNo%RVUEVF;K_b(b3!5)6u!W6(U&Z z<mg!F1XenI?*%5Q>GCI;S*I7oF>x@NI#2(1gUMn0vE$4KIay3RN}Z<*USby6KK~+f GjvN3MA6{?( delta 73 zcmeBw#NYjZzoCV33zMMI_8ujs6lS5c{G#&2q7;RajKmU!{5*w{{Pu6EOxwSyG7B%7 e-gc8oeR@Cx6YKU<Czua%GNpTN@4dv_BnJTZ0~^Z# diff --git a/sveltejs/src/QueryManager.js b/sveltejs/src/QueryManager.js index 339fb6253..14970b398 100644 --- a/sveltejs/src/QueryManager.js +++ b/sveltejs/src/QueryManager.js @@ -22,6 +22,10 @@ function updateProjectsInCache (projects) { }); } +Drupal.AjaxCommands.prototype.refresh_project = (ajax, response) => { + updateProjectsInCache([response.project]); +}; + /** * Handles fetching and temporarily caching project data from the backend. * -- GitLab From 4be07facfdab4db1824db3e5ac5cadc923dfbbdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Wed, 26 Feb 2025 08:19:44 -0500 Subject: [PATCH 13/23] I think it's working, ensure the activator does the right thing --- src/Controller/InstallerController.php | 10 ++++------ ...Command.php => RefreshProjectsCommand.php} | 10 +++++----- sveltejs/public/build/bundle.js | Bin 278887 -> 279163 bytes sveltejs/public/build/bundle.js.map | Bin 258319 -> 258595 bytes sveltejs/src/QueryManager.js | 10 +++++++--- .../src/TestActivator.php | 13 +++++++++---- .../ProjectBrowserInstallerUiTest.php | 12 +++++++----- 7 files changed, 32 insertions(+), 23 deletions(-) rename src/{RefreshProjectCommand.php => RefreshProjectsCommand.php} (50%) diff --git a/src/Controller/InstallerController.php b/src/Controller/InstallerController.php index 0bd3f6b92..95fed907c 100644 --- a/src/Controller/InstallerController.php +++ b/src/Controller/InstallerController.php @@ -17,7 +17,7 @@ use Drupal\project_browser\ComposerInstaller\Installer; use Drupal\project_browser\EnabledSourceHandler; use Drupal\project_browser\InstallState; use Drupal\project_browser\ProjectBrowser\Normalizer; -use Drupal\project_browser\RefreshProjectCommand; +use Drupal\project_browser\RefreshProjectsCommand; use Drupal\system\SystemManager; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -436,6 +436,7 @@ final class InstallerController extends ControllerBase { */ public function activate(Request $request): AjaxResponse { $response = new AjaxResponse(); + $refreshed_projects = []; $projects = $request->getPayload()->get('projects') ?? []; if ($projects) { @@ -455,10 +456,7 @@ final class InstallerController extends ControllerBase { $response->addCommand($command); } $this->installState->setState($project_id, 'installed'); - - $project = $this->normalizer->normalize($project, context: ['source' => $source_id]); - assert(is_array($project)); - $response->addCommand(new RefreshProjectCommand($project)); + $refreshed_projects[] = $this->normalizer->normalize($project, context: ['source' => $source_id]); } catch (\Throwable $e) { $message = $e->getMessage(); @@ -472,7 +470,7 @@ final class InstallerController extends ControllerBase { } } $this->installState->deleteAll(); - return $response; + return $response->addCommand(new RefreshProjectsCommand($refreshed_projects)); } /** diff --git a/src/RefreshProjectCommand.php b/src/RefreshProjectsCommand.php similarity index 50% rename from src/RefreshProjectCommand.php rename to src/RefreshProjectsCommand.php index c6fab42a0..d3e99d39e 100644 --- a/src/RefreshProjectCommand.php +++ b/src/RefreshProjectsCommand.php @@ -7,12 +7,12 @@ namespace Drupal\project_browser; use Drupal\Core\Ajax\CommandInterface; /** - * An AJAX command to refresh a particular project in the Svelte app. + * An AJAX command to refresh projects in the Svelte app. */ -final class RefreshProjectCommand implements CommandInterface { +final class RefreshProjectsCommand implements CommandInterface { public function __construct( - private readonly array $project, + private readonly array $projects, ) {} /** @@ -20,8 +20,8 @@ final class RefreshProjectCommand implements CommandInterface { */ public function render(): array { return [ - 'command' => 'refresh_project', - 'project' => $this->project, + 'command' => 'refresh_projects', + 'projects' => $this->projects, ]; } diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index 56b511bb69fe5939aa8f175e34aa259748724427..689ecb40102ab74270da73bcf8e0a2b6a2286bde 100644 GIT binary patch delta 348 zcmaFfB>1~Uu%U&qg=q_O{rP%*eTC5CRD}e+(t?!4l2i@N1cl;!g_4ZK5{1N^9EIZ2 zq~heF%%s$!Vg-#%y;MCNh0MH^%(Bds(!?Bvz|z#BO5eo1#Prl6E(I{q*H_5QD=taQ zOHM7;R4C8P$x%p3RmjUP$xO>kO;Je9OHn9FO-{`$OI0YzNL3KYNi0b%E>S2b%Fjwo zE>TEHEJ@UxUU8mzvw^<8f@4ljez`(&Vsb`m3QS3{LP<WzNQiq=6v{Fa6&$@BBlM=L zU0^m6FIKQs&`8Wmtk6-YMrf&>o^^p)nFDG|@%H%_m=7^~DI_N4mzF3b=2b!+ub`l> zuaKOPn3tXkwg%)vm>z}lRE3PhvQ&lK{NfU@AM#3aDiw0_6H`)CwkKX;X3GTt|FU_5 delta 84 zcmey}BKW*Xu%U&qg=q_O{rTyV7ns*f|8;>`S4hEDK_f9Mu|h|oD7CmCKd(4-y4^)) jWua&oSFfNbKPxr4BzAIjztr~Ti_9ySx0haJX3GTto7^FS diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index d96ebd864fe3e5eac3aec7f841509642f6ffd99a..e5853269fa1e62e3bd5ea1ac95703df28c7bd45c 100644 GIT binary patch delta 487 zcmeBw#J~6ne?tr77N$zYdVPI`(Bf2u1ijLNl*E!$4b23F;(UdYjKmU!#GD+3;?ktz z<f6=^)S_YqjZD2%JspM2yp+tc%#_l^9EHHr)S^n?#Jt4x)S{R?1qFS5h0MI-lEl2^ z)M8DA^30qZg``x4y!?{Pw9M2Lg~YrRg`(8t)XcI}g_4X^g`C6^!PMdsg@U5|tkmQZ zg_OjSM7`;yicE71^z{`Sb8_;_6_OK^Gg4DvN{SUq@<B#I9Gjw0mYJyF=;aupH=SRJ zNngBJ!B#;dF)Oh`N1+;_rFMFN5|aW4)RyAyO-f8H%<c+_N%^HE3W<4@AeV#Po}7`G zm!1l?1LQoI28Hreg^a|qRE6C9;u5e2@=9_l6>{<uQ&Lmf+0>Y}v#Bx5E#$ZMbaZwE z6FRPrj;_-g`I$whUub7yo$h>``4|^O#@Er&clv{LCMo-LCr8J0CmnZ3M|T}(M@MHJ zPe(^juucdO=;#;-VR<_`dV_RVxkC7bPL7U+(*qirB$;xYr{~^ea+rSV1oQdrQ%*8} O=U^<~-gAk$Sq=bJ0-;X; delta 189 zcmZ4dgunk0e?tr77N$zY>AXrzGp1ixV$u~-uvO4V%u1}#Q7B3+F38U-PMt2T%%mU` z4ddz+6y;~7CYQu+cUER<VQ%MBXWGuE&Mdc(RmauQ(RKR9Tqe=!0VkMGPggj}EYFha z=$Ja)Y8{i5n75;&x3#CEbAc;Zp-!Qbqhq1dbb&}F(dk{QnYgB3$Yo-gwt|^u`u*ul ktkZkfGO<tpa+}F|`puKf=eMst#r%nb(PaCA%goJk06ro?DgXcg diff --git a/sveltejs/src/QueryManager.js b/sveltejs/src/QueryManager.js index 14970b398..5267398b5 100644 --- a/sveltejs/src/QueryManager.js +++ b/sveltejs/src/QueryManager.js @@ -14,6 +14,8 @@ function getFromCache (projects) { } function updateProjectsInCache (projects) { + // Use `.update()` so that all subscribers (i.e., individual QueryManager + // instances) will be notified and receive the latest project data. cache.update((cacheData) => { projects.forEach((project) => { cacheData[project.id] = project; @@ -22,8 +24,9 @@ function updateProjectsInCache (projects) { }); } -Drupal.AjaxCommands.prototype.refresh_project = (ajax, response) => { - updateProjectsInCache([response.project]); +// Allow cached projects to be updated via AJAX. +Drupal.AjaxCommands.prototype.refresh_projects = (ajax, { projects }) => { + updateProjectsInCache(projects); }; /** @@ -47,7 +50,8 @@ export default class { // recent query. this.count = 0; - // Whenever the cache changes, we want to notify our subscribers. + // Whenever the cache changes, we want to notify our subscribers about any + // changes to the projects we have most recently loaded. cache.subscribe(() => { const projects = getFromCache(this.list); diff --git a/tests/modules/project_browser_test/src/TestActivator.php b/tests/modules/project_browser_test/src/TestActivator.php index f10a5e9cd..8a8bb74d3 100644 --- a/tests/modules/project_browser_test/src/TestActivator.php +++ b/tests/modules/project_browser_test/src/TestActivator.php @@ -40,7 +40,12 @@ class TestActivator implements ActivatorInterface { * {@inheritdoc} */ public function getStatus(Project $project): ActivationStatus { - if ($project->machineName === 'pinky_brain') { + $activated_projects = $this->state->get("test activator", []); + + if (in_array($project->id, $activated_projects, TRUE)) { + return ActivationStatus::Active; + } + elseif ($project->machineName === 'pinky_brain') { return ActivationStatus::Present; } return ActivationStatus::Absent; @@ -50,9 +55,9 @@ class TestActivator implements ActivatorInterface { * {@inheritdoc} */ public function activate(Project $project): null { - $log_message = $this->state->get("test activator", []); - $log_message[] = "$project->title was activated!"; - $this->state->set("test activator", $log_message); + $activated_projects = $this->state->get("test activator", []); + $activated_projects[] = $project->id; + $this->state->set("test activator", $activated_projects); return NULL; } diff --git a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php index c29e71767..d2d474858 100644 --- a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php +++ b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php @@ -80,9 +80,10 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase { $this->drupalGet('admin/modules/browse/project_browser_test_mock'); $this->installProject('Cream cheese on a bagel'); - // The activator in project_browser_test should have logged a message. + // The activator in project_browser_test should have logged the unqualified + // project ID. // @see \Drupal\project_browser_test\TestActivator - $this->assertContains('Cream cheese on a bagel was activated!', $this->container->get(StateInterface::class)->get('test activator')); + $this->assertContains('cream_cheese', $this->container->get(StateInterface::class)->get('test activator')); } /** @@ -281,12 +282,13 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase { $this->waitForProjectToBeInstalled($cream_cheese); $this->waitForProjectToBeInstalled($kangaroo); - // The activator in project_browser_test should have logged a message. + // The activator in project_browser_test should have logged the unqualified + // project IDs. // @see \Drupal\project_browser_test\TestActivator $activated = $this->container->get(StateInterface::class) ->get('test activator'); - $this->assertContains('Cream cheese on a bagel was activated!', $activated); - $this->assertContains('Kangaroo was activated!', $activated); + $this->assertContains('cream_cheese', $activated); + $this->assertContains('kangaroo', $activated); } /** -- GitLab From 0ffb1253c70d2312f1ca972c1d59304dff2e0a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Wed, 26 Feb 2025 08:30:40 -0500 Subject: [PATCH 14/23] Fix a few failing tests --- .../FunctionalJavascript/ProjectBrowserInstallerUiTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php index d2d474858..f5afc262a 100644 --- a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php +++ b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php @@ -177,6 +177,8 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase { * @covers ::unlock */ public function testCanBreakStageWithMissingProjectBrowserLock(): void { + TestActivator::handle('drupal/cream_cheese'); + // Start install begin. $this->drupalGet('admin/modules/project_browser/install-begin', [ 'query' => ['source' => 'project_browser_test_mock'], @@ -204,6 +206,8 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase { * @covers ::unlock */ public function testCanBreakLock(): void { + TestActivator::handle('drupal/cream_cheese'); + // Start install begin. $this->drupalGet('admin/modules/project_browser/install-begin', [ 'query' => ['source' => 'project_browser_test_mock'], @@ -239,6 +243,8 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase { * Confirm that a status check warning allows download and install. */ public function testPackageManagerWarningAllowsDownloadInstall(): void { + TestActivator::handle('drupal/cream_cheese'); + // @see \Drupal\project_browser_test\TestInstallReadiness $this->container->get(StateInterface::class) ->set('project_browser_test.simulated_result_severity', SystemManager::REQUIREMENT_WARNING); -- GitLab From 496916f28755dafe9a8b6bafae20d186c93fb6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Wed, 26 Feb 2025 08:50:35 -0500 Subject: [PATCH 15/23] Fix one broken test by resetting state cache in RecipeActivator --- src/Activator/RecipeActivator.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Activator/RecipeActivator.php b/src/Activator/RecipeActivator.php index 2cff5fc00..bee718379 100644 --- a/src/Activator/RecipeActivator.php +++ b/src/Activator/RecipeActivator.php @@ -68,6 +68,8 @@ final class RecipeActivator implements InstructionsInterface, EventSubscriberInt */ public function getStatus(Project $project): ActivationStatus { $path = $this->getPath($project); + // Ensure we're getting the most up-to-date information. + $this->state->resetCache(); if (in_array($path, $this->state->get(static::STATE_KEY, []), TRUE)) { return ActivationStatus::Active; -- GitLab From 4a488d921d22e1d4ef67eb7e25669f2dc320e7a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Wed, 26 Feb 2025 09:01:45 -0500 Subject: [PATCH 16/23] Tiny cleanup --- src/Controller/InstallerController.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Controller/InstallerController.php b/src/Controller/InstallerController.php index 95fed907c..161f4628a 100644 --- a/src/Controller/InstallerController.php +++ b/src/Controller/InstallerController.php @@ -436,7 +436,7 @@ final class InstallerController extends ControllerBase { */ public function activate(Request $request): AjaxResponse { $response = new AjaxResponse(); - $refreshed_projects = []; + $activated_projects = []; $projects = $request->getPayload()->get('projects') ?? []; if ($projects) { @@ -446,6 +446,7 @@ final class InstallerController extends ControllerBase { assert(is_array($projects)); foreach ($projects as $project_id) { $this->installState->setState($project_id, 'activating'); + // $project_id is fully qualified and has the form `SOURCE_ID/LOCAL_ID`. [$source_id] = explode('/', $project_id, 2); try { @@ -456,7 +457,7 @@ final class InstallerController extends ControllerBase { $response->addCommand($command); } $this->installState->setState($project_id, 'installed'); - $refreshed_projects[] = $this->normalizer->normalize($project, context: ['source' => $source_id]); + $activated_projects[] = $this->normalizer->normalize($project, context: ['source' => $source_id]); } catch (\Throwable $e) { $message = $e->getMessage(); @@ -470,7 +471,7 @@ final class InstallerController extends ControllerBase { } } $this->installState->deleteAll(); - return $response->addCommand(new RefreshProjectsCommand($refreshed_projects)); + return $response->addCommand(new RefreshProjectsCommand($activated_projects)); } /** -- GitLab From 8ea2fd402ef0570b215ee6d4d4d0a63d44859391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Wed, 26 Feb 2025 11:40:03 -0500 Subject: [PATCH 17/23] Use project ID as index to hopefully prevent flickering --- sveltejs/public/build/bundle.js | Bin 279163 -> 279138 bytes sveltejs/public/build/bundle.js.map | Bin 258595 -> 258613 bytes sveltejs/src/ProjectBrowser.svelte | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index 689ecb40102ab74270da73bcf8e0a2b6a2286bde..cecd5d94d1a117891778ff9a5cfcbc6634c4108f 100644 GIT binary patch delta 55 zcmey}BKW99u%U&qg=q`(ru)+cJ~Ap!e|?`>l0!kGD8F1UGiAHL1LnP~%zBwA(;2@o M+HdE1#+)Am0C=?(VgLXD delta 77 zcmaFVBKW&Su%U&qg=q`(ru);olbN~MEMgUG6*4DZ_^2`c@_lAWJ{^V3yp+@m1&yNo ja?RSwAJ>RYH>hFc+%Ecnc@FDz#xIQa+dn^L&W`~As4^au diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index e5853269fa1e62e3bd5ea1ac95703df28c7bd45c..a012e1af66e538537ad02c48022de757983f9f89 100644 GIT binary patch delta 103 zcmZ4dgn#Q3{)QIDEld}EI21IB^2_xyQ`+zPFm1o<!@RF;`kc$m?Pi{ij=?(4j*gBx nz97OMM7YC=NGQu)$J^1-S;y7U(G{$Cx}6@g-S&nn%!_3KgE1k? delta 75 zcmdn`gn#i9{)QIDEld}Ecytsp^HNeP6f}zR%iC}IFm1o-!@RF;`n1c;9o+fOj*jV0 eI_{2+?$ZM;nZ;N^0;ba)^_b1KPrAaqSQY?Xv>R^# diff --git a/sveltejs/src/ProjectBrowser.svelte b/sveltejs/src/ProjectBrowser.svelte index 3a9ed182e..d126cd6d1 100644 --- a/sveltejs/src/ProjectBrowser.svelte +++ b/sveltejs/src/ProjectBrowser.svelte @@ -248,7 +248,7 @@ </div> </div> {#key $updated} - {#each rows as row, index (row)} + {#each rows as row (row.id)} <Project {toggleView} project={row} /> {/each} {/key} -- GitLab From d8bcbfaab212bfb97c737129e01bb1a3fc9b8864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Wed, 26 Feb 2025 11:52:20 -0500 Subject: [PATCH 18/23] Add low-level test coverage that multiple modules can be activated at once --- .../Functional/InstallerControllerTest.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/src/Functional/InstallerControllerTest.php b/tests/src/Functional/InstallerControllerTest.php index 68480eb72..872efb976 100644 --- a/tests/src/Functional/InstallerControllerTest.php +++ b/tests/src/Functional/InstallerControllerTest.php @@ -648,4 +648,36 @@ class InstallerControllerTest extends BrowserTestBase { $this->assertSession()->addressEquals('/admin/modules/browse/drupal_core'); } + /** + * Tests activating multiple core modules in a single request. + */ + public function testActivateMultipleModules(): void { + $this->config('project_browser.admin_settings') + ->set('enabled_sources', ['drupal_core']) + ->save(); + + // Prime the project cache. + $this->container->get(EnabledSourceHandler::class) + ->getProjects('drupal_core'); + + $response = $this->makeApiRequest( + 'POST', + Url::fromRoute('project_browser.activate'), + [ + RequestOptions::BODY => Json::encode([ + 'projects' => implode(',', [ + 'drupal_core/language', + 'drupal_core/content_translation', + 'drupal_core/locale', + ]), + ]), + ], + ); + $this->assertSame(200, $response->getStatusCode()); + $commands = Json::decode($response->getBody()->getContents()); + $this->assertSame('refresh_projects', $commands[0]['command']); + $statuses = array_column($commands[0]['projects'], 'status'); + $this->assertSame(['active'], array_unique($statuses)); + } + } -- GitLab From a6ca113e8d435be965211ecadf35f6a08fcda1fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Wed, 26 Feb 2025 12:18:31 -0500 Subject: [PATCH 19/23] Fix the bug by restoring the module info from the global service wrapper --- src/Activator/ModuleActivator.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Activator/ModuleActivator.php b/src/Activator/ModuleActivator.php index 6edee6617..b6731d8d4 100644 --- a/src/Activator/ModuleActivator.php +++ b/src/Activator/ModuleActivator.php @@ -25,8 +25,8 @@ final class ModuleActivator implements InstructionsInterface, TasksInterface { public function __construct( private readonly ModuleInstallerInterface $moduleInstaller, - private readonly ModuleExtensionList $moduleList, - private readonly ModuleHandlerInterface $moduleHandler, + private ModuleExtensionList $moduleList, + private ModuleHandlerInterface $moduleHandler, private readonly FileUrlGeneratorInterface $fileUrlGenerator, private readonly RequestStack $requestStack, private readonly AccountInterface $currentUser, @@ -36,7 +36,7 @@ final class ModuleActivator implements InstructionsInterface, TasksInterface { * {@inheritdoc} */ public function getStatus(Project $project): ActivationStatus { - if (array_key_exists($project->machineName, $this->moduleList->getAllInstalledInfo())) { + if ($this->moduleHandler->moduleExists($project->machineName)) { return ActivationStatus::Active; } elseif (array_key_exists($project->machineName, $this->moduleList->getAllAvailableInfo())) { @@ -57,6 +57,14 @@ final class ModuleActivator implements InstructionsInterface, TasksInterface { */ public function activate(Project $project): null { $this->moduleInstaller->install([$project->machineName]); + + // The container has changed, so we need to reload the module handler and + // module list from the global service wrapper. + // @phpstan-ignore-next-line + $this->moduleHandler = \Drupal::moduleHandler(); + // @phpstan-ignore-next-line + $this->moduleList = \Drupal::service(ModuleExtensionList::class); + return NULL; } -- GitLab From 2efe8cf8cf5d8f3d2f79f014a4f9597be27ad6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Wed, 26 Feb 2025 12:57:30 -0500 Subject: [PATCH 20/23] Fix ModuleActivatorTest --- tests/src/Functional/InstallerControllerTest.php | 4 +++- tests/src/Kernel/ModuleActivatorTest.php | 14 ++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/src/Functional/InstallerControllerTest.php b/tests/src/Functional/InstallerControllerTest.php index 872efb976..bd24d048f 100644 --- a/tests/src/Functional/InstallerControllerTest.php +++ b/tests/src/Functional/InstallerControllerTest.php @@ -674,7 +674,9 @@ class InstallerControllerTest extends BrowserTestBase { ], ); $this->assertSame(200, $response->getStatusCode()); - $commands = Json::decode($response->getBody()->getContents()); + $body = $response->getBody()->getContents(); + $commands = Json::decode($body); + $this->assertNotEmpty($commands, $body); $this->assertSame('refresh_projects', $commands[0]['command']); $statuses = array_column($commands[0]['projects'], 'status'); $this->assertSame(['active'], array_unique($statuses)); diff --git a/tests/src/Kernel/ModuleActivatorTest.php b/tests/src/Kernel/ModuleActivatorTest.php index 1977ea6ad..ceac2d066 100644 --- a/tests/src/Kernel/ModuleActivatorTest.php +++ b/tests/src/Kernel/ModuleActivatorTest.php @@ -26,6 +26,7 @@ class ModuleActivatorTest extends KernelTestBase { protected static $modules = [ 'block', 'breakpoint', + 'help', 'project_browser', 'user', ]; @@ -49,6 +50,13 @@ class ModuleActivatorTest extends KernelTestBase { */ protected function setUp(): void { $this->mockModuleHandler = $this->createMock(ModuleHandlerInterface::class); + $this->mockModuleHandler->expects($this->atLeastOnce()) + ->method('moduleExists') + ->willReturnMap(array_map( + fn (string $module_name): array => [$module_name, TRUE], + static::$modules, + )); + parent::setUp(); $this->config('project_browser.admin_settings') @@ -107,12 +115,6 @@ class ModuleActivatorTest extends KernelTestBase { * [false] */ public function testHelpLinksAreExposed(bool $implements_hook_help): void { - $this->enableModules(['help']); - - $this->mockModuleHandler->expects($this->atLeastOnce()) - ->method('moduleExists') - ->with('help') - ->willReturn(TRUE); $this->mockModuleHandler->expects($this->atLeastOnce()) ->method('hasImplementations') ->with('help', 'breakpoint') -- GitLab From 101ba9ce43484875b3a37b5c4d2a590c78411e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Wed, 26 Feb 2025 13:13:42 -0500 Subject: [PATCH 21/23] Take another approach in InstallerControllerTest --- .../Functional/InstallerControllerTest.php | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/tests/src/Functional/InstallerControllerTest.php b/tests/src/Functional/InstallerControllerTest.php index bd24d048f..55769e6d4 100644 --- a/tests/src/Functional/InstallerControllerTest.php +++ b/tests/src/Functional/InstallerControllerTest.php @@ -660,26 +660,18 @@ class InstallerControllerTest extends BrowserTestBase { $this->container->get(EnabledSourceHandler::class) ->getProjects('drupal_core'); - $response = $this->makeApiRequest( - 'POST', + $response = $this->getPostResponse( Url::fromRoute('project_browser.activate'), [ - RequestOptions::BODY => Json::encode([ - 'projects' => implode(',', [ - 'drupal_core/language', - 'drupal_core/content_translation', - 'drupal_core/locale', - ]), + 'projects' => implode(',', [ + 'drupal_core/language', + 'drupal_core/content_translation', + 'drupal_core/locale', ]), ], ); $this->assertSame(200, $response->getStatusCode()); - $body = $response->getBody()->getContents(); - $commands = Json::decode($body); - $this->assertNotEmpty($commands, $body); - $this->assertSame('refresh_projects', $commands[0]['command']); - $statuses = array_column($commands[0]['projects'], 'status'); - $this->assertSame(['active'], array_unique($statuses)); + $this->assertSame(3, substr_count((string) $response->getBody(), '"status":"active"')); } } -- GitLab From b8f1136af70426458b91d41baa81be91db1f7690 Mon Sep 17 00:00:00 2001 From: Tim Plunkett <git@plnktt.com> Date: Wed, 26 Feb 2025 13:25:53 -0500 Subject: [PATCH 22/23] Use targeted ignores for PHPStan --- src/Activator/ModuleActivator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Activator/ModuleActivator.php b/src/Activator/ModuleActivator.php index b6731d8d4..527216b50 100644 --- a/src/Activator/ModuleActivator.php +++ b/src/Activator/ModuleActivator.php @@ -60,9 +60,9 @@ final class ModuleActivator implements InstructionsInterface, TasksInterface { // The container has changed, so we need to reload the module handler and // module list from the global service wrapper. - // @phpstan-ignore-next-line + // @phpstan-ignore globalDrupalDependencyInjection.useDependencyInjection $this->moduleHandler = \Drupal::moduleHandler(); - // @phpstan-ignore-next-line + // @phpstan-ignore globalDrupalDependencyInjection.useDependencyInjection $this->moduleList = \Drupal::service(ModuleExtensionList::class); return NULL; -- GitLab From ce140dd96ed4b0e98595f99e91100517a67fa3bd Mon Sep 17 00:00:00 2001 From: Tim Plunkett <git@plnktt.com> Date: Wed, 26 Feb 2025 14:35:07 -0500 Subject: [PATCH 23/23] Revert "Use targeted ignores for PHPStan" This reverts commit b8f1136af70426458b91d41baa81be91db1f7690. --- src/Activator/ModuleActivator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Activator/ModuleActivator.php b/src/Activator/ModuleActivator.php index 527216b50..b6731d8d4 100644 --- a/src/Activator/ModuleActivator.php +++ b/src/Activator/ModuleActivator.php @@ -60,9 +60,9 @@ final class ModuleActivator implements InstructionsInterface, TasksInterface { // The container has changed, so we need to reload the module handler and // module list from the global service wrapper. - // @phpstan-ignore globalDrupalDependencyInjection.useDependencyInjection + // @phpstan-ignore-next-line $this->moduleHandler = \Drupal::moduleHandler(); - // @phpstan-ignore globalDrupalDependencyInjection.useDependencyInjection + // @phpstan-ignore-next-line $this->moduleList = \Drupal::service(ModuleExtensionList::class); return NULL; -- GitLab