From 6d05bfc4c520575a0f47957fc9128f8cf5729f9d Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 8 Sep 2025 14:53:13 +0100 Subject: [PATCH] Add UIFeature to hide public space and room creation (#30708) * Add settings to hide public room & space creation. * Add space changes. * Add room changes. * lint * Add playwright tests * don't specialcase 1 join rule * Ensure mocks get cleared * Fixup test * Add SpaceCreateMenu component unit-tests * Fixup create room test asserts * fix import --- docs/config.md | 2 + playwright/e2e/room/create-room.spec.ts | 30 +++++ playwright/e2e/spaces/spaces.spec.ts | 50 ++++++++ .../create-room-no-public-linux.png | Bin 0 -> 33850 bytes .../views/dialogs/CreateRoomDialog.tsx | 9 +- .../views/elements/JoinRuleDropdown.tsx | 16 ++- .../views/spaces/SpaceCreateMenu.tsx | 23 +++- src/i18n/strings/en_EN.json | 1 + src/settings/Settings.tsx | 8 ++ src/settings/UIFeature.ts | 2 + .../views/dialogs/CreateRoomDialog-test.tsx | 21 +-- .../views/spaces/SpaceCreateMenu-test.tsx | 121 ++++++++++++++++++ .../SpaceCreateMenu-test.tsx.snap | 3 + 13 files changed, 264 insertions(+), 22 deletions(-) create mode 100644 playwright/snapshots/room/create-room.spec.ts/create-room-no-public-linux.png create mode 100644 test/unit-tests/components/views/spaces/SpaceCreateMenu-test.tsx create mode 100644 test/unit-tests/components/views/spaces/__snapshots__/SpaceCreateMenu-test.tsx.snap diff --git a/docs/config.md b/docs/config.md index 2bc36e206f..0c5a2b2dca 100644 --- a/docs/config.md +++ b/docs/config.md @@ -585,6 +585,8 @@ Currently, the following UI feature flags are supported: - `UIFeature.BulkUnverifiedSessionsReminder` - Display popup reminders to verify or remove unverified sessions. Defaults to true. - `UIFeature.locationSharing` - Whether or not location sharing menus will be shown. +- `UIFeature.allowCreatingPublicRooms` - Whether or not public rooms can be created. +- `UIFeature.allowCreatingPublicSpaces` - Whether or not public spaces can be created. ## Undocumented / developer options diff --git a/playwright/e2e/room/create-room.spec.ts b/playwright/e2e/room/create-room.spec.ts index e697ac97a9..b5a8ce3ccd 100644 --- a/playwright/e2e/room/create-room.spec.ts +++ b/playwright/e2e/room/create-room.spec.ts @@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details. */ import { SettingLevel } from "../../../src/settings/SettingLevel"; +import { UIFeature } from "../../../src/settings/UIFeature"; import { test, expect } from "../../element-web-test"; const name = "Test room"; @@ -65,4 +66,33 @@ test.describe("Create Room", () => { const header = page.locator(".mx_RoomHeader"); await expect(header).toContainText(name); }); + + test.describe("Should hide public room option if not allowed", () => { + test.use({ + config: { + setting_defaults: { + [UIFeature.AllowCreatingPublicRooms]: false, + }, + }, + }); + + test("should disallow creating public rooms", { tag: "@screenshot" }, async ({ page, user, app, axe }) => { + const dialog = await app.openCreateRoomDialog(); + // Fill name & topic + await dialog.getByRole("textbox", { name: "Name" }).fill(name); + await dialog.getByRole("textbox", { name: "Topic" }).fill(topic); + + axe.disableRules("color-contrast"); // XXX: Inheriting colour contrast issues from room view. + await expect(axe).toHaveNoViolations(); + // Snapshot it + await expect(dialog).toMatchScreenshot("create-room-no-public.png"); + + // Submit + await dialog.getByRole("button", { name: "Create room" }).click(); + + await expect(page).toHaveURL(new RegExp(`/#/room/!.+`)); + const header = page.locator(".mx_RoomHeader"); + await expect(header).toContainText(name); + }); + }); }); diff --git a/playwright/e2e/spaces/spaces.spec.ts b/playwright/e2e/spaces/spaces.spec.ts index e1284a2c61..3eaae8544f 100644 --- a/playwright/e2e/spaces/spaces.spec.ts +++ b/playwright/e2e/spaces/spaces.spec.ts @@ -11,6 +11,7 @@ import { test, expect } from "../../element-web-test"; import type { Preset, ICreateRoomOpts } from "matrix-js-sdk/src/matrix"; import { type ElementAppPage } from "../../pages/ElementAppPage"; import { isDendrite } from "../../plugins/homeserver/dendrite"; +import { UIFeature } from "../../../src/settings/UIFeature"; async function openSpaceCreateMenu(page: Page): Promise { await page.getByRole("button", { name: "Create a space" }).click(); @@ -391,4 +392,53 @@ test.describe("Spaces", () => { "space-visibility-settings.png", ); }); + + test.describe("Should hide public spaces option if not allowed", () => { + test.use({ + config: { + setting_defaults: { + [UIFeature.AllowCreatingPublicSpaces]: false, + }, + }, + }); + + test("should disallow creating public rooms", { tag: "@screenshot" }, async ({ page, user, app }) => { + const menu = await openSpaceCreateMenu(page); + await menu + .locator('.mx_SpaceBasicSettings_avatarContainer input[type="file"]') + .setInputFiles("playwright/sample-files/riot.png"); + await menu.getByRole("textbox", { name: "Name" }).fill("This is a private space"); + await expect(menu.getByRole("textbox", { name: "Address" })).not.toBeVisible(); + await menu + .getByRole("textbox", { name: "Description" }) + .fill("This is a private space because we can't make public ones"); + await menu.getByRole("button", { name: "Create" }).click(); + + await page.getByRole("button", { name: "Me and my teammates" }).click(); + + // Create the default General & Random rooms, as well as a custom "Projects" room + await expect(page.getByPlaceholder("General")).toBeVisible(); + await expect(page.getByPlaceholder("Random")).toBeVisible(); + await page.getByPlaceholder("Support").fill("Projects"); + await page.getByRole("button", { name: "Continue" }).click(); + await page.getByRole("button", { name: "Skip for now" }).click(); + + // Assert rooms exist in the room list + const roomList = page.getByRole("tree", { name: "Rooms" }); + await expect(roomList.getByRole("treeitem", { name: "General", exact: true })).toBeVisible(); + await expect(roomList.getByRole("treeitem", { name: "Random", exact: true })).toBeVisible(); + await expect(roomList.getByRole("treeitem", { name: "Projects", exact: true })).toBeVisible(); + + // Assert rooms exist in the space explorer + await expect( + page.locator(".mx_SpaceHierarchy_list .mx_SpaceHierarchy_roomTile", { hasText: "General" }), + ).toBeVisible(); + await expect( + page.locator(".mx_SpaceHierarchy_list .mx_SpaceHierarchy_roomTile", { hasText: "Random" }), + ).toBeVisible(); + await expect( + page.locator(".mx_SpaceHierarchy_list .mx_SpaceHierarchy_roomTile", { hasText: "Projects" }), + ).toBeVisible(); + }); + }); }); diff --git a/playwright/snapshots/room/create-room.spec.ts/create-room-no-public-linux.png b/playwright/snapshots/room/create-room.spec.ts/create-room-no-public-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..84494f46bf95a33198e513213210a920cbd7c0fc GIT binary patch literal 33850 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
+3%rP9j_-KTa~NdcXD+e*;4D?xsfu z=7z5cu%7*)oekW0V#|jX!(2Pz>;vKUc!QkTEmlgD3-19xl^M*A(6FD?vT2{CXn-h+ z{7>44R`xn}Fe8F@@`L0Sw>%*%varoAKS}XMyUpO{FYd_zD{T5++BYx&SlHG9PO_mK zf7#6)HXs)5nStsbE)?zT2Sf!@hX0V?YM${NQ+evfxB)k5oWDDs`OvT$m+C7)_J320 z6#RmPA*sL0L?QDfgmM2dgNkD3o!{NcepAH9Wfrn+i=LQz9)<&ohB-|Xg4C*#qMDSh zsavPxdV`+<0Lx**kLx&2E>V1VG~Q<@1TyDVRFsc9;u8RJPlT-HEbQc@Xrn%fuDASh zuCl?i5U+$TPc+(tI<$Y^c%K!Z)PhJ*#D{iSvW*|FbqMFYcuQ)IS=a0wFIo4Q>1`<< zy@%YflAb8T{{YY1p@zb9Oi6ZQRZm-1(vs7UoBscrL;Mdy@qY$AtZ7MEaq;o#@#*RR zqZA6o#wR8w$HylpE7RtENqKi_pB^haO_*~|WR-3!yMC1T$2yYu#wI5ZrlnX|;95>e zQ-0%oYXzAao0^!ISrrxkTe+A4MgAW;IvSdr!~`?P9wbV{e;}R!hRPUyXSj+LPA`ic zSah!BSI&54rE1dnw4`X}qHAR8?0*z(>@7&9@0$q=6B9l8e{ZCioE#DwT}(Lj*yumK zM+`MCIeuS?hU(&Y{al~4m{zonNAr~97v$6d(`r$4v4%IN4 zpvY)vnJcEO3J?>6G##LWeINKIoB?HdHY4w-XS6rc+j!Od2-aV+KiYu+A6=|Gb;aFh z1w3uy1LC|ZE4gwBhfjUqJGFTLBh7)(S8MCj(#3a|HSKLCNvG}icRK*h5D*fk2z^tT z!~Wp<9EHz;5>HJ-!~M?(I*tbz<2n^yi_ASeJ=N5b!sAHSJiI}Oh^%aEvoo`lOu|~< zN4s;d1QY2rMutaUo&;s3r6;lxEpzjWi*rkQFe1ak!T{3+TYe}|TcZ#V_xJbn+w3_w zIAmpIU0<$@Ca0>H3n27OP4Td?vtwh2Q2IXL`y0h;ogCc4O#dL80}eFK0)tDqpipPHg3yR7UK1d@5&DnY|h(ChFvcsv=- z?-KD3wlX$`*_>LuC=Lkd0Zdoq_L49l69EAM0wMx}YeAYv9|e?& zqCZ7H3S@9|TJ|eHMZ>{9{S;Nelk3yk`L_SUv7>#l3B6sny`5cMUQ!YP-Yf!`ul3rm z2dim5hqn3P<6Lqwzs{P?WwgTOC{t5YYpK37=B)*juU{OqY0s^8xqbfZuh%IKIAQ7O z>9_v~cF2#Wr#e<@U21Om#1vyLEF&Mk}iu*XT63@&rX>x3C zu0k3U!u9PnOSs`W_0n+cf4Km4@glIP*_5Rp?}`oFC&#siS68rY);v7ic7Q7gu(iXF zM&L+#d%rn4cBJn$^E7j-N8!yYM9FV&E2thdS=nG4q36XS6D#<>-;gOPi`JzkC8pj{ zo}u4qK6L#Qv9_>U1{ETI?@;!ciE@WZjn z5Q|xqUry19?D#EC)p7Vz)VtX#uS<)~t*sFvF?p6NnK2O&5txf1tL>d!s;e6YW+RcU z8PyJgeo9hOP|cTUfOA%6WmVKCVv46+PG;X6Vw8Py#=_Ga@1TngSccsGdddnSwgiQR zCB!GzHa60$%T0dO>(s0-0jALqyVkZAad3dMV9z*)k*KH^veT=^QK|4*gq&3{B`vK$ zt?LTmPt|#NJQg^xz*<6vG*yf->d?|)`~zF6TkZFWMMX);hKFgZK9d?uf#obhG5^^+ zIB44RBS|UJYOsDshgJLJSy=XG?5XgpmKOd3uAe#>i1qx-QkObK4N<8+DeDp3=a`=l zT*kTLHV3qE=5ywxu1{k4aL?pVQZmwFqj}{J=&13kXzn-I zx(Gq45{HM8=u5unvEfCKJJbk5eZyxW<*aPAK@$O=!LLFq^*ZtJ@C@q$XMa`)jm8WN zjZB!r8Cz8)*b)W|l}W>I61Pmi^rRY`f{184>OPHm~Z>v#M#s=-lrj z@AVW_H;y<_`mqri&1hnxI)t-o+M|WNcEe*Rto|$|IpHWcs7-tBe~K^{)H>%zriMmW z@e#pZ)R_mO*jQg#vPr{!!DWdBz2kP|X`3I+JS^VYN1?vJBO(T_+Uj2l!2A$zmT{DZ z`vajolae(MI*MF9F+4nw2TvHZ9r9!2=8y?hR7{S$VJYNmm=O6vpMgw!-xp;WgfZQ* zda=PiGTz}!!+P;5VwTUCgU~nXj@OQo#u4xni7Bxvb9pGGvQl!wG7CjNl|?C)m6f(1 zjjqc9zoz?gN`OQ<33C9i5tb5-kT_)DkBxS}7%6^jM=>#o271h^%1`VZ99=ID!X_@x z5Jqb-e|j(&evzT~WlMy|Q1E=;X9^UV6A{hU_Y8{UOiRHb&p7;``ji8>7E@DG+dSWV zX28*DQ#L#vk5CW9Ea!@lBJXVUN$S9TDG`1^K%?~2--1&1{a!dPmus=Z61$X272EMj z!R&Rc7K#8vKa`UPg3wq*rJ=}laDWu2%uZoJesKxLcDa`r9V%MCx6i!ZE+$|sJgC%t zF=8Dbo<<#TxwD+#fPmN%3wt3k4wrg;4ejdL@4Q3_gB2}eF8BiiMey_gE=19(u6@Z@ zvnQOQWv$~UzxoImTU>G%AS@>%jQ{lu@0C20 zONHM&*M34r0{kyBggrc>Ac|Nq2`^&~_d-!xT8Y1JbHbd;w3n)?lxTkUOzsZgePw}H znM&{}{^S@=GXnHC<1(_4dWtZJ@PRxsc+#&ziUd{(Byf=8M}<}PLE)$@u~Z|%D3k<0 z^8O17^h2{oc$jL8Q852kV+b@)RMh13^m<*{v;V~To#FBE#Q5a+#N_1BaZ2DpfU?*H zrTP<8lAtK}y|CUDXl5g1q&0ad5+0}`(xg-W?5X^hPXK4A0P163E(=6QOG`&bN0A@< z>z_Xmf*oUI_&uy}3;-@mb3kXjm!P7W$m;0W=0W{@4DCKyr(*^h;FiyO#>Om6l|>|a2a9*ZK_%FPC;XOgS>rMb4r)lL9KF+JuUj3Nn#A?4Jv@{XFlo);WA;Ycx z#i2iGP8x^eoQuf`E$g^Fu=3P?wiLz`vw2G#( z&~Di#Zk@&G_`I-kgN-tfFqCn(*GG182#?Eb)agS%7L>i%-+sb5Tf&8`9=(%Qaxzqma-N4-rV>6Bo9<} zsuq=*Hdj7O7#`PIQL6)xUsZ(Y$=p`U8{?ycbadV^X!??Vr*E2i@~gdzn`2^~PNgkL z2jY@3&v{mb&6lU5TC2pUCPLo}m(As~YKUHd^u$-`jup^60j~HoFe; zo!+Q>;osPv;cubXs++3aFPFw>ayf{Ta<8q;ZDkF@BXT7AuBWBp9Y>|Nn=F?JxA3M7 z9iLzI%wCKGVn!S>wxHEA5d~6Olj+D%MML7KUhChRr9&4-$F%f#ox%l|z5PA?KYA_u z;RfQmx)mnm5;+ChjW;r?_)hGoY0h&XMDP}_%3b5+lmuRr?Pv!2f$kuGB`u8WYQu2$-u zSIC4OdvDLT`OqhpbliWUYcsb<$y$G!4Cc^RiF;(5*_lQCP3+WzaPkyD?1GZHPTlo% zRELH~dVqaqWPklWUXmDcYukURrZ>j#`@T`TC>-+4t6E04*J;Uq7f8+2DK@x!G9}w| zVwezE;Ptpixq`ULb@7suKEuX3U2eS@UP%gn8ZKIBSNqVl;i6pS@38Y)KN!Gs!S8Kq zSQJ^%^zg_t@hc_4&`Q-n|KXs7ZbX|0T5dy3RY+oh1T~7h2KU>>O-!5KapL^72T_>T zM1H|tw{l}{#{l1aRZn|g7}(otwMk}Rq3uNa92XrgQRCm5)IfN;we{VnTNVL#uCLCo zGj|f4420xA<`z?eW|N7xDyoX~=Z4BXIXbXJ^O!a+UoUCV!pUD6XBSj@^SA#@SHm*B z2FgZ_M&Zs0>(nFMWJ_RWGSJiS{uqz=&dNDgPh)p0NI|f=%)=8~g1hUPYXSsxLh{4x z>LB9D%MM}d+3`z3OD1Gt&X-DXp``igA8dYNfh+g;uGzr^KzCKX?f~Vsm-f_~zUcO& z^G3!}6Y3c`S}GK)g8mSJgO#S$U~xGB=}#z6{grAK&3slE55VZeN)RGHk|T==bLpJ1gmQI?X8J^42Z_|O_~bLhRr z*~_(nxA8mW+SvH0mY|h=3=-mnR~t%;F;3@H)93Z{ZL#b;!zr+S`cQDuA@IrMJgB;K zzG~zOvQDP#PUEEK`m+~T3#HKxw9=d{S^c7+jIfW4CU-xD7_8C*BxNOquA=ylT&O$i z&xn}IAjoK zxZ{4SE}nDiH0KVlK$5SpJ1nxn z(2X!fzlyV1TUrzi554}1n2?m(7tnD(;jOa|qz&B&Ug^=@x^Ci8gGE4+&@#doI8z!G zFFLxW+Sg=v{*qS~X8(EY>t6Sd!t#8z)ov_0<=GA;9vy}-urU;xn#AB@Y25q4HbkumYU#eAL5Km1~?8sY=W%Ll`l;~ zD;%X!t%YR`PzX1Lm4UHG+ki(XHPSze6HSZL5skNRqk)VumdPN8P_v~Sac9(gYp`Si z6DQnaq3bjCx0l|KfZts;18m5FV9&bUDx1|Um}5%augpRQvb$iIm>4Pvi9O_b%eNCY zCnZ-%Eauvc18>wqm=ea=VP#UnZ%Q3)uZMUGn4|ctknBdnHdaM?SUTYyA-OrNSAE+Pq0`Wc^hF`= zjxTOju6tvIO|qB#DTXct=br&CoTeZ*7mTR1e-cuXMq* zL-@RPFga@gyx8BQd62y1d(y+1&ybnBOaOI+ykvLEaQ=~b>EHl$IDf`VT1p1o3%0U> zHr#LZUb?rRhr(V-w&?sp;vx zmJ(dFp$1RX0-mlu>0gVfs%l7l2lo~D%eZy5OJJXXPN6?aG8EehFSa0N+*vh4LIfF1 zY*Rr3eB>Kh0dSdt)I&NX1ghPyG*}DhbRoP(OKLCM6j=2HNY3YzwsfViZ^7jx1tFlm z#h(1$s;~SI?}%j7iF9do7AFWfsQf4f5U!%eY*@~dqaW@I?xy3&vmp;c)!$3g({WXp zXG8UZeVL9xpdXj^unWXU1ijfJ{GRtn)<_7FCnao1&Q+BmCaa=5Oy*~(F zS-{eUsJ5s3FFD%u(nXNPAE6qQ94$S^OOi5H9AR8_Wvt0UBp?be2_{mfw3xNDMSkf| zG(8&IFx)LQ{Q*vro`E1a`*OXFPr=canp24_BGc*k3v(YM{76X2{off3Uit(a^kfKS zm{q;JZ~VP5OTw9eaGL{T@9uyFgJ1St+)&@G>T0&K8Wv|*Kcx#GMXarb4g7UND@p93 zL~&L}If+crNedichs%Clk4&rVUK{cGVTVD8TfO@lL?8t*adBaD!QzWWMq_f8p*)rU z*nlymYCx^S{KY>!y4}U$xE^F}&bwqXb^%p^CWsU0M${4T9Y?Y2h772ZR6AK_5%3p< zv%atHX+u4v2|Pj+$PZ>6((h#TF!1`19;au>7wFx@l;0q^Sq3N8jl#fU`%|Eeh#Gr* zCb4lYS=xWnz=DOXfKBQObE`EXJ>y(4KB8kAU_j)N?OZLnpA#eCBVKW*kC;xGa}!=x z$Ff?yb2{q^V0#TAcCfw{LuJKjZAk8IYY&GRcsxT^RntNA98|37{m~O~aH+fVx+8j* zjM6K#b!hGSZ4_8#KRco4H`c)c>2z_l-1-V$s@-<}nTeL1?d4e@`<2qDDifrJESYm7OjZ;<5?9efV&M16#l)CadgtX&XBgkWM6 zS^S7Jt~>-y0w|;6Y&{;l4$}atW)%^pXQ@|Jk~z@paA(fsFxwXdsR9&%$p}q&xKE_@ zz)n4bEsxkC{5VyOy%>~z?=Dq$xz|fh_7o}3wSoFhh5lo$<&D}|sMDwK1e0K6|EImP z3ahG%yS<8l(ji>}QX(x~(n=%U9n#$!1f)|!K)R7lcXv0^-QC@AChz;jbH2;-oQrdD zE>TeS-fONo*ME%h8>VxN%7@M@!YxO&Bm$r2Ug63yB|Oi-I9`>en}5fN>%&9F5$99- zM}hBAD;U8KU0V-M<_2CKZPnjiFc&s3O3Yp)AOExFB_NnP!Faf*+%d1kx5E?BW98RS)K4|84(9f2dqioG7Vl$aB!+aH5R-at22PnICb4{v^)q@7%^7DpzcKZi)#n)5sN%#9U` zkauksdAd}+aq7ri);#*uqVZ|Z^7Elm@s=)uE|&**(y}%q%v1C$W)^<_`8LYY`E6OL z?OOxEZEpsZH!w7)R1ZGDw`+ar&Iz%10uk~L9gj?|&d-lJmTV>+{`7+UH6_{mUr9I= z0sNB3hHqYQ>D{>g5E}^TV|sb4TyuX!BPr*6^OWS@a@u(f%&Fc=&Sd}7g89FjGe;PB zTzDaMwx>25sASbUS6)%`!W$pe~1ju#2;8}8ix`+Kca-|+I1pSGTNm35vIj+McldipPgBi>y+eS5&1H!Naf z=ey=-?Np2$8X~Ga;PAQGjXQ{G8-&5%NZZoo~l%E?Ix_ zlDHvEUYU`ldAx;#%VGN=w&-|{0-8r1{u>OAeG0_!7BJioh#A6?>bkJV6pSlfb2qOz z)yKwr^XF|gG_9RGPW;@$wAv_cn-HN|>$jy7dic82o%%?gHYRwg@ju$fz{7~^RU@bM zic#H0MTS164VU{uJt7@6;}10BXJVw%R*j1FH*b+N){B&Smlf~ZoB$KT4*Jj3QrBB6 zbZ!hwQ(b%yrtb|UL(Xs?hA{W|Jyy4B>QeYT3shRDDOmM4{?4mTbD9*EhxFS`5^q}7 z2A)>NjE)P+DIYlZ_4JempZJQ2X5^rx)L;^$`^;`jND5NetzMF*RPEJ$KL9s!jRj$C ziHsvd`&TQALP~3`lT*hu%xc#2&e_z^=+(g9fgW`xx6e<9a30rABg~p)9*GCVF72j! zYFl56V`$i>HiDKlr&CL!aS^Ba*q=1^f&O)^OpPS<=4)=>n3A)(|M6q-`t2;CP6cIEGGFsbZ zvpvQSIppP*^=r^*AsbhFlV@fqaK5|YioGI*RN83NQfmmaE6PL+CNx)fA^5|Ens#86 z&S@d#pfa(y;(Q(3>r|*aKo>)Td)JedjVzA~^e3p1u~%olRtJW!nOU9y_uJ=trja71 zFRN?8{AAt01(KQi)}QZvM!w2;XMLWOh$9lFC;d;6Kn;A&-jlRS+=ZpzDynfhduu5o zf59tz_b5Zx{LybZI7s(1bk(5ie%c1b6U(?-U;v*3X?OHDX+Tp@eYbBBp4@IHj#Z3p zL#f*GPo5U6nq+r@H|4FBSBhOC^so$(>Zo^1N~?P34i$J0Fdt4L&8C$X-0tA&6dJ*^ z*iXbg#FpEJXR2PT{hsEK)gm3OOwpwYL7W|O=Q*3#Uwo6H-_wOZgoeop|31+jIplVP ztZ!gOY}C>#&J#id?p$7E7k4b3JdOzpe(f#7x_uaZ2H|4oblM+D#JZJ4a!43=LeqF1 z@%Dk|gbb$*B9ep?BpW zI`2+4U#F+9#*?L|6~0$t%4MnZA|1%aU=a8DbXp06vpmo8^7t|WtLCVpJ3q4MtEFm7 zoMslGuF^rymT)1}<7w_<$xd~5)>6&-9Ov7xY6ItdMot;+MuvwFS(*NN$g5EMd?i(r z*dGqq!tUg6e&~?EdWJY-p}hzWr@fGf?1BQt5)&!aTVdPhC)seTe)d0zk`g0gZa4Ic1b9NYaHl0M7%8xTI1}Sa*>LVzRqLC0UQ_-6mtRX$qbxDy zSKZ#wt;Jt76(3YRGY!de)GlIhSBgr}%P!b9d>?|~1nzVnVkQs6lbW3FlTpc@#eHkI z-HJ~dJ05O=)tKL!5?0|IhpI5PlfVnz_0myk!MZm$oK1C+RLNsO5rBQ4@d3;1&0o8% zvw}%0{#I>EwIZb|$Bt`?waDh5EKWRk3rPF(K?Y}5Ua6ZUc1~dCO7>=<$xJ>6f8yzf zP0rn(0uHHI$zTYb>HU-$6MX+!^q(#F|Gh5)z7Fl?T0a({$HHjWhuoZz(o=HM`dky` z(!NjF-I^hYtE?zAQXlM=UKBQ9i~kSCTh^}&f3_PD_mD4AJ^cf^4@A*xYv&5%L?N_^ zyDV&vT2B!VaasZ%OQ{-1GAf4fYx(ZJUW;$DZgKK3EyDl&Ek#rz;o=5_ft)Jg8q(d< zU7l}#Xs%Y0E$8Ci$g~I&+AFK~+iew240hM1e?Am3c4m$2{F<-HczPqLAtCkF)+vh_ zi@P1lV-wtC7P-*)tnLP4T>*&90YYH7(Umw?u~AaN{%vX9KR;mD%(&-ht}d=bd3uYx zB5S3&OKcj{t9O!VU>MTmZeO%WZVr9_K53t|R+WO=4}3}iU;SI>NSoM+xmy3UR9@V_ z>qioDuB+l^d$YdAg0tW5_W?v`~;J{|6CFXnU=Xn)MT%I4&-%=>FoF?J%ikW1K`6YZhz6_sleQ4NH zPc8^F2gdsjcP&05%YR_ASnz8@CFDu;j13QGa1148@wzT6-yMeC3=Ih}5PvUAHdYKW z7m_q<6Z_mO#sf)JI_N@ZkIY3)9${Z3bUjF<1rjsox3PhJWdZ@P){FHb-ZV2x&cdCXwPv~+!#l;p%zp%WC9 z{{}z>|78clqWqu<>eWDTKb98-go{G3RRRl7iv^3(*(>mBA;}^?n47S^8N5lxWxM0^ z{P2!VEiGMfmyO7Bp?*Uo(hHz(`6)yGjgV-^Nx%8;voky}(k3?4=b z3d6tA+ZW?Rv*r8M4zpp2gy=BcqsBvWf)-Ubtfri(#LO!eIyw(L@$sr4<9j$$fHN5O zTPFch@g12ckq_ed;N4N^fL~obp##rn57jE^52s%6rK^^>n*wY|$!&bNvTw z{ooGPQr~*#Yg1rhtY$*{Aoy(aSRa*5(>)gb$Gaa?9V|^)IAtrnTj4N5n0kmQdcf;o zxV(}TL#qztY+%thKbV9442%MMqfXC+(}2UU%?||y<$=-oz`O2pt|sGRZ=U3ywN_#? z>`PA%z`dx|n)}L@fJBr1?nK}HLmQVR;1+$nyg1$;DLd?vT~0GPjx?lIDje+V+ntB{ zN0wq#;?Svf?rYY#?&lm?Q;EUzT%d*Bq7stXjy!;K`XqN2L` zE-(&}`^7~>d>1Y^01+CwW$b|jRaM1gR^!I*RtC*lW{=i25Qul&{yWk+v(r7YdJl)x zIZT#q)*P8dn<`7w(*q(nOl(Za+*YLYUbLqtF=mH8-X%~ykyCn2uH!-5>-TkiZEdHc zIY%H>u{<*!J#IGf&u4&{Z!Ih=yg4{uq}6b+w>Ooq8U00s`5`xrvHH7@8#@r`!>s5HHp$1G)Zg) z>eyqfKw%XNBW4+%v`DdV-gCi+$bQ=p*=sR;g3vG zVbMij8R1m!@(V)i4v&?Asl6D!e za~}3*ts->D5FWGA8MInYPR`F+uE&p1?sa!tlJ(d>e0aaISnZtLLPAK`3jGv(`1hNo zR!dFc*7hgScx_$UiPG!KFLql$GxX_9v}LVs)rGu$*FG{;pV?zVw~8Q=px4UMWBCcT)_(W(Y!2WjGHRODJ&_xv4Wz?> zn@PKI<3uafw<9O-;xzL&K7P~2GSS~`DJURYAZDwpsku74-rI7CW~b&M93Sk{iheN1 zRajeQHB&%EMZ^}5UK2#f`zjD)(6id4k2J4Z;vT3|`zJ6kuC-%Ko0>}E(o*wnhH{~% zjFgnfQiW#|ADW@1)kA8fEI&d^X9TQ_oU96`XDqsK+Q+JV+bdsVM@#zCgr%`!XZYT} z-pYK9inyXhH@@Sbu&{V`r;8a@+U4m#PkhX3+jIOnbmE8W>tGzEd!CDTa2g-(0+A9xw{NB19*^gl1DpAE>gx*EV~I2uHGrAPOp7 z7;|%s<@tIRX*IQYh}?~Ib)l9BN+6D;nS7zoOcmE^JmIBw**=N6bJ2MZNT+@hevzfV zo9i3Co(RfvH00T9_~X!)DSIc+X1sa8DobGZ{MoI_CtGHOEUbArZzq=j9Z)|sUtVI5 zg?rOcelGaUdUQBW66(3SVG-g>Pd^C8J}7y(xMnm^#-?zI=R?{ezBuR9j|?dca6Dap z(s+t>ww?S1a?W+$hoP;XnLd8}ko%_p8{?fn$pXa4XruN#0n9Ed2w5D{vkW`>VvAb3 zgnztQ=4?R%luHh`gVrB`@(m3TV3|QDZ1-_J7yI<91CT5A{T*LT=?d0ZYE}V%t4s># zWWB0S|{or@TZKHX>khr#8ysa@rm#5ppX( zZG^y=mzS$mS<+-17{qclDEgbu^YHFz^ zU_A+}C_nG1r}PjI_`5=WlKTk?*#PBBXS{V#M8RoiE%51W+&hyp#r-{xbYoHEoMU8s z&R5gsbmlI?`Nb3A9W8s#?XktT>H7GE8KYkl>qwr&H8!KzE&1m=6X@g_z&uRI?)b(? zxic(bYj2H1|0a5y#R$=;`Sb!1vUsE~6!!r?mP_vnEe*rm!h$}L+hJ*GncHbzNRV%8 zS}~r>5MyJ)xAeZuh{Bf7`O0LsGRYc?+i8c;q9QIs;Xl}YRZ#%B5rM&}6h z#Nudw?ev!nI%ZCvwC(B(DWw^arb z-m8$y_vMlGchg$>YO2Rt*6Atbw=zD>>pQGwZEfUepNY7Q-sGf#f8?97x_*d6hE%eX z$0~{6!BLIp>isSx37$RIgpS$At$TKX%1QLy>B6m2bX3CDOvt#bpTQZww5%*60j7Ts zr|BA_-&^t5faRnd5jA_aW@)=|c#ST5#6()ToU9nrw=>C7m8YD>{1BrX2T!uddxaL@ z38SArWo{ZI9yRxy!ThfmcSIsE9EsDSRd3u*Zb16_ZXiXU0-uZK*5}UwcGrBbGKM4- zNYAI+kDj(WZhezfGo5Ixg8e*qT?Hh_N=NQZD9|D zz+maY8{Xu`+H_kHq6SmH?EQSGGL5mhru12##D63(bK+G7Ye@NunBseSm|FmnM0Ix<@JhvOwb)6)g*%~!VjP#J@Mv5#2I=2ynR9vY7s(Jjt4 zsms$M%9~s9TmR8>X){9hcrY;(HM*FlMYXXr%Y{zZl$w1lqQuB(>`bb5UVPjaLw1PK zg{UhRn!F%y1?Hu>v1dbZe!s<=ETw4U3CO7qSmrFaQ8c~gyKZ^hxXtES+44%ILrBP!AWO#+baGL_c^6!xXJef9^k=NOgA*B$M!os3|bwPd5=Y5_3TkmJ`+@ zT0(4}E4OD`Tpao%VV|_a}MouAO^FG3%k)KuckpILZOIZmKd27R9^Xur%(jRp> z?bRex738hKTVA=boBnlwK86=^<8X!JM*``xj~;htBE>Z?`?|6U<~_JLK9uHFxD25n zv$8tF%>EAp8EWHXzMo+e<-Ila1LvV7yK}u0X(zQ%&Ck;c2);CkPVXqQ1~qF) zs>C-Z9I`kO3(xEfLh^!6;so|<5K`)$A1#J+R9D@|P!qgg>~hWiG?}RX&sji=TC1a^ z#tQh&*cnI&a8lmcLxb~iNlEKTC|Uk8>~5Gi0fafNQ~jAk1yP=2XH>E4?w5}Tc{OcE zxK=ZE^rSQu!=)%Gjew0>VmGl!Nh=wX9nK60&NHBNJeo$~GDo zH`_q9(puELA7rV6GE4$Y<(%b<^3 z6ChxCF8jgz#h(X@5fE$%9_&t5`g4ty7PSYp0(aBe`Z~4@2}hDh79kCj|1pb`pYcs}R9&rQxTDSV66U4ZW`F;H*yd)>LVaup={zG$} zb``D*#id-o!GaZCSx=AwmR02e0=Q*v&wCq7tz;co{92_Z*GYKdhQxgHU{UF7vsa$= zQu48eWpEj3VgHzshWc%Im*E7ui!X<17BtIEX1uuC*PMCHE;Ma7-Gfg$9-h&0BN)7U^GCC@XlSsOL;6I1#&b z)r__vGxF9R6Enp?|C=B8o!4fc9L&r>(iFV$@(apb7L6!i|I5lU6nu#Is-&KT#XP5( zJGHV6^D-;X+1ShKW3DmcMBQ11l+aj^{aRxiwad`pPJU1#zmAu zA|ei(4yXP&X!4x0A6z%HwJY;f;qn|O#>y$>o9b(gXRoZ13@HjM*_^vxREqh-RdbJ! zuykHn7q+Miz6DzY_H3XS0-@Mz|{ifgVuj$i8b2f@o`Lf$Yz_(4%ivl(W zYeH3orELbKQV>t)s#yzw2ERM-)vVJtu;yFHh?@IhZSo)hxm8glk*-yRaIZ z@P{|tr-f_O4$Hx^ZjcvE?yFVn>G;UOVJ1kqAFaih(anluh3cL!>5;lSujwq2SH8-? z^4kxZ7FzSTkCA;@ZK!kAPSujxS{>kr#ry^Nyf0nb+QC}N+RNlqIR+B@l(@Ik&%iV- zFup%;j!Ur9D&&2P z81M6}MbjsurK0qYfgkRh)?V}AWxvCP#^?AEnp-()ScrWHqSe&Z+^_f;-Ep4()s{^P zcEaY-wuY|})M~m9K!=Umzk>R57%~3j(Fr#BXfOg z&=>GBJVIwV_ubs57Nq|vBQ@)XbHlK_FJ`tc|9F&G3`d@RJ7o+q;A1n%&c}SWuDeXiVfuI7T!=}7_V*c zJZu0LZm**q@=WnSXrRC zaY-PfF8%!x?o`{d%HF?{7+N-v#Ew&K-Hz|tzNB!}pEh(2RUI^OlzOU<8Qk_Bdt*gB zj@Z|0mpvW_U(`QBFdKt)*g7dFS<-J}X}*`v{N97R5s?tWaG#g40&VhQN=4OnZiR8}3bA!VO0>!k+;Djg1Y;ob0x+W@VMfrDNP3@&NI&fa71Ngn%E^v zn(Zi5I99*{WVD=BSgDzvJh%ZKb@YYB!ZziljOp1E4-}8-&J1(50Gjum@3$wXUbeWQ z%;L^&N^T8&hW~A1Ewh07mK>>6ZZHkmh6Xes;n}ggDQnc+4q@<;G3UhJcU(mF9+D{A9Ay@ngC9JE&3Oka1?h899ko~?`tM;{IM}EpNhG~V`R}^8 zQB%h49=46A71(_60wnzA$Wu9ibF0Nz#-KphyCzA>ZD@6j@ypVWl_-AH7nIg_x>8Fa zaava%q2fV3cPBPvo4+R*m35=^lypaCgyLo}cnGn4(v*iY-{)iTL@*kk!IpoD@W#ii z9Lgd{K5`j>IFaz{V=f1|0uLB+z+0m1x5IqJ6$}e!!U%hm8nDLh&m|9FA$LfyVz=xC z%CsNvf(v4*KNq5TJE4oBNZzp9L+MLBYc?=eu6LJ)kBV~F;-TShv2mGqYo_$|B9Old z`MOM~x{!YqWaGbUWn7_$*Fiwhhf%{aRiAEMJD)|xrP0MI4Q;E$o%@N2GtY!c4d}ht zFw&tOIO5SeADD|h4VDtZiRREr(Ni{C85n`pt@9Ody2Vu@T}b#nz~DL}t_&QZ1|w7&Ca2(kx?r~PV&md=0csH z&kj88&vJW97`ijIM5ufGmM5GO&7=!B(yQAa=6?&QcJ|K%e9_`bX?Z~wHsfffPO?FD zM#PTEy&O#QisQUkBTu>HBRh+ZonGr$ZRB+KHo?-!Dw@DzM1w*9g$@G2vceBO*fxCc zir<`-{&Br^W90(*I%{X=#rv9s=0~ia#B&32okU!QbZS_bu7P*kb@-0I8i!!~rL}^$ z-(D5R{Z6QGXQ-#+5n;_!DMsZ%T!YbjE-$j`f7<6gwPo{g2h;wFR42q2cDk610P|S( z$J@;!zVA%WLQh-yUj~}U;7-6bVTY;$U)^7&czx%x|< za$I7h6{J`zH&T_?m@z*?faN2(zPm0M4dKVPPW-X!Tjk=Kv8!xa3EtSeI84ObJFHN) zL=uq>QgCJzAls;5QV1cLc^&(#B2RhN0{Rcs>v87g}j* z28&O(yZK|-9fs4*U!V6e!Al{S6xhP!Xp|lYBq7r*epgPZO@F8w^^G-`4V(RGf*qGy zd`@TU>@fYP?>~J48^Ca7Jao+lD?K+tUgn8xZV>vEaDx3mNF4p&{h5x-#Xpx97fFoY z^3>=39Clfgc?@qrPHgH7407V*R0;WQf@ttQXjWpJbGjY8kS7EDKg2v7JiH!2rcLpp z%8>1m)~sD@dQ1gTG?291WY935Y&-?R#y|9GMrY}JAZHvO#z<_2goNNW8X_pp*E_h4 zW`{m8A_YJ2n0@5rgncwJGAseOWRzvy7M#I*`?aX3$V~>7Jy3oDFwy|gV!I8vGQrpk zX5-mDNst1Mdw7s5-UE!-?d|PfG3FNgdHM;O6EHhw<7Hy{Q|h4T&dkg*9bM(+MP2hn zej%{Y{hG`2E|bfFAPv7wEM&oR2`#}tRBlOQZar195QRazQP2QJY~0?uW!dV*Lh1H@ zOP+a?wNmirWwCI4?spu9J4kC*6ZNe0p#Z(y1TP~xTV>|sIa_g9>fW&f#;67$wr}Wd z?}32oWNZJOXMd*R@_^Ur9oTpQAkAd6koV?hz1Cqr1PMU1ryJa~2nM@n$ zFSR{aJFo@sCgib2=$)vru>H>5Jzzpf4~7A-j`QB~bfsojYO2njTD<{YCJzHcky8<| zc(3@BR=ELQTkHuj?=9IoJuB@BX`-sogepIOf54Zx>ssPG55^uWD}$C=Eqc+ZSFJ)T zD;*ED=DWfPU<3?U9QKASK_}FdwY~j!QkTua1|Z1I)j2(mLnp28F88McHJ|h^qN;!O zFILMjv^2#bss-wazP{OsPW)d)jg^+;t$lp}R9zxXq}(F@>9EpbdTVP9l(7sQA*qn? zH{~239|wNyo&&nWMZx$;p9u#CY zk$;DAo~2qw`MIjR7#Gxt_`rwD8ee?cIs}AgyyW?P4vU))iRm5xDbTa_RocoJ)h}(d0V+v$I zApqDwN)8c(xL$1e4duuzU(Ox2o%%k%I@aI1ut=DeWHp=6VgEo!cVy)*_~PN*?dHv6 zrTIb#3!-kyf6fBtVPSuq@wi`cP;=mx2D~95Noj<*@gAQfvpM_)su^h*7;`VdR+*0^ zVwzynNl9t)&eN$^HlZQ}1&8W4e8bIY5g?svZpZofhG1Abu7q3YqFAh^W}JuKA6`2lS1gAOyWI`&>oDN3DDG-yt_A9YAbC;Yc)bx0uU)-kKp6 zp7K3Yun(Wj{OAEPTrgW7o6E8Jd-Ws>3Q+q_R@(maGd1%5Yte7m=366;=~_QYFXyEC znZG7X;&%8Et5Rza--FMHJRPWsOyVl>b1*ua0kfIf! z$J>yfqUtUI7zlSVKC$-B_PjrTN;~&r%qFLW78=}SvZ)2`;C~ir)L`&TgN`${Z+}Z6 zx0N78O(m1m)6;{;k(%Pn4VBA)E2c$PN#t-*b77Hpv@`{o%#)M)PvG@Hm5MsF+7V=> z8)_XQJWe+(0IFasNmdRJ0bcpF7XDDs^`K#5`YGja71q7FKkEdKf)9s7%p7{S>MPI z-Z|X6lLeh;pWOxTz&@oLHeRq=X>=ac6V-`ho0-2CW|5E<2}<$Sp)wz{)0XZ^-Jl93 zB1;$qK)Y=NT!I~Xdg&>XsZ@c7^j~zU0X?*dM6L$<`k@wb35zy%5zjqU6ow` zom$%U)^PerjzC>n-%}mMig^Rrr>_fsG}PO|9u_c2Ng?+`NbSF0ZKt(~f&b0YRPX6! zkqG&@!A3p>o#B`}v$612oKAs+3!FuM;PQO-A4dGyMi`bYppnWe!m63eCiLeqUfd0PTy{ zybxj>oY=UC+^hmDQ13_zw1sW6e++uUfmBNtOM^*uL)z7uxv3F}zscIVi?^AXnURij zWaZ?*vf7?XyD!%D$$zhG^guK`$?dpTYkgb_bOuvXe4#t!ps~q!85BvNvXj#e$JNCJ zfY$(;=%?j_!!Rtk5&>_LL5pYclXl8AB(=zRNOfs_ADj_{QH{QQYHg&%qZ`RzEC#C~ z2^>>gYHE<=B4s0Dm1PwO`7O@?2{CT51uTFn^ZB1(nM*)4TdB*7zKBZDgE~LYmF1Y| z$bK{mEO5QCD^DhIS2F%(GC4Y;(pq2?#7oa4%YOP+>QMZuSFd09khW#EXJlq7jMTRl zBq+zPcsD*AEeknHqXiDl0(<1CNUM`uY>TTJ0+o6g-~tZUL}S`2mX2Pf~HL>svL;7H?Q2IX#urp<2MB zFf;;W*FR(o3=GW7LDv@p)6-vCg(mLG9$Gsj@Z#f<|G1RnN(F(0g*5I-DdfA*%u)RR&R(D zv)b2J&IDVls}c;8v8GG2U<-BCo{B~{4(@$p(AeIs*i>|-u)E(Q8NP!CdLA&t|6uW8P-WF+`%K+{2 z)R@z0<3~n@5U7+$=7V%be~ylc0VufO9vw^Fq@<*QyW5WzGu2=-WHlR$h>Ai+xwZzq zC`8;&EbWIr`(~x05-*99WfT;8qYaQNDoPq{?*De9x4l;^MY=vBWoM`|4BprrN-i&{ z)C(rFIrP% zWTZC=oylQ?+htVL{ftP|K1;>Vavl z9ToEH6*%bH+MUKlB7DG~VwTfxl#FBKBqQ@)?wX9H(+=chLPmCgg@w)hn5&I?aIvc_ zquq2iTWP4jDNQt`56T*!`xqXjKHr@z^w_ywyOuv3hyJpoNQjRwRBc#!Xiz_d(bnei ze6rik6}q19>@C*q#;3$SLXD}yND399l=9-;eSN1VC-f#oo0tp+IuC|$XNPS^vD`KG z+Xw{JZl{zweZTgHlACftC!6;2msIVy6d(R$DUr_x6#`5tYG8CMwb%(TWmv$tm(4sTW4N`BhN;& zO(nv@<|pWHMK+J48HB%;Wx}i{QrXYN;^@Yas0dsFEFnsvF3ohJm?v{B;n~D7!de$G3?c#S$qBin=#9)NE>l)cqo$R5( zb-z|;WOK6aMuw=Gy{1cv^k$aZbu$Y^G7)-kWIS-1&7uSyOU6u zFEKGcIc-ktAMg>9U9U?!a0Sqf#k>^rrcm&w7V~Vg4tQ^a7CTCHS~L<-2tX<8@Hm~B zfyvd0=dqP!%MkxN+tzj((1e3Z%%kUQ zwrtXhU!Xj>e-$Wi4#&ZVNqIcyDXgr^@X_KgF7r&K6s?j_o#SrD5#6XF?XME-_J|gk zV@cq;IS2Qut01u#ogKw#J~r6dAJK4dl!puXM<>Avk}(=Vlg_hikoSr02}j1ZKU0a~ zOfXe%sP2e^)C{Ve^VOTs-*Z}YnhOc}jNUOZQ&XF)2W3TcgY%I98EM9H3O|EJJ7SHX z@4x^pA(y?u#-7U+LfOdd?noRHxkxXylPNHH63tYFhKC;xmzW9ar6(rNC8_^M#dSa47G!}XJ?C8{j0?lE1x&r=@NkBVW@7*SKPKtmTPh zIy$!gS+4s;eas3{x6Ar~H3&RpB%w%%GaWr9*4D4unqWr%{#{=)NK3oIAGm~j_%1(m zbo<+&TsR^wF5sC`VA7w<)h0FI_2z9)=4c|GSUJ}B2Xv3x%dJsBiO!P1&d$oxtaF5s z24Fq~(8etsOL62u(!tms!%Ber>DT_kNSK@7*>U18^+IAJzk{Z5_UBf>s=hIhFp%Bc z@SD|+YXiHqw2U-g3ZGrDcZrl7^f)xScbHO8P<#nGUnY1nkic&EgyACQcMV8t)S2WJ zjAyr8Xr&@!F10o6%l})Zhscf`Niu=)SjFkGgY=NZp-Y(FZyuDS88edM*Vd2fe9Pwf zP-O*kTvYONoCn?>DtjTKY^av1{r+wK z`W1xi_jM?Z@XHiu7m)R@o^b5+LTVZX8_m$s;)^$B!8A;%MLqa*=v%R)h=_>#;*4<_ z{La=^pt*dvx`vXHV%x+lmca_{6f-@rA+EiviCh$km2tR8bGBooSy^MdfdLp?Fxc*i{Ic4)osZZ~qmM-D%G>c)7&rc69cQBs*G1M`h{oB6@W6 z_H0}Gkx4l&krk@!KkdA~Bl7tjQtkUnwxFOO&Jb-rI|HX1S&V^6VK*_!nUTg?f=$i&>{uuHquo9II5%%>&-*)4ddo0j9A#u!E81kG9N^5{E4<>E*!1{%m>Sgn`MZXd%9y8fb zkD$%R7s$x})C(9yp5C}_b+@-;+*`ttlKykDc=KLu#=z*;7MS3^%N%a{Bc>gj&cQ$m zcav}vDYC0XXY%RxF%?qBw!%_wIUY&+z@YPV6UbyVI{SP<=ajCHk(bA1x@LXs(iwtB zxaQ_V#{kzG?gs-ON^! zr?D+s@-Z3~LPSUynE;x*CMv|E;5?187}BS7lGDIw>@V<5 z9Paud??7d>W6c{c&sotSbX7VnFZ`%~+M6r_Ki9@~nX%+T6^|{xI4;7;32>6jQ_84R z>(G<45^KR+GGvwSWOeH;&`oi65Fu&~C#W(3HjY)J~| z;c0n!dHI>qzmgZS?7B#O;IVJQASK{YWC3D4lwPC=p3gwLFYe0F*RiK<&CuEHmd|x> zd3Bcn9UYH|s30v3kNBa8LF+oDqrLqd4U?R*%;eNm_ee{cB@z4U*su3=&bL>59{sl# z)k7)ddg=NfD?UCp3uZm4W^V63K7aP?@e$!|%DZRJp20mWO_*nY&XwQYU1r3AKY8{= ML|V8|P{;d!0RzV&dH?_b literal 0 HcmV?d00001 diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index d65a9892ff..d3771d102d 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -85,6 +85,7 @@ interface IState { export default class CreateRoomDialog extends React.Component { private readonly askToJoinEnabled: boolean; private readonly advancedSettingsEnabled: boolean; + private readonly allowCreatingPublicRooms: boolean; private readonly supportsRestricted: boolean; private nameField = createRef(); private aliasField = createRef(); @@ -94,11 +95,13 @@ export default class CreateRoomDialog extends React.Component { this.askToJoinEnabled = SettingsStore.getValue("feature_ask_to_join"); this.advancedSettingsEnabled = SettingsStore.getValue(UIFeature.AdvancedSettings); + this.allowCreatingPublicRooms = SettingsStore.getValue(UIFeature.AllowCreatingPublicRooms); this.supportsRestricted = !!this.props.parentSpace; + const defaultPublic = this.allowCreatingPublicRooms && this.props.defaultPublic; let joinRule = JoinRule.Invite; - if (this.props.defaultPublic) { + if (defaultPublic) { joinRule = JoinRule.Public; } else if (this.supportsRestricted) { joinRule = JoinRule.Restricted; @@ -106,7 +109,7 @@ export default class CreateRoomDialog extends React.Component { const cli = MatrixClientPeg.safeGet(); this.state = { - isPublicKnockRoom: this.props.defaultPublic || false, + isPublicKnockRoom: defaultPublic || false, isEncrypted: this.props.defaultEncrypted ?? privateShouldBeEncrypted(cli), joinRule, name: this.props.defaultName || "", @@ -419,7 +422,7 @@ export default class CreateRoomDialog extends React.Component { labelKnock={ this.askToJoinEnabled ? _t("room_settings|security|join_rule_knock") : undefined } - labelPublic={_t("common|public_room")} + labelPublic={this.allowCreatingPublicRooms ? _t("common|public_room") : undefined} labelRestricted={ this.supportsRestricted ? _t("create_room|join_rule_restricted") : undefined } diff --git a/src/components/views/elements/JoinRuleDropdown.tsx b/src/components/views/elements/JoinRuleDropdown.tsx index cded54ffd6..860b87fa00 100644 --- a/src/components/views/elements/JoinRuleDropdown.tsx +++ b/src/components/views/elements/JoinRuleDropdown.tsx @@ -19,7 +19,7 @@ interface IProps { width?: number; labelInvite: string; labelKnock?: string; - labelPublic: string; + labelPublic?: string; labelRestricted?: string; // if omitted then this option will be hidden, e.g if unsupported onChange(value: JoinRule): void; } @@ -38,11 +38,18 @@ const JoinRuleDropdown: React.FC = ({
{labelInvite}
, -
- {labelPublic} -
, ] as NonEmptyArray; + if (labelPublic) { + options.push( + ( +
+ {labelPublic} +
+ ) as ReactElement & { key: string }, + ); + } + if (labelKnock) { options.unshift( ( @@ -72,6 +79,7 @@ const JoinRuleDropdown: React.FC = ({ menuWidth={width} value={value} label={label} + disabled={options.length === 1} > {options} diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx index fd5b0cb1c0..6b881e33a8 100644 --- a/src/components/views/spaces/SpaceCreateMenu.tsx +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -45,6 +45,8 @@ import defaultDispatcher from "../../../dispatcher/dispatcher"; import { Action } from "../../../dispatcher/actions"; import { Filter } from "../dialogs/spotlight/Filter"; import { type OpenSpotlightPayload } from "../../../dispatcher/payloads/OpenSpotlightPayload.ts"; +import { useSettingValue } from "../../../hooks/useSettings.ts"; +import { UIFeature } from "../../../settings/UIFeature.ts"; export const createSpace = async ( client: MatrixClient, @@ -212,7 +214,10 @@ const SpaceCreateMenu: React.FC<{ onFinished(): void; }> = ({ onFinished }) => { const cli = useMatrixClientContext(); - const [visibility, setVisibility] = useState(null); + const settingAllowPublicSpaces = useSettingValue(UIFeature.AllowCreatingPublicSpaces); + const [visibility, setVisibility] = useState( + settingAllowPublicSpaces === false ? Visibility.Private : null, + ); const [busy, setBusy] = useState(false); const [name, setName] = useState(""); @@ -303,16 +308,20 @@ const SpaceCreateMenu: React.FC<{ } else { body = ( - setVisibility(null)} - title={_t("action|go_back")} - /> + {settingAllowPublicSpaces && ( + setVisibility(null)} + title={_t("action|go_back")} + /> + )}

{visibility === Visibility.Public ? _t("create_space|public_heading") - : _t("create_space|private_heading")} + : settingAllowPublicSpaces + ? _t("create_space|private_heading") + : _t("create_space|private_only_heading")}

{_t("create_space|add_details_prompt")} {_t("create_space|add_details_prompt_2")} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 90a5ecc683..a82c06a124 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -718,6 +718,7 @@ "personal_space_description": "A private space to organise your rooms", "private_description": "Invite only, best for yourself or teams", "private_heading": "Your private space", + "private_only_heading": "Your space", "private_personal_description": "Make sure the right people have access to %(name)s", "private_personal_heading": "Who are you working with?", "private_space": "Me and my teammates", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index f0f52bbbf8..0d272a46c5 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -1425,6 +1425,14 @@ export const SETTINGS: Settings = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.AllowCreatingPublicSpaces]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, + [UIFeature.AllowCreatingPublicRooms]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, // Electron-specific settings, they are stored by Electron and set/read over an IPC. // We store them over there are they are necessary to know before the renderer process launches. diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 4aef725d4a..12b0c7c089 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -25,6 +25,8 @@ export const enum UIFeature { RoomHistorySettings = "UIFeature.roomHistorySettings", TimelineEnableRelativeDates = "UIFeature.timelineEnableRelativeDates", BulkUnverifiedSessionsReminder = "UIFeature.BulkUnverifiedSessionsReminder", + AllowCreatingPublicRooms = "UIFeature.allowCreatingPublicRooms", + AllowCreatingPublicSpaces = "UIFeature.allowCreatingPublicSpaces", } export enum UIComponent { diff --git a/test/unit-tests/components/views/dialogs/CreateRoomDialog-test.tsx b/test/unit-tests/components/views/dialogs/CreateRoomDialog-test.tsx index 1f6b2c2217..5fa0251129 100644 --- a/test/unit-tests/components/views/dialogs/CreateRoomDialog-test.tsx +++ b/test/unit-tests/components/views/dialogs/CreateRoomDialog-test.tsx @@ -17,25 +17,30 @@ import { UIFeature } from "../../../../../src/settings/UIFeature"; describe("", () => { const userId = "@alice:server.org"; - const mockClient = getMockClientWithEventEmitter({ - ...mockClientMethodsUser(userId), - getDomain: jest.fn().mockReturnValue("server.org"), - getClientWellKnown: jest.fn(), - doesServerForceEncryptionForPreset: jest.fn(), - // make every alias available - getRoomIdForAlias: jest.fn().mockRejectedValue(new MatrixError({ errcode: "M_NOT_FOUND" })), - }); const getE2eeEnableToggleInputElement = () => screen.getByLabelText("Enable end-to-end encryption"); // labelled toggle switch doesn't set the disabled attribute, only aria-disabled const getE2eeEnableToggleIsDisabled = () => getE2eeEnableToggleInputElement().getAttribute("aria-disabled") === "true"; + let mockClient: ReturnType; beforeEach(() => { + mockClient = getMockClientWithEventEmitter({ + ...mockClientMethodsUser(userId), + getDomain: jest.fn().mockReturnValue("server.org"), + getClientWellKnown: jest.fn(), + doesServerForceEncryptionForPreset: jest.fn(), + // make every alias available + getRoomIdForAlias: jest.fn().mockRejectedValue(new MatrixError({ errcode: "M_NOT_FOUND" })), + }); mockClient.doesServerForceEncryptionForPreset.mockResolvedValue(false); mockClient.getClientWellKnown.mockReturnValue({}); }); + afterEach(() => { + jest.restoreAllMocks(); + }); + const getComponent = (props = {}) => render(); it("should default to private room", async () => { diff --git a/test/unit-tests/components/views/spaces/SpaceCreateMenu-test.tsx b/test/unit-tests/components/views/spaces/SpaceCreateMenu-test.tsx new file mode 100644 index 0000000000..ad8c3ad86e --- /dev/null +++ b/test/unit-tests/components/views/spaces/SpaceCreateMenu-test.tsx @@ -0,0 +1,121 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2022 The Matrix.org Foundation C.I.C. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import React from "react"; +import { render, cleanup } from "jest-matrix-react"; +import { type MatrixClient } from "matrix-js-sdk/src/matrix"; +import userEvent from "@testing-library/user-event"; +import { MatrixError } from "matrix-js-sdk/src/matrix"; +import { type MockedObject } from "jest-mock"; + +import SpaceCreateMenu from "../../../../../src/components/views/spaces/SpaceCreateMenu"; +import { + getMockClientWithEventEmitter, + mockClientMethodsRooms, + mockClientMethodsServer, + mockClientMethodsUser, + withClientContextRenderOptions, +} from "../../../../test-utils"; +import { UIFeature } from "../../../../../src/settings/UIFeature"; +import SettingsStore from "../../../../../src/settings/SettingsStore"; + +describe("", () => { + let client: MockedObject; + + beforeEach(() => { + client = getMockClientWithEventEmitter({ + ...mockClientMethodsUser(), + ...mockClientMethodsServer(), + ...mockClientMethodsRooms(), + createRoom: jest.fn(), + getRoomIdForAlias: jest.fn().mockImplementation(async () => { + throw new MatrixError({ errcode: "M_NOT_FOUND", error: "Test says no alias found" }, 404); + }), + }); + }); + + afterEach(() => { + cleanup(); + jest.restoreAllMocks(); + }); + + it("should render", async () => { + const { asFragment } = render( + , + withClientContextRenderOptions(client), + ); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should be able to create a public space", async () => { + const onFinished = jest.fn(); + client.createRoom.mockResolvedValue({ room_id: "!room:id" }); + const { getByText, getByLabelText } = render( + , + withClientContextRenderOptions(client), + ); + await userEvent.click(getByText("Public")); + await userEvent.type(getByLabelText("Name"), "My Name"); + await userEvent.type(getByLabelText("Address"), "foobar"); + await userEvent.type(getByLabelText("Description"), "A description"); + await userEvent.click(getByText("Create")); + expect(onFinished).toHaveBeenCalledTimes(1); + expect(client.createRoom).toHaveBeenCalledWith({ + creation_content: { type: "m.space" }, + initial_state: [ + { content: { guest_access: "can_join" }, state_key: "", type: "m.room.guest_access" }, + { content: { history_visibility: "world_readable" }, type: "m.room.history_visibility" }, + ], + name: "My Name", + power_level_content_override: { + events_default: 100, + invite: 0, + }, + preset: "public_chat", + room_alias_name: "my-namefoobar", + topic: "A description", + visibility: "private", + }); + }); + + it("should be prompted to automatically create a private space when configured", async () => { + const realGetValue = SettingsStore.getValue; + jest.spyOn(SettingsStore, "getValue").mockImplementation((name, roomId) => { + if (name === UIFeature.AllowCreatingPublicSpaces) { + return false; + } + return realGetValue(name, roomId); + }); + const onFinished = jest.fn(); + client.createRoom.mockResolvedValue({ room_id: "!room:id" }); + const { getByText, getByLabelText } = render( + , + withClientContextRenderOptions(client), + ); + await userEvent.type(getByLabelText("Name"), "My Name"); + await userEvent.type(getByLabelText("Description"), "A description"); + await userEvent.click(getByText("Create")); + expect(onFinished).toHaveBeenCalledTimes(1); + expect(client.createRoom).toHaveBeenCalledWith({ + creation_content: { type: "m.space" }, + initial_state: [ + { content: { guest_access: "can_join" }, state_key: "", type: "m.room.guest_access" }, + { content: { history_visibility: "invited" }, type: "m.room.history_visibility" }, + ], + name: "My Name", + power_level_content_override: { + events_default: 100, + invite: 50, + }, + room_alias_name: undefined, + preset: "private_chat", + topic: "A description", + visibility: "private", + }); + }); +}); diff --git a/test/unit-tests/components/views/spaces/__snapshots__/SpaceCreateMenu-test.tsx.snap b/test/unit-tests/components/views/spaces/__snapshots__/SpaceCreateMenu-test.tsx.snap new file mode 100644 index 0000000000..f8e1b4e6c9 --- /dev/null +++ b/test/unit-tests/components/views/spaces/__snapshots__/SpaceCreateMenu-test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render 1`] = ``;