From de3a9bc593b28ea43ee0c9ef016e94d9001482b9 Mon Sep 17 00:00:00 2001 From: Thor Arne Johansen Date: Fri, 15 Mar 2024 12:38:15 +0100 Subject: [PATCH] Get upstream changes via verji#develop (#13) * Update `@vector-im/compound-design-tokens` in package.json (#12339) * Change 'type' prop on badges to 'forceDot' (#12327) * Change 'type' prop on badges tio 'forceDot' Which, hopefully, better represents what it actually does. Tidies up some of the logic. Split out from https://github.com/matrix-org/matrix-react-sdk/pull/12254 * Missed a file * More comments * Oops, there is no count here. * Back out the logic refactor of StatelessNotificationBadge because it was also updating the logic for mark as unread badges and rewriting the ternary to the previous logic would be quite complex. * Fix doc comment Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Clarify doc on displaying the count * Update doc for the forceDot param here too. --------- Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * [Backport staging] Update `@vector-im/compound-design-tokens` in package.json (#12340) (cherry picked from commit e3ba643bdce30e8dc5565c7653c1f1f775928fce) Co-authored-by: Florian Duros * Fix the image view (#12341) * TAC: Fix hover state when expanded (#12337) * Fix TAC hover state * Add playwright test * Update playwright snapshot after last compound style changes * v3.95.0-rc.0 * v3.95.0 * Reset matrix-js-sdk back to develop branch * TAC: Order rooms by most recent after notification level (#12329) * Order room by thread timestamp * Fix key errors in test * Update jest snapshots * Update snapshots * Rename alpha/beta to numbers * Add playwright test * Replace forceCount prop with hideIfDot (#12344) This replaces the `forceCount` prop on room badge components with `hideIfDot` which hopefully gives a better idea of what it does, since forceCount did not really force a count. Also remove the prop where it was just passing the default value anyway. --------- Co-authored-by: Florian Duros Co-authored-by: David Baker Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Co-authored-by: ElementRobot Co-authored-by: Robin --- CHANGELOG.md | 6 + package.json | 4 +- .../spaces/threads-activity-centre/index.ts | 51 +++-- .../threadsActivityCentre.spec.ts | 39 ++-- .../tac-hovered-expanded-linux.png | Bin 0 -> 14993 bytes .../tac-hovered-linux.png | Bin 0 -> 5889 bytes .../tac-panel-mix-unread-linux.png | Bin 8147 -> 6969 bytes .../tac-panel-notification-unread-linux.png | Bin 8158 -> 6972 bytes res/css/_common.pcss | 6 + .../structures/_ThreadsActivityCentre.pcss | 6 + .../views/avatars/DecoratedRoomAvatar.tsx | 10 +- src/components/views/rooms/EventTile.tsx | 2 +- src/components/views/rooms/ExtraTile.tsx | 2 +- .../views/rooms/NotificationBadge.tsx | 15 +- .../StatelessNotificationBadge.tsx | 26 ++- .../UnreadNotificationBadge.tsx | 10 +- .../views/rooms/RoomBreadcrumbs.tsx | 2 +- src/components/views/rooms/RoomSublist.tsx | 2 +- src/components/views/rooms/RoomTile.tsx | 6 +- .../views/spaces/SpaceTreeLevel.tsx | 1 - .../ThreadsActivityCentre.tsx | 2 +- .../useUnreadThreadRooms.ts | 29 ++- .../StatelessNotificationBadge-test.tsx | 19 ++ .../spaces/ThreadsActivityCentre-test.tsx | 78 ++++++-- .../ThreadsActivityCentre-test.tsx.snap | 177 +++++++++++++++++- yarn.lock | 4 +- 26 files changed, 404 insertions(+), 93 deletions(-) create mode 100644 playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-hovered-expanded-linux.png create mode 100644 playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-hovered-linux.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 563f34e1dd9..bdcf0d86117 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Changes in [3.95.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.95.0) (2024-03-14) +===================================================================================================== +## 🐛 Bug Fixes + +* Update `@vector-im/compound-design-tokens` in package.json ([#12340](https://github.com/matrix-org/matrix-react-sdk/pull/12340)). + Changes in [3.94.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.94.0) (2024-03-12) ===================================================================================================== ## ✨ Features diff --git a/package.json b/package.json index 36fc01ecb40..ee387bcf6e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.94.0", + "version": "3.95.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -75,7 +75,7 @@ "@matrix-org/spec": "^1.7.0", "@sentry/browser": "^7.0.0", "@testing-library/react-hooks": "^8.0.1", - "@vector-im/compound-design-tokens": "^1.0.0", + "@vector-im/compound-design-tokens": "^1.2.0", "@vector-im/compound-web": "^3.1.1", "@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4", diff --git a/playwright/e2e/spaces/threads-activity-centre/index.ts b/playwright/e2e/spaces/threads-activity-centre/index.ts index b0fcd4648a8..4360ddb9816 100644 --- a/playwright/e2e/spaces/threads-activity-centre/index.ts +++ b/playwright/e2e/spaces/threads-activity-centre/index.ts @@ -30,30 +30,30 @@ import { ElementAppPage } from "../../../pages/ElementAppPage"; * - Invite the bot to both rooms and ensure that it has joined */ export const test = base.extend<{ - roomAlphaName?: string; - roomAlpha: { name: string; roomId: string }; - roomBetaName?: string; - roomBeta: { name: string; roomId: string }; + room1Name?: string; + room1: { name: string; roomId: string }; + room2Name?: string; + room2: { name: string; roomId: string }; msg: MessageBuilder; util: Helpers; }>({ displayName: "Mae", botCreateOpts: { displayName: "Other User" }, - roomAlphaName: "Room Alpha", - roomAlpha: async ({ roomAlphaName: name, app, user, bot }, use) => { + room1Name: "Room 1", + room1: async ({ room1Name: name, app, user, bot }, use) => { const roomId = await app.client.createRoom({ name, invite: [bot.credentials.userId] }); await use({ name, roomId }); }, - roomBetaName: "Room Beta", - roomBeta: async ({ roomBetaName: name, app, user, bot }, use) => { + room2Name: "Room 2", + room2: async ({ room2Name: name, app, user, bot }, use) => { const roomId = await app.client.createRoom({ name, invite: [bot.credentials.userId] }); await use({ name, roomId }); }, msg: async ({ page, app, util }, use) => { await use(new MessageBuilder(page, app, util)); }, - util: async ({ roomAlpha, roomBeta, page, app, bot }, use) => { + util: async ({ room1, room2, page, app, bot }, use) => { await use(new Helpers(page, app, bot)); }, }); @@ -265,6 +265,13 @@ export class Helpers { return this.getTacButton().click(); } + /** + * Hover over the Threads Activity Centre button + */ + hoverTacButton() { + return this.getTacButton().hover(); + } + /** * Click on a room in the Threads Activity Centre * @param name - room name @@ -330,23 +337,27 @@ export class Helpers { * @param room1 * @param room2 * @param msg - MessageBuilder + * @param hasMention - whether to include a mention in the first message */ async populateThreads( room1: { name: string; roomId: string }, room2: { name: string; roomId: string }, msg: MessageBuilder, + hasMention = true, ) { - await this.receiveMessages(room2, [ - "Msg1", - msg.threadedOff("Msg1", { - "body": "User", - "format": "org.matrix.custom.html", - "formatted_body": "User", - "m.mentions": { - user_ids: ["@user:localhost"], - }, - }), - ]); + if (hasMention) { + await this.receiveMessages(room2, [ + "Msg1", + msg.threadedOff("Msg1", { + "body": "User", + "format": "org.matrix.custom.html", + "formatted_body": "User", + "m.mentions": { + user_ids: ["@user:localhost"], + }, + }), + ]); + } await this.receiveMessages(room2, ["Msg2", msg.threadedOff("Msg2", "Resp2")]); await this.receiveMessages(room1, ["Msg3", msg.threadedOff("Msg3", "Resp3")]); } diff --git a/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts b/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts index eb4d7c8df06..93094073b31 100644 --- a/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts +++ b/playwright/e2e/spaces/threads-activity-centre/threadsActivityCentre.spec.ts @@ -35,7 +35,7 @@ test.describe("Threads Activity Centre", () => { await expect(util.getSpacePanel()).toMatchScreenshot("tac-button-expanded.png"); }); - test("should not show indicator when there is no thread", async ({ roomAlpha: room1, util }) => { + test("should not show indicator when there is no thread", async ({ room1, util }) => { // No indicator should be shown await util.assertNoTacIndicator(); @@ -46,11 +46,7 @@ test.describe("Threads Activity Centre", () => { await util.assertNoTacIndicator(); }); - test("should show a notification indicator when there is a message in a thread", async ({ - roomAlpha: room1, - util, - msg, - }) => { + test("should show a notification indicator when there is a message in a thread", async ({ room1, util, msg }) => { await util.goTo(room1); await util.receiveMessages(room1, ["Msg1", msg.threadedOff("Msg1", "Resp1")]); @@ -58,11 +54,7 @@ test.describe("Threads Activity Centre", () => { await util.assertNotificationTac(); }); - test("should show a highlight indicator when there is a mention in a thread", async ({ - roomAlpha: room1, - util, - msg, - }) => { + test("should show a highlight indicator when there is a mention in a thread", async ({ room1, util, msg }) => { await util.goTo(room1); await util.receiveMessages(room1, [ "Msg1", @@ -80,7 +72,7 @@ test.describe("Threads Activity Centre", () => { await util.assertHighlightIndicator(); }); - test("should show the rooms with unread threads", async ({ roomAlpha: room1, roomBeta: room2, util, msg }) => { + test("should show the rooms with unread threads", async ({ room1, room2, util, msg }) => { await util.goTo(room2); await util.populateThreads(room1, room2, msg); // The indicator should be shown @@ -97,7 +89,7 @@ test.describe("Threads Activity Centre", () => { await expect(util.getTacPanel()).toMatchScreenshot("tac-panel-mix-unread.png"); }); - test("should update with a thread is read", async ({ roomAlpha: room1, roomBeta: room2, util, msg }) => { + test("should update with a thread is read", async ({ room1, room2, util, msg }) => { await util.goTo(room2); await util.populateThreads(room1, room2, msg); @@ -120,6 +112,17 @@ test.describe("Threads Activity Centre", () => { await expect(util.getTacPanel()).toMatchScreenshot("tac-panel-notification-unread.png"); }); + test("should order by recency after notification level", async ({ room1, room2, util, msg }) => { + await util.goTo(room2); + await util.populateThreads(room1, room2, msg, false); + + await util.openTac(); + await util.assertRoomsInTac([ + { room: room1.name, notificationLevel: "notification" }, + { room: room2.name, notificationLevel: "notification" }, + ]); + }); + test("should block the Spotlight to open when the TAC is opened", async ({ util, page }) => { const toggleSpotlight = () => page.keyboard.press(`${CommandOrControl}+k`); @@ -134,4 +137,14 @@ test.describe("Threads Activity Centre", () => { await toggleSpotlight(); await expect(page.locator(".mx_SpotlightDialog")).not.toBeVisible(); }); + + test("should have the correct hover state", async ({ util, page }) => { + await util.hoverTacButton(); + await expect(util.getSpacePanel()).toMatchScreenshot("tac-hovered.png"); + + // Expand the space panel, hover the button and take a screenshot + await util.expandSpacePanel(); + await util.hoverTacButton(); + await expect(util.getSpacePanel()).toMatchScreenshot("tac-hovered-expanded.png"); + }); }); diff --git a/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-hovered-expanded-linux.png b/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-hovered-expanded-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..37405cd821a101589f12ebd44a24ecdfea71c164 GIT binary patch literal 14993 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-hovered-linux.png b/playwright/snapshots/spaces/threads-activity-centre/threadsActivityCentre.spec.ts/tac-hovered-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..26f5bfdfa9876ebc10989a890cdd8f6b41506a03 GIT binary patch literal 5889 zcmeHL_g53zvJRpk(n0CXgMdggph)M?1P)CB5kY!KI+(;zLJw7%M|wGkh@v0@p(iQ< zL?Vc^KnjLXq=XQWgwD-*>%F_~d+WV_;H~@9>@|DO?7hAc#f)Yr3Y%#~v7^e9uXMBAGi1DZx9RsjH^9!parM@R*Q5F6u|mNZ10$N2u_ zNzMQLlqKX+qPM-2F&8ht*@HLjDrQFSXF#za+#G1E#f=ey+X)GMLkRAn&{^hu9&lzn zKO1}|E4j9&=1v{oCB9PvG939y`HTntblwWDpCgsSn8i_Ri-hWEtx_o~8$b@bpuADmwS27k;2dZwIS5PuKG)|~ThLnj(@y^@jYg>q%d z96wF}JulX~^L|s3aOw`?i^!Ihg-mbEmOI<~?`IdnSHB;yCU9`H>67>XY(tvp{>zEu z_UbjszYuVs*bI0zqKA-?jm7VcPi>+L+!SunJN~H9he~}}aUHjxXLC+m{AF6_d&$ex zh%GBz35{MKdhbugq3+f6L!R~J$hm}Xx~L(1hCVZB#Ojf+M7-!^w17SN_Pbp%%*h(p zC2pIuT46P0&S!?I#UnkAo%dC>Ds7oh{lvby7U4a%b_DSmI0y_4b2Lq+d*MyqFN3m7&;3Iv;)acihzW`}|8@O(d@;0jj~gEmlt` zgzqs4)YAw2O>7b0!s&dp^F<4MnEZ0$i$w9`*o5tSIhQ~E(A2}rAg$mNt2r3=`n@%iftSoxnoXdL|KM7+Zfs&Fn;N~cgUD7oyn?) z@0ViA#OsE~vm})auSOTL^zBWum$U?`+iMh=K;7>a3sR}N8N4H()j}QO5DN8dv-A=0 z__zQbm_D>C+694(6!$S0p{hfl4`$Bn;OWb@AqObN)`0m6SX}A{S1CD8-<{ARF1#kL zn!5t(2Zu}`E{5hx?t1o{Uo;F~F3##EsJUEau{1d)ow_&IL*tth(@hrZXl)gA1^1@x zr{9bj<4ej7UaBfG3SZPycH(S0`gpw>^Yyt`B!{MT-_csli}5LvDRaN?@d~JiMuf9R zw){1F;1WT$7pj)~+$M8DH?~?)GicM9OSIq=0E_g#(XCSfNEC1h#y}`@tvnki4Ij5! z>gr7K(24t8l7;?6&AR=sUHR1x?;5bNu`_GD1^Pfvvm3(R-*(9PPyb9FjQ&#JM^7>t z1nLbg=M6S&C~rT_ukU`EKbV)@;k8D&%K~^cGWHjumPfibE=b(B{ayrRMWK9ujtN3H z@78Q;TN{G3$6fB(?(8kRSav4OLaOX6fXH`(y}wD|p$#I`nkk^3aqMJtP;@_qkTB^H zg6=Lp7U~f+MdC8c`d6;5y;81QsU!K$K2PbUX>8{g=uWoHWVecub*8gKIiXQisay`8Uxbw&;w^&`7q9%SUp?LPlfUVqUNGWpP(JS%Xd1j(LR}(YzCMa-PjKZcCuGr{cNu*lw=GzH5;IW# zu2o+JjHMMwLa1YY{x+(Y<*c&)j!pJnnL&o~Y-u!42*mH;Ik;nUlN#fuS~OOovO8#v zmOI@Mt6_3KnfnH;@lxH2FSm4hGrpD2AzS{Vcgrsbeo`h#*8bMJOKpsc@xT@2%Zt^V zEP(8R~qt_YNcOzf-*6rA3sB;h!zymhw3J zdZ?avMW3}&+}zxVCuIqBW%*)x2h~VclbiJ=`a#VkpY#RfpVgde&hn1@+A-2)m05r9 zawvi(UR3HS^D_+N+3nQ}t*bSEdlYXmdZ%=kL1D7F*V9a<~wuoobmb8%S?s$oZ^e3_GOP`(JXwya)m5V#lju* zeN6W^l_%lGr)%$||9$A6R{VPc3&RdCwl;fU=4J!89i625NwOC@AvEvm`qC#)Qu$@y z@9;blzU%!=N~wyR+!;FqnwJ0Al^ve!NBM?tTv8NZ^z5*HdE3kA zjoH>)jxpTzcI8P6C!%urbvOZyQ>V(moGYKo`uiH@Q_odaRuWN%c2Xz& z2C=v|Eu8lE9&vFBtI&wjh6xf?yQ_Hx#l?A}wgJ_$yFM(Q9k^R}?xewyM^|6HvW3d_ z9xgz0M2mRQ3q?I1C;rbF^dIBTwlGvD83hGlN&cK9yC$>EA2HsO4fhY%iVe-<4wu$% zCA2QQG_cX#kl}%|;yhseu_rJX-J!0o{)68)+?Ho2K5Q{ZEG;~K+lc!1jjKn08Yii` zoJ051Q&ittpT6H8KzZwu#8KOn##GtYtrM9`Bb$ShFCBXO5y)C#i0yJWVnvrFj8Y#}H-6i0_*CpdLKk^0ShJGOsDkz&&6@wyt?m8vRNT zu^D$g<~g!)xvoN({`|JK{1+s){DR26A%49fc~Zu{8fN#EcC^q~Gi`|m3 zy;rzzo^R{I)SbblD6ffWidMv1z3-5)4)JEx4$r9eD4Fgm{609Bh;nr-&c8u~zL*g~ z@<=ETmG^+mW2&4TQzk34#eYWI&YWiG8?^qYxN@6MV1mpCxGZvm?OBv+yw5_64fkWo z=B(92^fe0?mn%YQhZ4%w8)g-@3JFJ(t$|@2R98X&jMXd58rg7z4S)^N*xT zlWD04)K1H=Gk6*p8uT&QRrP4mEGkMJgzA&+mNwZ<5CHFf;>U)x;3Q^4oFuNG4y6>8 zG&DAX(vCVVD_`Cs8pQm#`Z@urBFgR5cxGv-cn0#EhrYp^S&0b zZB^6wHJ=wZ78^^2AG|%Rf*C*DQAFcH`Q74`0Sgq(3Ik^TNI|i>dIrSDC;P`RdeB__ zxx#W?F~dC?Zy!VHvDCmfKL)95W%@VZhsTf}Zq3GlCt5-P&BilyEpY+C4d?b??x%!p zfNfRxAuIM|yJ#rCNRSxL?X3LN3Wn+Q^q*R5hb#Jnw=kw7QCQJERyKJ;lvLD$w|oD+ygaketL(Yx@>Xzo($D3H@DZ*NrH79fH=ps za61jpHT~~X27`WpyYcPk`O?LM~t#B*0 zF9HiB@geZ4h$EHhmJ-H3rDzJHdgVA3C$w_kYv0$_J6S=MsK0SH1am~#*3E~QOdlioT7R5P+28KCu68aCru>FsOvT*_g~5M zpFH_r?D}uTfB$X#8;^hE@h_jx|3Bf7nLU~6>VhLRgqymWE6mO5evj$izouXQB@3Mj zxg?JT#s!KM7719F-}MlJ_mecElU0kw+aC%?Oh=AmhUr(MAUf|_zj?JdTUR!{G;+b| zMAYspGu?bC^Zng2llEzB*x25?H)!J8Lvj20uM+JdyNl8_$iHSMgUW>Jy4ocJBzBn1 zR3K9XhOS=?h1Q?ZhP8~pwoH57g#6GJR4P>0KGh3*eyhE_v#bn(W1gvc31)13;9Q@s zMby{ZO^l5Wf2bX>igXJK${U(H>IG?8yBgnDC6jY<*JpOL*#MK%lb+=Lhv|6_LD~m; z@p&22FP*9lrqo_64jKBeYz5P!NSpz#{Eix5LrWoak zOG}sDh)6Px=h)lqpU-vL%|>IOiv2U1*+E8yf`VVMcnyn7Cc8+)J!@A#^=}R~C+Af} zG#f97uD-0d8Ecc_pn-e?>dUWp)%XQ)n;4&caUSZk76vx;?|vz*M3+|tN|lP5SXcU+ z8f+Li+?A%;(Qa}{XKi4--ntyn-~^-#GY&ej83<638a$)J2LfGGg=b)s?nT)fqy2RX zn@UU=f&bD|{U2PBxxaI9qx8=Dt=oG*!1p!Fv`*M+vA2#P(9Kv(th|>wh&r^8D=TyL z*0w6p9S-q5D5rW-k?Q!E{t)KKBweMM@_w~UgD^NO`Dd=(e2hNRXlrnkPy(blbkg># zgb&UCm`n~K919V#T?(&Ph)lb<{lTvKv7&;8H|kX6NuGfoxccR`fW>4orrbf`I6C0V zwTJGfGUwd=eW$mi`kTs}6ZfL6fKlk@L!%WBJ%>gKmz%YwkGf=AYp<7&-!m?Ab%BN$ z%G+H*vyKRPg&YT7l9T^U5yO?^{63(_tyy?<<~l5tSc-OZWauV5n*J{Pwq_$O=Y@>8KhS{HYN&K-)RP+;s9 z{Q7z%Ho#6p6LxL0FZQB+VUetUC+hIHI7WfHVU##2~434WM+FFocA3hm<1?0@7UrLwAbA&^>h5 z(ER87yZ65Nt@Xd_uKW7FIp?hNIiIu7+Gn5r>}T)L56Tb{LOMbK06_9yPFfWJz~RD< zi}3NVZKsOl3v7qutO@}GN`@FV0RXz=_tKK;ZYjHS?rss7+q=VclVp=bxsaEMF(@ifbgyS2A)=x5o=|IEytA3|p_PEXi~=G^ju|&pygLsHQ9@^6L@r zeF2B>Gnw=d4E87{CSz6*`n=9Qdzf`+XS9pgTGRf;Y(Z7kTO!}i&zaJ_!<{UAon}P1 zUC#+4WLD{pUUP7O@qrAzgM(D5UH6N-7-7}yPwnjL>V$AIRP&2=<-J{(xH8c@JBm@Q za%q`>gt*5Z#X$&{#u01&y83#}n~wy*rxsmp&z+8mSJ%*S3C7RbQy`p3JbM*Rv&tzt zZR&8`1o^Xmh%9PQ9TN0Vhzpn$TtsQpwW9Aml{InwQK(uTUZHa&crzot?M<*6>cS zrX7w*FxoeNR8D}@RGV9Q1P*c9tfo7P3=~4sJQ=YB6?lq)@jr4QmHmMW zA!^Dcl4kl~e3?I%{Hv>U4s0B3iKU15KuXEoHTh2;M+UFz0?5JdUtrrX=GK=19~cO@ zfC0;L4g^3-h@S#}rVs`8@6Omo3h=9_2T9~$$A4d1CzRE*;iG)pzDp#YYHEwhb-}+o z_hDunDTWq!a%>COwAFfpvYyXqFL?;I8ysvGn`z^Ce@1SLG#>9x1l zrML=*_7ef!S0`14X1)GQOzPW$c9}}sF{+Zs%RT0YW1D?*7`xp)O_h*`2~vLSVnrJH zs5B2RIHrh$IDy=8+T$;DC z!eV2_HxIAt58K9?T4-2!xXw*G-WdyHhqktyua8=gINy&;7cV6Ab$H5hoAVux#G*s7J0`*`r&cTSep~KpaJ7IX3{2=wd zru`s7NWgA>h`ux`Q)jdb(18`(w^=#hZck`cojiEg3?6%{;T^TTaTs?#c4pSo@}%S! zbfr+kTHaRT%lVUArJE-ptKX+cTT$2Ytp+9nQevaAGIIy5hQq1mBnR{sKVZUka$sV& zAtWloXf-|kIWu#(Ouwcs=FV}&tY&vora|D782@hxf(A??Eoz-8$@%av6&`3LI1jJW zVFwd;y`EeR0|gyj&LF&K7K(IK`hCqJ=x&Mk_N>;t$|I_*QA<&@1wNH5Ic9~hE%I?v z-}}K)_l(}GIvLF-2W+P+SB5lGe1)SiVbQyb$9In0Isj;4kseWrm96lf%mU?OpGKh zPxpeRp-Qr{ERpmV6?coDwr`vFlyXZ{Y-MjRUaPZFMbh&dO%FsXoPMhL;ppg?8*A_N z^MW79Rzb37bqbBnY`m?pQR&E2wv{FE!Ap>r!CJ!Eu0%lo`C2=Nf?2lDTfS4<4`j_< zB=5z)1W1tV4Y#&;zeyha5@OY#_UnGm^n9^FTo;Muhf=QKsOzDO?kk5AOxzDemMmuG zz6&ObthK_U#iN5dWD`=iIQM*l+nJy^cZc`yrXVj5Trnc`ZO+&gYSbR!aJl2&bOnrZ zUguVAsG+&I!2L03X5CO!WaH1p8)9^z?V74GiJq#Ho@w-*R9q`rtY=~SjEB#p|6yFEcFD-A$lHykZlJW18oE{E(Y!&mgR`^gIiE_RhoPr+@pZi+hg9h}ul2Oa z7ZJ{KXPWN`sm8YJoB6D}TQ1uKckGs03ghM49Ak%PW+y~aBNk#p&U^QWl1fZ&vr5ko z<^=mA@)~cgJUty>?*6Q9ju_zhPI@gX=b%6*@Z0{_5dx7t*u_Ycvu*+)J`(TOqdp3W zPTg*bi}3nk59z#k@R5`Wq&#!*VLQ$@@B^>RZ1;F}f3`j%rR!Teq<3J*4AW$?HURCp z2+s!y<*To*q8$$ocqZ&;+j`Z^<#+Gou4?pEnp^+g6|JGXdN~ZMbH}Lqv$8(VrwIqc z%zU~^K=1YI+z7JE$s^bHR3xyP+=Gh5cr*KRH(v%JugH!%zf*m!b-h0{G&CU~QXGW6 zpo-%p5Jt>S1sN5yn0O7_CdEGDjXzCupc$+&+<1t;wVaKqyR&0v6;u&t340jy+^-N8 zJtPJ?cRXVf8ptv&6mr^mpmMTpdLzSdv7UF}pmlsIXkGtBI5K1+N5zP2XCc`xRkRFi z%>P(K8dNk<#0R~$)>qezluH$v|9U*H$hmRr`>o> zPkeE4y^DU7QzCMEq2m7zw@~v=?$f6cP&86XT{J9QX{>bH7%Ac=h$)~MFCn3Y>uh|{ z^|OZtQh_(s^(fACJY2(y_8ddxzdLPlFn#4s-%AyJa{_N22QdJ5YWR8H)2`P^ciLdx zYST?HVB;?j0ujs{&@Sfy%D_r4x;ZqT9kV0$G<&0DH@C{(;s4HW8z<-RV6l=cPYd%V z;5TKMkT7=uIqduYA6xz%h8%H(e1wsRZeRepfHTg)aWNME$ieu@zf#26nWzHE`~G(j z_U~f;JG4{RfON_}hScQ5oV0)BVrrJPLwuX1$NTWDqy`VlNYeDp#Zp2j{!bdGgKL5! z6?hh!^Dqfm>039Mx}%LCGd383&Y1i%i;Mj^7$~JRS$QcqduoMR3Rvhnu6Ens3tfN0Z~tb`qq@E8RloFC(`Hew}7J zjMhz8EpW4w#iZL7IW+&XR zrYzt$wd+GvEGOo!-;Rwp&Wk^~s7in6MNY++9kK~q$<&{kRtql?F1u|O#@fJzOew_b6Gpg% zD7bVI6q41pJj1PCz_vMqs>J~zV3kdz#5TQ^!zAlh)!S~HoiuFvwY(e#jq$F#)4a!X zn*g!-W+qmZ+%l6*r(5AOrg4xMaE}e9CWgAL2CN)X0>&gce~X&EMRX%(pfh{4T4&?> z4QCj;)m~>l%-(+nNUN>IYga%t}9eYzdD`l|HR~ zJCr6N@YgGp?%P+(-BUPnD7Q!cWQES|Hg<91ijjEmoApCdKVe+`_)RX zXS&aAwrvge5ukNcU<+6A`Wj!^FZl?a?B@S2j=xh;zPy=J*NtsbC^A3>WoRxv2(HRR z8T0JSC3jp^&cEc~p!B`}kn9od)C8Rz0O@fCSp1VIB~XiFF+DbXS*Z+j9&qvcYR43u z_tf|L`dWgaazwjFf%`6DAv%50Fun6{W4%vcv?2b>!o)?k`U1CIMLa#4Yr=QMmmAtbt>?sk`V4BQ= zc(lg*X0Uny!t`BW%~apsq7}tNOUaF(Pt%OZ_LgYPCeQ*c<%WtCGAnEqmCE`mg5zZWE(_Z#pfKF_9ZgCRz(GJ-dueA&`U6dO>I(ewEO}=#wnfig#e6&r{blR? z)!l{FYo?)#ABT@wmY0?!6%`HVzpl8M<}keF2ZV+;$SHCldAmA~`7w4r zhYq8li8&y~W-ii7p-o(2SUJ{}>f&-deX^dT_mh8xOj2y@(MxOKQ?TCSIEKoiL0=SN zDXRn#tW)AODu$7A6abznK7s@(vDoz*VbMj!NDFL2HzFZ@(#|TFM${IbWktQX5 zX*Z<2Otb$Qllzxq&x3!?z(NIk&egA2zD*oF>uVMMIXg7_FmJN<3_nRlpILD3-5$w( z?RQ#w{v}a(3mFJ$QbJ&nmO=`Y-INy#taZhq?=@bm64Ma!(EAcW_m#s;b* zhU0d0?wNG|^}^^Qgqiu1BgSmF7!Yk~p_E~pNu6uLLxeM=&;jTO;Da*mpxuhtQ5J;J;>Q{*Uy3VZSD7I7t zbs^TlElN8wRNfQm{_XDXh$>_hkOc-f313UA>dK4oG3i;rEao;ByH*BvDr4ALOGS%v z$-(subo%w1cx$A3lGFKLAc=u`--P}z+L$^zI{G1G$s2id58FApkU>JM%wZ0UQ%-ly zJGsyKrnHWGkK&5s-fYF^7_b_U_E@jxGsD0)G* z{Q?Ua24Sao@tZWG{t4T05GBxxxMA24D)hB;Pm+>~dciwWRpb3aQxnO?2r~K9#mGn@ z&C5<`p%mJ!z}@s=2@zFuxUTfzAui9AKL$U#%vOL+TB+<7<3BKc`xsJ^dyibV^if;` zXy|dP?M{g;CW8ISsswuFDR{J659gUct&{2(R5b06mDoN2Jvzzln?E}Lsc&YKfFS(Y3l6_czIkS5W`7=IR`M~nl!K60W{rH0 z!#h>Y^reEu`?CU%^Pvqj?NJ&beKltr-gq2$0UgJ$O{iDnbYfJNFDU<+sD@x>pv}5| z?2EB11I+?~*zoeraf@44P(W+3#`9&G-TbkpeRN;KTR>xDBjDT=Q%X#Kc#kb1EP8#w ztnIwKqw{8fZ`3k{QIocG^X+}h>9OcZ;Yv#sm!A)} zb`KT=5@?g`{qaaeWq}2KSE2&y7Rkj0HlUKD6j3+$1+$jMl$J-B5)1a8%d|Oufesnfr}ls3s6H0QOi=A?00yfOT$ zr`}z`U+X?dcoPFtvy{2yD)%_0^V1FdXFG%F&N7%i#s08IQd!vt04al*w26tH^)sB4 zt*QIIQo*ECvv?VnY7*PSB6EX0sMEuGFNQP^z|rwB`&Tirm}0@pI{6Yd1JStF3Ebv; z9}1g>*7Sfon+?*X1|aQ94=QWT(zbiW z13y9KKEuG;Fig)J>ecPqHr{@wQhIg*MRf;nY@A`i-+X$kw$2-rDqNnfU0X|FucWHV z3)j(^z|`1=Kg@91o)D;gwEXGB19kgf7_a3Ey`rxmoaFTp1f-o-eevO$Uh`tUd^j}@ z3rzzdMjK!%*!Vu;F{|>uH{RFUyu7?mPM0kv?kKyz=k&vQUGO9&zn$za46g}*#Uk*6 zt5&)nt!%8K?s3D&8fQV+kslKz!q`Dhv|)ROQC~9kX4gQ|{rGxrqICa+cJxto&2{OG zP=m;$vCf4O@BO!@yMm6cu4~1$dH4V1uVi1Z7v^AdJ6$S2dLYi?K$cwYg_27;SoQQj z;)>Gn?WEca=bx<<*PAr*Cj~S&F2#7(0sf1ZR~7c4N^d)cOy85z#uOtWBI3Dqhz`l8 zfBsngVQP!lI6*?=G7cJOli3|;>76V5PqMXmR8*S1eZ0%~v5y5Q3wvj%rDu9W0JNeG zF7_31aDveVjybE>dsJXNZGGTXbYb@oBo#k)%f(_nm4$8%>Y`rwK6xnGWNqpfzzezrny z;7y*yE;mRqpE7N&-nf1jT zBWJ(duNNdX!hzb7AVlcC`fpJPzX~Wb;B<5?j_=JN2QSsIuxQMA=yO?_w_V{;%nOzk zASl6N878{S|BFQUzfiopcW|7_IMcRJ03Rsr!b?rPw6+!&r@5kRmzIg>W)2`n_YLx8 zjJhqZF73(M8F;%yL-2up331l%3LU$9rFLbb2C%7k!k9So7U*tRTzDK|T(ACrB>CZk z)MH#+Lf_!J9we;F>7{!-1i%guql617w+g4S^5QSmdtf-N z*dl;Iwrq~-Tw`><3GdRnUa(ZRYJulhY>l8RGyC;h`@ew7i#1D54J)ex+?LPfi+fA0 z_T9r2^98uoNU=iCB5+9XY~+9qDWj;&Obj(86)o0d0e*8q5hmDdp96cNK6FkF`SV*> zduj%t+3INx`SK^1>oH;C3)!Fi1;M@QUq!3b3zAzqi~q1)%=GpW7?%g`Fx+QjrSd4o z!xkx&3$djLT_l7#z-{pfQFzZFUre0!GuC0>DVx?Awcv?%Dy-jUt9k`@OxUq0*S=+K zNqNm7pr&op7GwWnIELGhi~=l_GCfFD{I4<+S@rfH!S_ymk3^U5;k$>(xpH^>TF@xA P76y1PqbywlHuC#_+vd}2 literal 8147 zcmdscXH-*+Qe%sXl5-Byk~0WRkf0zr)8r^YkSs|uNJg5RGX`=F-Q=8G zXfiaJIo@|?=B>N#ytUq}HGk$$pI%jUs!r{_tG;jVFH}uMmgpYMJrD>)BrhkW0Rr7( z2mTl1-vxd!`XBK9Yh zvnLIZ+x;R(C7DNXzN{`O^=gEX9#{HRw5;Zx_~$K&i;{P8?*%;%qVc@{Oe{{RxBmk2 zLGCF8hX-QQ>}PGtm3$TN6_Rk{>DmLjf<<>gv1>yq*QQ=c9SJ<>gT^u_-dfl~L}mne+6k;~iaoz2i)}-Dkm@t?^xL zuGxcUA*`>n^DB$M?(%kS2Lg5+J4Xv?ml#hzOq56Et?1@(2XEVSS1oJAF;(-@;Hjjy z$*^IW$@HL~$=0;`(o;|@@Kq^7E0@9dCU&szcC+>G@03?nZJbUi^4javeLzq^KetU) z>~gwXS($LI7B?e7CnqQRjlQYx7N(yWWRgNf9}joMCG`oE*VN#?vgM_bSCa3Vl2=4) zu>VK*Hycas>L}t_Ww}n^qX&Drzho=M!GZM)QtApsLVJX{t18vZX%82eAVH+6ED86x zxrqvSDk~iwN549YLGjg}QZcbvo)aQ1(Vl7Pbc&5&NYIPe@G44!6tIg^me)Jvq%Pg5p^e# z6zcf2K8Q4o@#nK3H97)zNYL`X4y~x^d>lAWe>{w-y5{LXvI`DJyIq%M^5xga$xl?; zjZrdd7r)Z6C`Y%*8(oGhzLF6BROex_5HL-_n?X9njJPwgD0!t^b|Yd92J;~fp-nK# z$mENk(L+Twt!^%k9*bh+IWf43 zu!`Luklv;QlXnY-_1cKr9xzEX8l5KO=FiZYVe_ZfrOU3?Zb2Dp{6vdcd%0t9>C|(> zn^_$OqhuMkTYiO9Kcc2~_e3tDG7P@!%wy(HY`&IFb+_V#3{TEb)n_+2uhFnf*e&;> zXde;YZT^GIRxPuOckwDqT(3Uq{;dZD$!efH+)3-%YgQkRXrewsG}aeB;>ZXZDQ~9aJ)e`#Ac>AJ&sd z4`6{QSl`z83uo|A4;L^I6VsSo3uaEvxIG@ahsrTK_C}zZX@XHWi`^9#Am>Kj!A||h zPrGmYYjGo~U>xRaE=TSvH%HE5=7R>0yBD;fiZ3#>VEWO&a%5b7&C~>Wu>`?4IM!!& zbP;#wMBKWu^W*eQ-!&SZv^2Wi{xR3WoWtbXli{Q8ygTc^lP)rxr3~jKrdeJgFND=_ zheUw|j&BZsya%Eo57;Ac4FJt#D@=X~_QZiBA4^Jzv{RWUtRO znh4mf^>TkxRn=ss$N=*Rx^F(3(iLJbOKzg^ne450%DV@I)vqNJrp*z)fSfK>W6nnw zk6?*c>aqtlY!=ObP(^Crp&n7`g3^gp(+_<9uue@)0akvw>?ilRV1b;3UAF6DsBXf7 z_ru(U>EK$f&mPyP+X`9t;GkJ|H`m#G8(e_+;~^DCm6Pq$M0TS*o2k*YIQd?nj*w2B;HZS-q`*EAo1nPys&Uc&|eZSLp(uI?V#}1gSTI)~m06tefOkjJH z;#!g_HabM`?R}u2r%DS1Pm0A4+vZMw%2H2S@EN7%v!C0L!i zMcT2d-!I*IdhVsiYuSwuYx?Re1l8Hb&Z+kbc0B_R0mo7ZI4B@xTD})(r=W5gf>QH% zU}e57zYaqa)m`8+_1%4L1j)c9(;s?KlEb**EtHkpN1CnkVtKWh6uSEC{|qeuia{p# z9nL!EVoP7+L;B6`FB&%J%TEA+XeQ)#olVDl4-)@>hhP7?%zp)SJ{shv=qjNK44t}l zPJ`n)|4TQQ+;;RU+(FYC!Nf6*DTL^8)A`Cm5kJ3c?04wr017V$G}&lqT1A}1I=xh8 zitfAAQ)~HlBgN(3iqvhReJWyhnv|0g+0*iT3q_HKZlO^8PcLg1dwaXhF1D|1Qd-Fk zjL#X5l~u>9OxF!vIfRwk_spu@#R%Ver7kmT;b(Dnkm;j)JX^Qw;>ViU^?0M$xzZ_2 zuDhnh5+89^u>jK}P%nbi*b$j>VPbBHJ+|g=>GVA4=e$#TFlO+>ZF)rH%fH=swR;Wd z7fuY7Y$A>&s6|d@4a`mqpa=Bns4hzAi*Qr`i^$lIesRTm7x6*8*W!yXCAtNyk%DV} z*5o?CaIT6e_1~K9EM5LYjMJ2=ab1*^o33XVXUgL5A5|(iJX;ufAY_ur7tGzaUgjF( zEpi+>z!c=(sIykI+}JZSTwo?zt^BS@xVdUG;dY(rCUHI3{46EPvii~3qitR9$!xCe8dpt{VyQZ=66)qtt*Y~F1E-+sr!CTl zKuq*3Q%s-@sK93*&e8<5AGCQ^oytZWzH1a0b2T{$sZz%gS`7aJL??K zZL-7h_rLW_FO?KmeyTEk<|h5q^^p*WcfRJOggfb=?OPquT446vw=XHFmS8*^Y5-~H zO!1j$;aeZnDzn1}Z44Q4w$#Bl>>8cDLZ=O{?|cf{zy3_Zpc6xZ%1>!xTIp0Z?w=$$ z#SL1dBK;KfYmcxeVowR3vT4w-6%Z~T`0aq!Sn2mwQJ z|5Lp2H6weHrO~{;vp>3(&JAc6!~0Zh`L<2?qFbkA56L>qml(dp$J@9nLsu1|E+hM{ z7M@^!yeE|Jc@!HfFXE8&w?XJYAiRqsX3^7pgvHy>D*|mj>JK^PFT?$jZ|Uc-a)dEX zZQ>NL@4T(n=xhl(*|~_D_c3~%r?HPZ`cCL&j~^4$Hd>|40NnpoGoWIf2AaU1Kad>x zd$Vw&q~jGjVJYW)86r0`rA&nuPsk;wH9uf!mg?5xZ5xd@Z{(GgpFLj}{VnVLIXwJ6 z$ML3}6*+~==S0ur&Yr2zo=oKrM@$UxKS)c%?R{jK-~A|>8U%9la$H}UdvhhwvVL54B}9%ox#0gw~jEaVx5ZKqn5xx9CD_J zJeBn6d~9plHN~}W>m^o#M`;%q4{xN_;0^*-X1e(Cy4q$WEs_>K$h0nYv_0R#;(zNp z=o1R>aWjt1WO+<_lj~cIIs3KHk@WqDD%r&8wzC$igMN z>7DJgH*gPux{HgYKeu1$>FH%1*V@g+y3L`I0ww(skJ(SZ#z9%Ew4LPW_owuZ+Y>+-qyo2W6mqNkdFyuF)o4JzNm*jX=ckv^+F`_+v;&)3(nGn;FKS+ z^R!TJZ&94P#Kyln<$N>hK60%*U&dghrKIqxW_0=@X$7Wgeeh;#ci+8PEsFs2WGvL# z7QhQG{7!gnCY#zjI}Z*JS2_(=L_nnFskZq?JQ51aZGZZv$!V?kcKK%FvwW8_-hBB3rPOP_r6r7RtMdb5terGxL1fMLsR@(M1yQbjn}74wV3W<25AEx_ zZ5{1iOjoxu>`&r`XM% zTJA*O!NcRSyOg>X^Q1!+!ScMYrLZ}?{h{UAT3oQT-_H%CueS=O%LKy#YY4gjs>WqP z(ge|&MM+d?aVq0F7+t9#xo$Php?Te{_u22;f}5=FMg@PmxJ6XSdXG=ir;^U#!JOi_ zSt$Nmg@Qu^%sF58=G?5hI%+(SW?5q%{ZDwt5EQ1%)_f~6!nG#SE=+MjkY4+a{S(#o z9E9pE9sz)>pzT^Q{)NN=p&JZQ^julAF|vWt|`uVtzOjpD$=!8$}|HjO$sC(fK7|}^GTr%#bAAqiYYyH!+VgA zxpU>_)5h@Bui|6E+|CWf)j_0KikUHprWHlx1@c)nLLz3&tt!7t#+l=yf}FD?n9)OKIj6qlYd_b`EL-(|1>mX^02=0 zXew>eHL9>?WL7YJLo%R9l!WYvS@o*)`n0ZyjfCtW^kLCtpkm?|mDPy!ORn8_SYo3fUqWNy z=z|!*(oC`0zoiK6Wx^+Zm)F$2EY6J~gF=iz+rgM@A<{55>MaTIP1<#dynhb-bR?IaklM^osn#+`;cw3J{0Nch~ys zSE8vgjPo1q`xcE@_vHHewatjXvG;r_fcmEo$)Mw!3`V8Ik@G zFqQ8uh*Hui_5S`oA()%ce&KNd^jDIgCAUmY#qc>dH}_=ud-6zH3DvgN7A~5|VuPmE z?yDlehURf?@1~E+2=I$T>g-8z7Tm|Go%*bMH{K8smPSV#aR$zNr;4~8EXXxGj2KVX zhX1IF;*f;AC#1c;B`@D2J^uLGV_b?Mdn}-A%IA!Z0B!j+x1i$UKuDPFG=*;@gcaN- z6|j{qUAA|H;mR+Q#|vJZ5#4AW|9?s?ZD-uRd`Xh)qI!Z)R3f{kkkBu#uit9?>;lWM zIHuZl__G^(b~to(f-o4fT!Nvp2e0IvzhB$e{xg- zz+Y`!@a~{3BYAgrkU;3+HfeHM-BGo3D8Vl_&JP!>*l?tf&P@5~=9B1TwXu@!;HRlO zHIpegPNZXnvom-xs12lRp` z6Gq$9E;lj@J9ABBj}}#RbbLU+M1BMl(M~m3V@+-tKATVUycGajr#7$}IyS%@d{@Oi z%9zPi-w2%|s%S<0_2j-+wh-$D+L!-fjH6<&Rm0Leyy8F{Nq0n({g zXGQR%!lbL;4o58~xCvcT1y>nZo15L)4ME(0)k>je*wfQvb7sqO{0D{mn4LZ9c_i(v za!=(EMCsl4$I4l_!-XS2$fcGErccn!ZmW_^S`+#;EzY?!fs6Gt1r2^ z!M-;cq#apO0W7;(9kx|i__6!od%b7^jDs`Z%?RJuJK9_0Y`QhZFA*@-j^8=YRaEqZ zT_UI_g+y3!OC>8!*G+?rpPB8fsl=pgyuY| z8U~Xt<=igCGKVL^DL;OIf?GwBqBw#iu2n+KQ|v$WM%N7JQ{rhKe^j767NCh>y%OAj z;r}oG+4LV&w?H!RJc2N;J-menfXsBy-v3Cl()YAeKTK~Hnnx0?&vNE0d~^J^a}X~FuM~y@VLvD2-W8|E5RqL)=<2kCz-8($6uPHP{ z3yf$s-lpVsPA)_;d-|Q-WGTbVj3mnGQUQH)%-J|IT@od{ zi?d5a`?DHtL=K+MSW~20`Zkg@*QsGWQPO3>JG#=bLt=AyP!!nK#;`SE|CFa{apMff zdHa#1n%d(Wahd_DHw9za9&f&~83Y9eo(&z|{9Cda-D0#nH8H}JWMJ>Bbh&9C%5cTj zG=DkCjCHtG_|N(vGFy_8XrX)Xj6_1eqBj2ux)xnLpf)fUm*~~fp7^F9-uWd|r(?G% zr}$7ORfU1K3wF3F-v0|`@_Iv^Eo0If5V*p%0{UvQc?QlW>N?fTEX4{T>}%=&>_XYS z!iW~(7PuKK>K(CJ6X|~Yp9D00Mc3*0VC<^>J_H9&!%o1>wsO6=(A zAkyVqjs)_Xqet}?e1)1tX&3dCRaK5<`cWoKLkZ44Q2hOmCL^zV5@(D9%+vEyIQEUpBSM+pdf}B-i;xkEek#4y(t0Nm z+Q`l}+$17u-MieCl_Pm*KxOX9#3O(dtC-k_#Qj6;6_gZ0 zUKw(5Kwwu5yNvRqEZNk^&+hdP1)twi)y=<=-tWB^Y5~`B+Qyuo1;0VGATam?NUnKG zYN`{hc;@hDKh;qx+fNEIvMdd5vXxo$0xP`~m{|9G%0pZN>ryk6-}4#*^3`eT_6wg#@w4))eEir+Oo$UO zEo=%TmEHJ0JsoaeEH+wUf0A*tf(`l?o*i^~Oc}*-Z-ST(zxXaDhKUP-|6-YGBm+?f zg#khZ?ujbSV5{!_eg-PK+G%a0H`Iz1v>3(h|I`Z=Ad2a@<&4Lrd??|Pzs8l>hb0Uu zh=SBAIM;TxjN)_h7wvdsSH};Y(Sy|73tv38O=5`bu)DKurlXUfK@SpMMqm>?gt|cJ z@9%UsjV6|^K%*ENH>KD1Smc+NH|{$22!uVqlx?J{i{Ojw$hSMW#CSRFo1w$$fww|-enbh+rq?jDvXGu-9w*ye1$Y2pXnqx&h`u+O1nKW zaCkTvv=jXMKBZdf7CBl4-_QbE!Ll#fI>HyYrH-ErV$Ln9uAw=H{mcCHGF?)Y4tH&l zwi8A;Bny~BS}Y-$?{YSxBV^Fk*a`yTGlDT7szzlCt%6WMGduv(;pTKSAhR_KBp%O^ z`~$l74rSe$4~9N4GBV1ic*g@Fl7lrfKeD6UW|N*e8F9viaPJO%H^E(+3l-M)@{%YA z@O@`r?_6hO+C6X`870t8RJ9#s6V=I|>cj&b{uy!Peall2=BX z1Uhqp3`Ky?k^j~UeSpdwcDKq`~j!aa_$6Hz>+32UX`ue5Qb^)Xx-(s96QnOo*aM#VGJs2P1 z2~5uvf^r&nbe>ees*lf&qi{tyjF&*JQkfz*URN?)_Tw1sT4#&BFk~%m@{OU>y!so)te>GdLmx+t%LlX^S{hDZkURg)sNSl zAM2R^r2KS+2n}$0u5y*UEhdE!;{+4_q_{F~%x5~&c6%=#O#D5WL9XzrJ~?%tX^ixk z0^GhJ0VZ`8fYsPoX0Odc9T%7BiDAZ8KB;|Ciw*LP{Y16!d(44q|2SATS3$$y?nW?g4JN)1SwB&en?$%sN=m-Qbwz&Eh&Lsl z`<{a~f~Fjob8{aTz8I@}cBcq)j56TASIZzMsF86f{WaV}D*yJ(uQDTe5HkmAVoezo2b z@|KpOMVWg5DMQ9xyMUIY5ct=R7J|)Op4YRN=8leMu3P|~M&T-P9|wnt+s9-VWX9q{ z^pHSZM$O>U;`xEDSJINO@yQoE^V(>d@6&r+M`ykMV2c***`}Oj0+)?sx(KnRriOvh z%~8Sm$M>1cbMlmar%CfKXJAp;I;!8Hs4!Pdm3AK)tlez<x>Pp{?i|8~X%QAWOQT8lsLi@M1{ZhInSK({gdZI~G~^b~tJQ-w)R{^APRAF? zhSWjQ89Vw1{Alm)PXC30SZ;(6vyp#P&b@DJn~ByI>!}w{Ts=5~0yQ;0B19!6{IllJ z3Ga?`GaTuK?~s!esio!m>G*SLZn+UlR98pggLL$ot8(x`h2&Cc3A)KD$3m;wc3td_ zRLs{-^cCL8jGj1dvk?4P(y!oyPCZIEwg2oCujB7CYS3+}%xY*v)GCKEMJ5RNh#cJ| zc0xt;E^tEouX*tU#tGmApA{EwN?me#_VC=r$h4CqDA-Du=KJHq+k#>VcY1ZJC`v-f zK4X{jUd1|s)3IpkS&$5xPrmDqp{u54wTQ6rD$&#@zopJ2d0C24T zy!#>v{eDEx{>j-o49S zyf)teU*YniWNQ1c;jr91Vr2j;-7%A@{2o#@&;E83pG3*%-P45K{ViPSY3m1NuVSEWUN(6mA1!Qc!f6x4#X&{{XorW^D@2kKuT_Vo=xDQo+dApDI@vly4*`5iBH zucoLMRQ7ZreJ}WTe{hrEb{|D0r}jO=sEnQ8irEDHLRz8|9TE%M_eDQ3s zJME;zaRQ+hc8WojSY9XNZ8ByX`;DU_-(Q+i)m&QbapoT>S+}Lv7r5r-ZAA~-7D!fR z5C$z>^4xU^)JagK2Yp+;w6OWIlG1k=n-N6?t(1isL7$U#7|$danZ_r{(dRrX0L^;Pdwg1g#~6MVXqkE-oLIm{Z~THdUJg`nhu8qgJlzqhS0S zm?-2OsImkvd!uyP;j5F+`kD;6;*7=EPsUPRjqPC`6Sv+}8DBmd)V53n^@%BN%LJ7W z=i^s^5%&KQ!sN~DmUF-aM9=f)&%0W#pJ`sx1;-o87=Xe$44!#%EMGsgTNc*6m(@@j zuU!5P`|`YV9l~{8#zP#j?a~~lPoHN2Q%U-q`p?KWK`tDx*@0rs`rX<wGCT_HL;t{ zZzRsU4X?*&8~%u7%O}^Gd%EW|MAp)QXUg#*+0%Z}!+T_d02OEyZV=p_;`353*y003 z)6{XImth&-S6+V>#)gcsLah@LzQJ2a$t}S!c zydkqjgzOs>_5vc5k!Ctu98J@bzsVb{pJq?SOYc`|%zI_0*V$eJb32!lPaIcl|J1Ui zY#UQ1ElW4mALes8xaRNQb_1)+M^SQ6!k^IjCs{weINufJv8>Aq50r6thpSUUS3a4R zd3+2%X6NM|i77aPY7V4pV#{3u0s^$Nq|=Mq&a|@t>BgeSJ8E&Y3aEIGkl)rY`=sct z%>akO6>sqUltO!;&W$PdwN+sVeC-F4X%H^zwEj=HcE+J_Z-Ri-vYwddrV-YXqat>x zH+g-wLB+hmy>dN|Uf=a+aQx>Jax#MWz`2}kny9zfeeK@@WIvrd!un48%DIUY zWx)sj^1Mv<^;gnYh5i~5zVeWhSU%piL%<=WsgxiP>O;q!N@kwjtHLRt5(p!JnGJa5gFw`C{gq<6i7w!Tas^2lpGx&hQLi)16kg?chEDw(Wn%(pGK=hA2_46UcI8H0wNQ51te=) z1rn(ONn6z4#|ktm-dr4QX{=mv&fX@cZ3YF+Ls&2SQT^H5dk~@oRnm01i_%00&&k8v z>uJyzo)_nhY?y=G)|=HTtso|~fgG+zl^vK$8ncQB>eG`Hmx7u_=zAIWF#pNA*9$L8 z-G9_5pfbX7Fjl8Qt{~A`0HJJe?+L3tnj*_~0&qex0_n|+WB zvvq%_si@{;l)BPNobjU?t#>yspP}Cs3M_C%Yr5B_4!3iR56%~zl1ypbve8^r3AAQS zXkK?x+pp6L?is_b!}~b`T5u^}oc4u_ z7uTjLE3uTZUSna6w>gdS>N>3Nn832=8OzxPn^%W+$`W@ zZf^W-<|Gz`3ljLZI|I^X7mW_<`MdT51PAS-tT1%jYj_Zf%D|kRH7%$|4QH^NITdPm zP;h8W*gK@uPi@Co9Y(?C{7yN&sy!kS;g*!;xse4qIfwKb6v($Doz8;Y|>&gWB z+`Wx4bmJaR^h@p;lPoKpb)oeo+4syzOdjmjnCpPTyr&^3syg4Wv|S)~2>_9`$t zYu@=g;aF<>Vft$Z&~{l37G%IGY!}fW=*H5zzpKm~@WhC?xq&E}(YhHGf{e++ zT!ntNh4VbG(03F6r)7zm?gt-Xqm*a4XtTeykWOlC!(|jZvA}MU+xNIVjO-Fu;FF?F z8_veqASzLM{A0=c#lx#V1m9IDQ)B6{g=~u;fr;cQc0J&w`=%EMPb}|z4s<-B-vSbV zfqp0p;=IaSq8Hf?3fp%RU}9hdZI}Nyfj#F7Go@bC_CFn79UpVuGC$>^@Uba2>J|*j zC7pvXgsA-sR8vn5F7D}SPktW4wtBAN??)$`%h!J@?QmU1E2yETn#NC4gt7n&-g1`` zD1CV8KMTTr62H6q470T5Jzm}nF`_OnK8u_P0*kxM*;veCVLSzF(8jIM+xJBUvQ72C zbqaOcz77-5oJdxE$O$>3(aV4F%!0tbh2&l~e~e3i86U0vipbJ2kZwK%cyaXHewji7g_WFcaav>&h`+Yp50Zubtewf0c6%)l z!?^?snrpN?SMZbU{BWCu7e>?jUKbSP{*+ayfYgX~@bXWaPVT*l#wNDSM{-4}0hK>( z?w)$zi1AO|CZVM(Uu=#xGq6Sn!(i@#-a7^ArAmn!$3d~d57h6bIi}e z`JVgr@R*+IveQu0fV97Es)Z*UP$BqmyRYOg{KZ@m6+k>?N>zC=ZnUvw-tUSaEQ{vL z>sP8xai40+)V=bOk{JlXGOHBG7M@*-jM!T&K_~v>UG#!f*11@jI$mflW>)W_N&rKS zynh*|4X6UP@+of_X=}EKf}73wlws+QV+}*AHsFz6X|$_cjA)G01j>(ctR9gZ<&-gmXmJE4E^tq+qP>#1m!zhmp@ zDDd}PpS8k;tfjN{L&izq~_Co=MzHt zdxw8@UUo_e)FU7YMxXD7GlN_@aLXT=P0kpixnA~(^axkPcKRr^>Xwb~nBtkqjDZaJ~ony|l-+79OvlrWUqWNy9`j zI8ENYoa__P@811Log#=?*c>1(@c}Ibv+>sSL4m+j4}~5^$ckRWBqj}nBmgXzxtwU~ zRgXQjvf^{9O`n{6pq+aJrdWlaZLQQ%F(;oDeK)Ecb}wk9y_V*-$~>gyS(4k`UwChX zq<+PIt?!{!G4*JLtMi8sGgq!%>-!qbPPN865xp?#$$C?9D6yFC^cTv<$L9v*c&CJy z_wYPf&~P1{#FpZ_c#Nft1V|m00w>Tm(ajYvmo0u?!wB^?hG>0szT@XT&c9VOKCo|T zN7|6NRL^in2z9eHC)V-&?CxCdDL5|=M8&J@{kgA?ocid(8n%{>N+W>}*~Ic_Z?!Eo z*SBfL9{Jt}Z|arzJwulK+`?jL z{!BTd3xfVVmNmVyA~Q4LNP+w7Z0V+^sR#8Q^VE-K>fI~e*k7is3PYu?-QH@2cCKu= zb8bk_8)SG1qsLpK@&9@YXyDOI9h!1tsn|QXurT&W>i}L*F(=nz{Y8i3p{ENT>M4yS zcUy}tO_okj#qz79#>O(Mw<=|@sB{Chl)Y7?oB7PfhU)Z~glvj~v>g4I`CA3MDIt;l zYI;0@_z-4xMz7EF|JZ}%z4zItp59(xQKKg7+!T{gNU(mmxSCf)F$!B|HRo-c6L8PB zuv=9zrz5-CK_{XxqNdHj3?OBKv{R7C<|E8oi>-7v@=JwwsNwPWs5oM$xkuak@?)~* zw;y%Ykt>5}>RS?Cm!qQ#jeAhR)#N#%7@zy;N8I#EPQ{;+`g^?M2eHw0rG0Xhr(N5Q z&tN>CWtw7RKZ}_%?b72?(;RXg-Dz9qSOk4ZkfDxe#J=zSv*i^kz<=a>;m@m*g3Xwl zTlxdSt?GVVt!?$VV|M5y7-mlGOR*Qy3~y{Iy$`VYA@+9G#1|{4^dJ|pW*T^TbB4r& zh{g4#H=18b?%09Rx?ap6+P z6$lgR1j~IfE1&==p!WvrH^>O~rCguRRC18kWv5ZBIQC0TedQ`R{7s|@DKwYYuMak5BiU6-gR<^`7vj&%sY|zQOs#l)HP$Tgh4Fib72t9hr-1!%h~n_(S4yKnJcaA5pBKlVC`{8`YeWK0L=&39V!Gf8ROu~q_!CN~`unlT$;G`6 zl1nRjs++5^({5l6v(G~)O_*%v@HvM~{58%@AraYpj9g+d23|?W0NT5m`~1{?6b z0vi*!!c-;bfe#GVk8+Zrsu9X95QrvCURpxKD|2to(@Vo*ruPu7G@!)r6_odomhiz= zERl3V+&jr95xF0qyE#ujdWs=qyv6heI|b4JQb>B0_0H+(mdr~Y`I0;=?AM}6V#jw+ z$%r1Cn=piioNxSc5llZ$z7Ay;&rx@gg`}lfJ>(#dMR{Di2LuKAg(U7~iqyIu^^IC4 zW@LmDO6nxFw^OMIVNts4)sqWT?(WqzXo7P;mMZNzbTk%)j@H)H&>H4Q{Sc#o=*89* z#4$mbmv>9L=I8gv&=A7q$dsfEIoTiCF+(gL5vTMNT&0T@1^Ll*m?6KP>&O;7g(!U8 z-X1T9X&LbGH5#<}JY$rjns>K_py#n^?;2IITiyXruAbQ^!@j~0ZkJnxeI*vozZEQE^ znt*wC%0Y&sZDa%T_4OSwhN?suw;xrhV$<#ut?tO&d}W@RnR$@QM;=@F*?eWSq^lv= z)q9dLl+d$ubvt-OVsJRwEk>4Z`^FFa;)+GE_;sjP#if^b!uKjhc5xc_pqdMRB1!DS z=8vT>`QcxL_&Y$Ba)#cMbfJVrE*{84NK&*AvoIE;T=-Fy95$`O+HRp#4kScKREnLM zjW+a^fZ+Cf_-Mh%@L;O=%2UZV!9DYFsJ6C0*Uh1XlJfMxHHz-{>rgey$H3_2_fC&# zpUVX+V&@4E8AxKo5*LXi-^4vkC6sjj--k}=nI-&ojD@1l&>i1RZAgh$;T`v!o#Y6; z!Zusox&XTU@iL-`H{B=maykAo?-RrgyJi=f9Od^E$W4w3N}oLn7BBCK^|kp#jq+s=TVITh3ccQJf2`4|+0^EI!9dQS zwem^V!@tD1nBe)-oxjzp1We7STZ~sl!&nEiDO7edwMoc&o-*x9h4?C}$2bSPdyJh+ z;wxk~6IO^Qw;M)orSeXQEk9wq^Aj}mR&{&465P3AeBU|kO4>I($lR#MV%My8;K|9U|?qIZ~1yqDN>`MCpx>2ICBfcCTMI+Ok= z)7f}R(J)JU0!#wG>67_&`&OM=KUV0x5$^6RMP*NRb;osN`D7LN=jU6;8>l;}GWl1o z#i9*)6%~uKHrS6lTmG9K;3g|FqQ2ufx~Uq=1w;2+&j~*^L#Vy1;VF_1i!0> zFKgWBN``>5Wc$_W zYN*9@wtaizhh>urY<{-hoe_xJSh1xxNJJ#tbg0mJcjlQC+#zdoy7F1pOp|26ctU2T z`KsarwqERy zZvQv-P-T73Wvvz>@b#%|(Z{7V-HcnTIJE@hi%%&{+6CwO>A$qAqvVFkDMg4BtNXC= zX+*<*o8(&SfX{j;nI;+w4{gRPBu>jT1&Dkz_p^ivJ#U^rdi03L`nldfM*?Adha9y5 zKE7wRWA&I-U&ra7Uq-?He((DQC561)+%I_rJtV&OJ;U^%k<8Z4qw%rK4yQ;3h(Z6r zK#vJW+Dy@pA4@yxaJP|)vJ8sA)1FRfMbs7K#lp}Z6ONCQh>&bU;dFdN6q7x)aP_%g zd%M!HX4$4fxCblE=ElR(dU+`zQp-mWX?$w2sJbM&0O;xPKF!3N(1x*&#BbkvmI|D$ z8pwkm_)$0G(^B)ti!@Wq%1q)+&CZThd&Zlhh39>=?)b%rx~9hx-#(LFSzYamar~V4 zJ;lLhV|iI=xq;%xvpXOSi79%mcs{n6%3yX}aS3ICE#b!I6DXLUurCHe?Czk-M`G()W9rNx>Mu$-^g3#0v&1hdL_!8 z)Fl`HwBggU;$=9=&b`|TCaO8T8 zB~Krx5c=-Y8wxdW-@d~4+VZBXCnsSugsm}0hGG(n2hFkOi&kda4+M3|=|Y9RH?n3B z3kO;_I`;ky#LIVCi()aPhDGsG98{=B^P`>PwXKgE9B)}fTfe#8C%;X^836-I$ea{)IU1AxOAUSXzI`= zt29{m2AIvR#}2gOe~?w_%~`2q?;l67}RZ`frLoQcCf`S^)CEvvVH=gx>HPr07 zgkb>w_NB+MwW(IG^IEq%m&11d`@bPLnM_+wWffg>tvIoCU~DUW0t(@N4O;2`0g>jD zx6{(_sgcH(>;C>B<{D)eN%H>3)&?RjkoLhl>79x^nftI%0>_{cP5p`2IJD1y3?bZDN7sbqI)a-ad|64DZn8GmZyna!edNBeIn6T zo%=bHCW*aPYvW{rLLMtG$k6xb-zU{PQ(Z9sd=D#J7}fW;qYbUqVQt1a!X4UeO0%{6 zR>8Y!SOsG|++$XJ)5o1WO(_}Cn=>A4yE0^$IUU%q*EOepGaqf2*-}2V@l;X-3TX8f z{|T@LpJMZdYL}v^3XS1@=Esj|soAw!%9=kPeRkn;Mb9#nNcrEj9~2Qat!*E_kLl>Y zgVfkvIS^d7JUXXh7N$$(Rc{`ILrAiBz@QUguiRO9+(A&OUMY}({tRy;>1+^i*AOR#T6g9>)) zh_2|w0$K6VW&(s<#F4qP6U@Qq!1!!8hLS6(wohr`)ooa|D;AN?Q9!-5@7^f61>d=S zrCy`SU7O(0tu;x3EL3WDOu*;NC$X@www1}IwEg-lDZ%N*7h-1ZR8K+4WQu6Gv!RI3 zxDcddzimi`A5A#=tz~Ym9Pz`Q8F4;8f4X&SDy~(f*JT@GR_3bn1V zdU*)PFySE^hVIOhyDm)plz0QO96<@*prHPYbG~iwMA4vNsV*&i@WDhAK zNm&fKU8!Cjl%eIEtjN0L+7$Kld`fvEI&w6fu{MS76+MgulodQk-cIZ8wmE&TI_@yn zL}5htvZpRyo&QO_IJ?q8Ov+t{El-vmv`t*Wn}F&)<;u7=sxnf3*f~B<{AJGfF4ys1 z`$MUQAE!T4*8;VI+e!7wJ|aFd)1Yn^#G(%+3i;iMg#S1=K`39`g-^RhZOZP=P!FYv zLBjB)F88l>frVesKDD2(g(M?2e!0cvekcfQ)~ z%eB-sTz}gOshfIO(muj|hsGwOg?jq9Ss^JYeK7Eved*#XTzKg&_vi^u-_TIE70 zr}W23VgS=Toi7E0xIg>Je8nNYupT-8!GYLviy^zD-YMW8rK`=*wFlk!`CnN5^#bWN z_>maj6Z-l4lkwIbq~1-h4<8WVt}XtSI@uj-@Ad(XK(Tt2Kh%3SaCUIfeVH^HF55`! z?a6BG?Yof-VXW95>Mu+V4sC+wWFJ24crSh3zyNeAV+$enDA!e*G4AG)QP>03GS*}X ze;@n+jQzwG_njAGM3i3d8I>^2)xtZGmN>)KBemHfi^HhFqA~>d{H>3s^Va<>&X=CC zyP#g5v=&GGM!QF10lzP4ff#9L^`=4 z2e4XeWB67>lKQ7%O%!T>H$0mAT@A6?hpnwGyBqswcock~mv7%jT4wNrP1ef(Y8xv5 zAbV9KgOH#P`lN`UZ=+HF>?y*Z$uq_T%6D^ zkH|L@Kz!!v?nCNpkXM_={r&wG_jhDKxnbkunE^!s`sp(M{W_asWy1cs%nRJFP1Vm? z+v{)1_ydUAjl3eKngxP`AG|o5ApGTqA9#^Pq{`D#wolM7Y(RpgUD>%!a>`F0!*ZE> zpoN`U7AGArwVya-2pf)NP$owd%9}C7&~(L8@TZHdC?d`h7GIj0;*;pR+s(H<`jVUb zvTjzVD+<_Jnwl)w8j zM^6HQ#g4iq;i@u zE^g^=1ZgR`i;7OSTM`_T-VA-`{qRq&*3|S4ZN9yWdv!}u+j!6M-51%Vx$~ubB50lZ zRA)AjEb$JHm3m=$@yL4u!4$7)#OFZQawVc+L+oGdE?We>&x?-DBF`boCV~6TUO3a3 znwfQ_IX}a&n%{j9V8lTR>>jgSaz~dZ?2if1ee8-P`QuhY4rZm}wiBj2d7*xC+I^}h~I>Y3#NVzrl{gz=>1b6;lc-E38M1r#Mw zyta6{`9gR$gm)^@Tv;pfE?a+AZo8k*QiqLG&sDfjaF~w`HvbaVx9w|f9xv@1h53&O zO=pbhVILOOZWrHZR4n~TkYP`i|0$s5W-|3RKtRjo=hc)Xwnfd=2Gay?Nl!0lR|$m{ z!p7{X@x**Nn^xnn!m5GuE8&2}>EZ*Yiq+1NlAx2EvGM$*%@GHoEC2bgpE~*!6tSJ< z(nYBxi?qhtjKSnGn1rJK!uspGuNjZ$`Q=H9+$To2$9qCM5RxO7k7@T!ArxxEi8<=; zP$^z#)lq8${X6C;WKv=w7M+0R-z5#*@K#k~a`$t_pN9T+y94H&Z$j0Y7d(4Jer-5U z+t^|6Zz?;9th+7$md__Q{LF}E7 z>q$&5heN4cl4Og4*CJ*)HGG&TC65cr!5Z&Uz(61*ozozF$v;eIQ4Hl6~AEpiUy3u8A<{Lr5(r;5Mcu}G- zbbDV`HGVPp1AUf|qwlm(Q_Jn5%Wd(*UmR`sVGhW{T7aS;DP2`E zSghn-MwW7wx!WgXvvIW1MttS)eOP#(!eEV+5p5`!ZridD0GQw$mYmxjoaM%DaIRK) z$?k{5jb3rNTT77x`>jahonMr*F6&UU)eRB==yfIO{Ailj0aE(M>T+gaso*rbL;G)! zAOEuKI69j2Y0zU0fQq%@w_g`&DK9^*sp86VD5*TnmZc#zmBDSdE;E3bzX4UYoJhK%jD?pJc2+v8D$K5$we(na{rHlYiB z<4H?4sNMlIh>r0?R}t%4+uQhfaR&U6i=pCwdf%yb1C~k`*n0LYDd`25n*e|c55vPa zw+i~nVrA=g#;tJfZ{#c4?o^u{7Xp0Fx1yUI2{9z_E;cfx1Ox9RYkWx9FJULYN*+BS zJv0c%Zri?oN=e>x@D%+mCB?x*SrXOAv=1`e2<#tuQY_y;^6Is4bQY-!!u>9_B1+ig z@M4X~;NIwhXu*ia_Ug)NLG4Cw{E^A+#%#08vQPT3Kf87{!-K``ZJf@1(ZJEB`|=VE zCMZCXOO6Dug|4)L7m`y^shYM2rLY=D{x&h$wp91J+G6m#@3Iq;aVzk!C-|(CqtF$W z=XZX~0vrQe;aC4+K6-BMq>Bsgf52{JWTb7hytq_ZX$Zq;6G1A+D8~@U70F2x(0RDR z=mnjOE-WkraRkm&0@?7%$A1<8Ij*Z)g2(vy%i_j?zjvzfR#S~rxKApVw60%7cf_kU zRR#j$?PCsKVNzE4%yz%m5xO!9|2{V3(iGD9{{l8So@d+7;{w6MrxuZhQ_m?@cuv<6 zv*pUl^9851s|djp~0Kl!lJ)2D3+N&hrmv%$pk`^s0TU*ll7 zblIIBo-OJtOZ1%VosNzf=#Q|322!XIJ!RdH({gbw;ITI#WzU@o`@QyYJhD)+lmJ-_ zAX~BTBgFMeoE7DsbLsX;r>l5H2WC)62slUOGOe={1lXBkl69Yzm4hCpRV`8px`a|E z&JEf8J6u|~{?Bj;($&>HuV^xC<-55yzJK_VjynXxtj)bWcP=k4zhrz*iQ5?j4*!SI zd)Y6l?hX&nI}~>G@*X_G1S7=mgO4}huKOtOPYd*xA}49mTuX~jGw zZiOtslozfm@^xrR2gg3J3|txTG8`##kr&MSk}pt?m}nN5s;V(NITm zT7_lp@hnkN#k-s$V@=QK-lhx6|Ai`3yNb%_5@{NJ>0KA_g8``I{&Yj;fNJhPd13s$ zG5jIc(`T*;$w=Y6Ht%B+P;hYYkb`;(lUBuht%Z|zi#GVGWvIv3noLLQ5~(+k`eG}U8JfBh;$ezZXWVc@CJU_|)LXSV1N=G?5c zb8yy-e^8oc4XEC)C~^AS`K#=OjtGB~7W>|8lf_8{a%mcQEwf9Fk}2Y*ns^w-}dpT^@UTfvtOk1+U9$?_Hme+ z#p}A$bT7yi21-qw(a5^S`!w57IgJaAq6(;OuLcI3fU^!-XUk1H4(&-jY&{6ATz z#Lm7~!*T??jgzd2^nR+(pLkH$XCMk;UNKe?*6(#Chbv;{vyD_SFZdH*N3SE=rnzt0 zOxO@V0)GRPxxBde&dW}FB4zR5v`qd2y~Zmuz_a<&m{Vr#1yOTLdwb;KTqM`2@g ztb{1mZL<;sj)t&nniz>nt!G4(-G5B&HheD#NoAhjKbb%dTnf#C~0b>8xc}ek;t1<^PA( zL}H+4i6=$U9#h_0*AUhgt)CTzYltGPO!EH^I5<@gV486H)R&(+NUuZ5EW}}FEl$ZS zsPiwI>(-Un7E@zoCG8UuuiUzU(_i0*%Nb5s=A~8tFQ3fE2?GF76Ak3MEBL0(n7o@M zKh>=m`p-{e^P<_f*_<0}8t>&vSVqbwHvY}b5u4mtp(k-=&E9=6vs`(ghvPfLS z0Qe*=M}4u0yUBb2a6nz4CX2R;5#tKb$fOq~`(64Czj#0hq#+l^PhQ=@UjRmqpSL(v$6`>kKpsUy7>7 zYng@5XjSItcB;*vV$({O;$qT~>+2&;a)ayQk}TPhJ(VQ^24!4^uWTQ*Vs|w0(5lu*N@QrX@(MMuT(k98Bv%qm1 zUcpP*zkg3lkW86W?gGMFPAT)dt`8eu3RI#)da9g)%z2(0 eTM&ES-qSSr90b059uG9^LGm&x(p8cs!T$zFwouam diff --git a/res/css/_common.pcss b/res/css/_common.pcss index 568b9eea123..b750de0213c 100644 --- a/res/css/_common.pcss +++ b/res/css/_common.pcss @@ -335,6 +335,12 @@ legend { max-height: calc(100% - var(--cpd-space-12x)); display: flex; flex-direction: column; + + .mx_Dialog_lightbox & { + /* The lightbox isn't so much of a dialog as a fullscreen overlay. We + don't want the glass border. */ + display: contents; + } } .mx_Dialog { diff --git a/res/css/structures/_ThreadsActivityCentre.pcss b/res/css/structures/_ThreadsActivityCentre.pcss index 4c61d32f776..76b38d6c076 100644 --- a/res/css/structures/_ThreadsActivityCentre.pcss +++ b/res/css/structures/_ThreadsActivityCentre.pcss @@ -25,6 +25,12 @@ margin: 18px auto auto auto; &.expanded { + /** + * override compound default background color when hovered + * should disappear when the space panel will be migrated to compound + */ + background-color: transparent !important; + /* align with settings icon */ margin-left: 21px; diff --git a/src/components/views/avatars/DecoratedRoomAvatar.tsx b/src/components/views/avatars/DecoratedRoomAvatar.tsx index ed8afe7b61a..5879dd3b1a7 100644 --- a/src/components/views/avatars/DecoratedRoomAvatar.tsx +++ b/src/components/views/avatars/DecoratedRoomAvatar.tsx @@ -35,7 +35,11 @@ interface IProps { room: Room; size: string; displayBadge?: boolean; - forceCount?: boolean; + /** + * If true, show nothing if the notification would only cause a dot to be shown rather than + * a badge. That is: only display badges and not dots. Default: false. + */ + hideIfDot?: boolean; oobData?: IOOBData; viewAvatarOnClick?: boolean; tooltipProps?: { @@ -178,14 +182,14 @@ export default class DecoratedRoomAvatar extends React.PureComponent ); diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index cb414173387..b36fb972555 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -1311,7 +1311,7 @@ export class UnwrappedEventTile extends React.Component {isRenderingNotification && room ? ( diff --git a/src/components/views/rooms/ExtraTile.tsx b/src/components/views/rooms/ExtraTile.tsx index 157bfc4d562..3bb3a21525a 100644 --- a/src/components/views/rooms/ExtraTile.tsx +++ b/src/components/views/rooms/ExtraTile.tsx @@ -52,7 +52,7 @@ export default function ExtraTile({ let badge: JSX.Element | null = null; if (notificationState) { - badge = ; + badge = ; } let name = displayName; diff --git a/src/components/views/rooms/NotificationBadge.tsx b/src/components/views/rooms/NotificationBadge.tsx index d152ab6a626..d4f7ee50407 100644 --- a/src/components/views/rooms/NotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge.tsx @@ -28,10 +28,10 @@ interface IProps { notification: NotificationState; /** - * If true, the badge will show a count if at all possible. This is typically - * used to override the user's preference for things like room sublists. + * If true, show nothing if the notification would only cause a dot to be shown rather than + * a badge. That is: only display badges and not dots. Default: false. */ - forceCount?: boolean; + hideIfDot?: boolean; /** * The room ID, if any, the badge represents. @@ -48,7 +48,7 @@ interface IClickableProps extends IProps, React.InputHTMLAttributes { } interface IState { - showCounts: boolean; // whether to show counts. Independent of props.forceCount + showCounts: boolean; // whether to show counts. } export default class NotificationBadge extends React.PureComponent, IState> { @@ -97,11 +97,12 @@ export default class NotificationBadge extends React.PureComponent = { diff --git a/src/components/views/rooms/NotificationBadge/StatelessNotificationBadge.tsx b/src/components/views/rooms/NotificationBadge/StatelessNotificationBadge.tsx index 69f756b3b7e..1d26083b6a0 100644 --- a/src/components/views/rooms/NotificationBadge/StatelessNotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge/StatelessNotificationBadge.tsx @@ -28,7 +28,12 @@ interface Props { count: number; level: NotificationLevel; knocked?: boolean; - type?: "badge" | "dot"; + /** + * If true, where we would normally show a badge, we instead show a dot. No numeric count will + * be displayed (but may affect whether the the dot is displayed). See class doc + * for the difference between the two. + */ + forceDot?: boolean; } interface ClickableProps extends Props { @@ -39,8 +44,17 @@ interface ClickableProps extends Props { tabIndex?: number; } +/** + * A notification indicator that conveys what activity / notifications the user has in whatever + * context it is being used. + * + * Can either be a 'badge': a small circle with a number in it (the 'count'), or a 'dot': a smaller, empty circle. + * The two can be used to convey the same meaning but in different contexts, for example: for unread + * notifications in the room list, it may have a green badge with the number of unread notifications, + * but somewhere else it may just have a green dot as a more compact representation of the same information. + */ export const StatelessNotificationBadge = forwardRef>( - ({ symbol, count, level, knocked, type = "badge", ...props }, ref) => { + ({ symbol, count, level, knocked, forceDot = false, ...props }, ref) => { const hideBold = useSettingValue("feature_hidebold"); // Don't show a badge if we don't need to @@ -61,10 +75,12 @@ export const StatelessNotificationBadge = forwardRef= NotificationLevel.Highlight, - mx_NotificationBadge_dot: (isEmptyBadge && !knocked) || type === "dot", mx_NotificationBadge_knocked: knocked, - mx_NotificationBadge_2char: type === "badge" && symbol && symbol.length > 0 && symbol.length < 3, - mx_NotificationBadge_3char: type === "badge" && symbol && symbol.length > 2, + + // At most one of mx_NotificationBadge_dot, mx_NotificationBadge_2char, mx_NotificationBadge_3char + mx_NotificationBadge_dot: (isEmptyBadge && !knocked) || forceDot, + mx_NotificationBadge_2char: !forceDot && symbol && symbol.length > 0 && symbol.length < 3, + mx_NotificationBadge_3char: !forceDot && symbol && symbol.length > 2, }); if (props.onClick) { diff --git a/src/components/views/rooms/NotificationBadge/UnreadNotificationBadge.tsx b/src/components/views/rooms/NotificationBadge/UnreadNotificationBadge.tsx index 5864a63be01..c3c8cf7df89 100644 --- a/src/components/views/rooms/NotificationBadge/UnreadNotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge/UnreadNotificationBadge.tsx @@ -23,11 +23,15 @@ import { StatelessNotificationBadge } from "./StatelessNotificationBadge"; interface Props { room?: Room; threadId?: string; - type?: "badge" | "dot"; + /** + * If true, where we would normally show a badge, we instead show a dot. No numeric count will + * be displayed. + */ + forceDot?: boolean; } -export function UnreadNotificationBadge({ room, threadId, type }: Props): JSX.Element { +export function UnreadNotificationBadge({ room, threadId, forceDot }: Props): JSX.Element { const { symbol, count, level } = useUnreadNotifications(room, threadId); - return ; + return ; } diff --git a/src/components/views/rooms/RoomBreadcrumbs.tsx b/src/components/views/rooms/RoomBreadcrumbs.tsx index eca8da46240..cd31dbd8e79 100644 --- a/src/components/views/rooms/RoomBreadcrumbs.tsx +++ b/src/components/views/rooms/RoomBreadcrumbs.tsx @@ -61,7 +61,7 @@ const RoomBreadcrumbTile: React.FC<{ room: Room; onClick: (ev: ButtonEvent) => v room={room} size="32px" displayBadge={true} - forceCount={true} + hideIfDot={true} tooltipProps={{ tabIndex: isActive ? 0 : -1 }} /> diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx index 4e84bee0be2..c8ad9d4acab 100644 --- a/src/components/views/rooms/RoomSublist.tsx +++ b/src/components/views/rooms/RoomSublist.tsx @@ -657,7 +657,7 @@ export default class RoomSublist extends React.Component { const badge = ( { // aria-hidden because we summarise the unread count/highlight status in a manual aria-label below badge = ( ); } diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index c0b71709238..315cba3c1cc 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -113,7 +113,6 @@ export const SpaceButton = ({
} > - + ); } diff --git a/src/components/views/spaces/threads-activity-centre/useUnreadThreadRooms.ts b/src/components/views/spaces/threads-activity-centre/useUnreadThreadRooms.ts index 115b2085607..72b5380fbd1 100644 --- a/src/components/views/spaces/threads-activity-centre/useUnreadThreadRooms.ts +++ b/src/components/views/spaces/threads-activity-centre/useUnreadThreadRooms.ts @@ -89,12 +89,12 @@ function computeUnreadThreadRooms(mxClient: MatrixClient, msc3946ProcessDynamicP const visibleRooms = mxClient.getVisibleRooms(msc3946ProcessDynamicPredecessor); let greatestNotificationLevel = NotificationLevel.None; - const rooms = []; + const rooms: Result["rooms"] = []; for (const room of visibleRooms) { // We only care about rooms with unread threads if (VisibilityProvider.instance.isRoomVisible(room) && doesRoomHaveUnreadThreads(room)) { - // Get the greatest notification level of all rooms + // Get the greatest notification level of all threads const notificationLevel = getThreadNotificationLevel(room); // If the room has an activity notification or less, we ignore it @@ -110,20 +110,35 @@ function computeUnreadThreadRooms(mxClient: MatrixClient, msc3946ProcessDynamicP } } - const sortedRooms = rooms.sort((a, b) => sortRoom(a.notificationLevel, b.notificationLevel)); + const sortedRooms = rooms.sort((a, b) => sortRoom(a, b)); return { greatestNotificationLevel, rooms: sortedRooms }; } +/** + * Store the room and its thread notification level + */ +type RoomData = Result["rooms"][0]; + /** * Sort notification level by the most important notification level to the least important * Highlight > Notification > Activity - * @param notificationLevelA - notification level of room A - * @param notificationLevelB - notification level of room B + * If the notification level is the same, we sort by the most recent thread + * @param roomDataA - room and notification level of room A + * @param roomDataB - room and notification level of room B * @returns {number} */ -function sortRoom(notificationLevelA: NotificationLevel, notificationLevelB: NotificationLevel): number { +function sortRoom(roomDataA: RoomData, roomDataB: RoomData): number { + const { notificationLevel: notificationLevelA, room: roomA } = roomDataA; + const { notificationLevel: notificationLevelB, room: roomB } = roomDataB; + + const timestampA = roomA.getLastThread()?.events.at(-1)?.getTs(); + const timestampB = roomB.getLastThread()?.events.at(-1)?.getTs(); + // NotificationLevel is a numeric enum, so we can compare them directly if (notificationLevelA > notificationLevelB) return -1; else if (notificationLevelB > notificationLevelA) return 1; - else return 0; + // Display most recent first + else if (!timestampA) return 1; + else if (!timestampB) return -1; + else return timestampB - timestampA; } diff --git a/test/components/views/rooms/NotificationBadge/StatelessNotificationBadge-test.tsx b/test/components/views/rooms/NotificationBadge/StatelessNotificationBadge-test.tsx index 66ae273e247..6ee93d82db4 100644 --- a/test/components/views/rooms/NotificationBadge/StatelessNotificationBadge-test.tsx +++ b/test/components/views/rooms/NotificationBadge/StatelessNotificationBadge-test.tsx @@ -35,4 +35,23 @@ describe("StatelessNotificationBadge", () => { expect(container.querySelector(".mx_NotificationBadge_dot")).not.toBeInTheDocument(); expect(container.querySelector(".mx_NotificationBadge_knocked")).toBeInTheDocument(); }); + + it("has badge style for notification", () => { + const { container } = render( + , + ); + expect(container.querySelector(".mx_NotificationBadge_dot")).not.toBeInTheDocument(); + }); + + it("has dot style for notification when forced", () => { + const { container } = render( + , + ); + expect(container.querySelector(".mx_NotificationBadge_dot")).toBeInTheDocument(); + }); }); diff --git a/test/components/views/spaces/ThreadsActivityCentre-test.tsx b/test/components/views/spaces/ThreadsActivityCentre-test.tsx index f5f183e7c6c..8deb27ec7e4 100644 --- a/test/components/views/spaces/ThreadsActivityCentre-test.tsx +++ b/test/components/views/spaces/ThreadsActivityCentre-test.tsx @@ -59,16 +59,23 @@ describe("ThreadsActivityCentre", () => { }); roomWithActivity.name = "Just activity"; - const roomWithNotif = new Room("!room:server", cli, cli.getSafeUserId(), { + const roomWithNotif = new Room("!room2:server", cli, cli.getSafeUserId(), { pendingEventOrdering: PendingEventOrdering.Detached, }); roomWithNotif.name = "A notification"; - const roomWithHighlight = new Room("!room:server", cli, cli.getSafeUserId(), { + const roomWithHighlight = new Room("!room3:server", cli, cli.getSafeUserId(), { pendingEventOrdering: PendingEventOrdering.Detached, }); roomWithHighlight.name = "This is a real highlight"; + const getDefaultThreadArgs = (room: Room) => ({ + room: room, + client: cli, + authorId: "@foo:bar", + participantUserIds: ["@fee:bar"], + }); + beforeAll(async () => { jest.spyOn(MatrixClientPeg, "get").mockReturnValue(cli); jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(cli); @@ -77,26 +84,15 @@ describe("ThreadsActivityCentre", () => { jest.spyOn(dmRoomMap, "getUserIdForRoomId"); jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap); - await populateThread({ - room: roomWithActivity, - client: cli, - authorId: "@foo:bar", - participantUserIds: ["@fee:bar"], - }); + await populateThread(getDefaultThreadArgs(roomWithActivity)); - const notifThreadInfo = await populateThread({ - room: roomWithNotif, - client: cli, - authorId: "@foo:bar", - participantUserIds: ["@fee:bar"], - }); + const notifThreadInfo = await populateThread(getDefaultThreadArgs(roomWithNotif)); roomWithNotif.setThreadUnreadNotificationCount(notifThreadInfo.thread.id, NotificationCountType.Total, 1); const highlightThreadInfo = await populateThread({ - room: roomWithHighlight, - client: cli, - authorId: "@foo:bar", - participantUserIds: ["@fee:bar"], + ...getDefaultThreadArgs(roomWithHighlight), + // timestamp + ts: 5, }); roomWithHighlight.setThreadUnreadNotificationCount( highlightThreadInfo.thread.id, @@ -181,6 +177,52 @@ describe("ThreadsActivityCentre", () => { expect(screen.getByRole("menu")).toMatchSnapshot(); }); + it("should order the room with the same notification level by most recent", async () => { + // Generate two new rooms with threads + const secondRoomWithHighlight = new Room("!room4:server", cli, cli.getSafeUserId(), { + pendingEventOrdering: PendingEventOrdering.Detached, + }); + secondRoomWithHighlight.name = "This is a second real highlight"; + + const secondHighlightThreadInfo = await populateThread({ + ...getDefaultThreadArgs(secondRoomWithHighlight), + // timestamp + ts: 1, + }); + secondRoomWithHighlight.setThreadUnreadNotificationCount( + secondHighlightThreadInfo.thread.id, + NotificationCountType.Highlight, + 1, + ); + + const thirdRoomWithHighlight = new Room("!room5:server", cli, cli.getSafeUserId(), { + pendingEventOrdering: PendingEventOrdering.Detached, + }); + thirdRoomWithHighlight.name = "This is a third real highlight"; + + const thirdHighlightThreadInfo = await populateThread({ + ...getDefaultThreadArgs(thirdRoomWithHighlight), + // timestamp + ts: 7, + }); + thirdRoomWithHighlight.setThreadUnreadNotificationCount( + thirdHighlightThreadInfo.thread.id, + NotificationCountType.Highlight, + 1, + ); + + cli.getVisibleRooms = jest + .fn() + .mockReturnValue([roomWithHighlight, secondRoomWithHighlight, thirdRoomWithHighlight]); + + renderTAC(); + await userEvent.click(getTACButton()); + + // The room should be ordered by the most recent thread + // thirdHighlightThreadInfo (timestamp 7) > highlightThreadInfo (timestamp 5) > secondHighlightThreadInfo (timestamp 1) + expect(screen.getByRole("menu")).toMatchSnapshot(); + }); + it("should block Ctrl/CMD + k shortcut", async () => { cli.getVisibleRooms = jest.fn().mockReturnValue([roomWithHighlight]); diff --git a/test/components/views/spaces/__snapshots__/ThreadsActivityCentre-test.tsx.snap b/test/components/views/spaces/__snapshots__/ThreadsActivityCentre-test.tsx.snap index 5b9e2091b75..0d2841c6148 100644 --- a/test/components/views/spaces/__snapshots__/ThreadsActivityCentre-test.tsx.snap +++ b/test/components/views/spaces/__snapshots__/ThreadsActivityCentre-test.tsx.snap @@ -38,7 +38,7 @@ exports[`ThreadsActivityCentre renders notifications matching the snapshot 1`] = >
`; + +exports[`ThreadsActivityCentre should order the room with the same notification level by most recent 1`] = ` + +`; diff --git a/yarn.lock b/yarn.lock index ce60176a7a5..6bcc5013577 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3013,7 +3013,7 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@vector-im/compound-design-tokens@^1.0.0": +"@vector-im/compound-design-tokens@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-1.2.0.tgz#ccb15fffc24cc70d83593bfc5348e6a0198cc08a" integrity sha512-8LSbb38KxvStcOQZDSi7lI4oqtCuHFEgEQi9Q0KUx+5OnklfdyJ638txM1bznX/Cp9lHgMk4dHrTiQHBOE0ZuA== @@ -6860,7 +6860,7 @@ matrix-events-sdk@0.0.1: "matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "31.5.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/461aeae2815a223c817c9768e26220cec4a69d12" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8e0ef5ff2cd927efa1bd22cabb075a14b10e39d5" dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-wasm" "^4.6.0"