From da03bdef1b884073eb53acc121a0077b2a11a73d Mon Sep 17 00:00:00 2001 From: David Langley Date: Thu, 22 Jan 2026 19:07:43 +0000 Subject: [PATCH] Migrate room list to shared components --- .../RoomList.stories.tsx/default-auto.png | Bin 0 -> 46838 bytes .../activity-indicator-auto.png | Bin 0 -> 3766 bytes .../invited-auto.png | Bin 0 -> 3968 bytes .../mention-auto.png | Bin 0 -> 4635 bytes .../mention-with-count-auto.png | Bin 0 -> 4791 bytes .../muted-auto.png | Bin 0 -> 4018 bytes .../muted-without-activity-auto.png | Bin 0 -> 4018 bytes .../no-notification-auto.png | Bin 0 -> 3522 bytes .../notification-with-count-auto.png | Bin 0 -> 4144 bytes .../unsent-message-auto.png | Bin 0 -> 4007 bytes .../video-call-auto.png | Bin 0 -> 3860 bytes .../video-call-without-activity-auto.png | Bin 0 -> 3860 bytes .../voice-call-auto.png | Bin 0 -> 3936 bytes .../RoomListItem.stories.tsx/bold-auto.png | Bin 0 -> 7391 bytes .../RoomListItem.stories.tsx/default-auto.png | Bin 0 -> 6804 bytes .../invitation-auto.png | Bin 0 -> 21906 bytes .../no-message-preview-auto.png | Bin 0 -> 5475 bytes .../selected-auto.png | Bin 0 -> 6865 bytes .../unsent-message-auto.png | Bin 0 -> 7549 bytes .../with-hover-menu-auto.png | Bin 0 -> 6804 bytes .../with-mention-auto.png | Bin 0 -> 7753 bytes .../with-notification-auto.png | Bin 0 -> 7271 bytes .../without-hover-menu-auto.png | Bin 0 -> 6804 bytes .../default-auto.png | Bin 0 -> 10666 bytes .../narrow-container-auto.png | Bin 0 -> 6620 bytes ...arrow-with-active-wrapping-filter-auto.png | Bin 0 -> 6777 bytes .../no-filters-auto.png | Bin 0 -> 3522 bytes .../people-selected-auto.png | Bin 0 -> 11211 bytes .../RoomListView.stories.tsx/default-auto.png | Bin 0 -> 42126 bytes .../RoomListView.stories.tsx/empty-auto.png | Bin 0 -> 18683 bytes .../empty-favourite-filter-auto.png | Bin 0 -> 14208 bytes .../empty-invites-filter-auto.png | Bin 0 -> 11596 bytes .../empty-low-priority-filter-auto.png | Bin 0 -> 12208 bytes .../empty-mentions-filter-auto.png | Bin 0 -> 11930 bytes .../empty-people-filter-auto.png | Bin 0 -> 14889 bytes .../empty-rooms-filter-auto.png | Bin 0 -> 12754 bytes .../empty-unread-filter-auto.png | Bin 0 -> 12899 bytes .../empty-without-create-permission-auto.png | Bin 0 -> 14855 bytes .../large-list-auto.png | Bin 0 -> 42126 bytes .../RoomListView.stories.tsx/loading-auto.png | Bin 0 -> 14916 bytes .../small-list-auto.png | Bin 0 -> 23137 bytes .../with-active-filter-auto.png | Bin 0 -> 42274 bytes .../with-selection-auto.png | Bin 0 -> 65657 bytes .../src/i18n/strings/en_EN.json | 61 + packages/shared-components/src/index.ts | 4 + .../room-list/RoomList/RoomList.module.css | 8 +- .../room-list/RoomList/RoomList.stories.tsx | 87 + .../src/room-list/RoomList/RoomList.test.tsx | 67 + .../src/room-list/RoomList/RoomList.tsx | 197 + .../__snapshots__/RoomList.test.tsx.snap | 1277 ++ .../src/room-list/RoomList/index.ts | 9 +- .../NotificationDecoration.stories.tsx | 120 + .../NotificationDecoration.test.tsx | 80 + .../NotificationDecoration.tsx | 90 + .../NotificationDecoration.test.tsx.snap | 242 + .../NotificationDecoration/index.tsx | 9 + .../RoomListItem/RoomListItem.module.css | 106 + .../RoomListItem/RoomListItem.stories.tsx | 206 + .../RoomListItem/RoomListItem.test.tsx | 123 + .../room-list/RoomListItem/RoomListItem.tsx | 202 + .../RoomListItem/RoomListItemContextMenu.tsx | 40 + .../RoomListItem/RoomListItemHoverMenu.tsx | 42 + .../RoomListItemMoreOptionsMenu.test.tsx | 227 + .../RoomListItemMoreOptionsMenu.tsx | 137 + .../RoomListItemNotificationMenu.test.tsx | 164 + .../RoomListItemNotificationMenu.tsx | 105 + .../src/room-list/RoomListItem/RoomNotifs.ts | 20 + .../__snapshots__/RoomListItem.test.tsx.snap | 1236 ++ .../RoomListItem/default-snapshot.ts | 39 + .../src/room-list/RoomListItem/index.ts | 25 + .../RoomListPrimaryFilters.module.css | 32 + .../RoomListPrimaryFilters.stories.tsx | 85 + .../RoomListPrimaryFilters.test.tsx | 140 + .../RoomListPrimaryFilters.tsx | 116 + .../RoomListPrimaryFilters.test.tsx.snap | 388 + .../RoomListPrimaryFilters/index.tsx | 12 + .../useCollapseFilters.ts | 71 + .../useVisibleFilters.ts | 55 + .../RoomListEmptyState.module.css | 33 + .../RoomListView/RoomListEmptyState.tsx | 182 + .../RoomListLoadingSkeleton.module.css | 24 + .../RoomListView/RoomListLoadingSkeleton.tsx | 18 + .../RoomListView/RoomListView.stories.tsx | 220 + .../RoomListView/RoomListView.test.tsx | 177 + .../room-list/RoomListView/RoomListView.tsx | 101 + .../__snapshots__/RoomListView.test.tsx.snap | 11387 ++++++++++++++++ .../RoomListView/assets/skeleton.svg | 14 + .../src/room-list/RoomListView/index.tsx | 12 + .../src/room-list/story-mocks.tsx | 136 + .../utils/VirtualizedList/VirtualizedList.tsx | 47 +- .../shared-components/src/viewmodel/index.ts | 2 +- .../e2e/sliding-sync/sliding-sync.spec.ts | 2 +- res/css/_components.pcss | 7 - .../rooms/RoomListPanel/_EmptyRoomList.pcss | 33 - .../RoomListPanel/_RoomListItemView.pcss | 102 - .../_RoomListPrimaryFilters.pcss | 34 - .../_RoomListSecondaryFilters.pcss | 12 - .../RoomListPanel/_RoomListSkeleton.pcss | 24 - .../roomlist/MessagePreviewViewModel.tsx | 57 - .../roomlist/RoomListItemMenuViewModel.tsx | 226 - .../roomlist/RoomListItemViewModel.ts | 327 + .../roomlist/RoomListItemViewModel.tsx | 250 - .../viewmodels/roomlist/RoomListViewModel.tsx | 100 - .../roomlist/RoomListViewViewModel.ts | 450 + .../viewmodels/roomlist/useFilteredRooms.tsx | 131 - .../roomlist/useMessagePreviewToggle.tsx | 32 - .../roomlist/useRoomListNavigation.ts | 56 - .../viewmodels/roomlist/useStickyRoomList.tsx | 138 - .../rooms/RoomListPanel/EmptyRoomList.tsx | 182 - .../views/rooms/RoomListPanel/RoomList.tsx | 144 - .../RoomListItemContextMenuView.tsx | 44 - .../RoomListPanel/RoomListItemMenuView.tsx | 242 - .../rooms/RoomListPanel/RoomListItemView.tsx | 124 - .../RoomListPanel/RoomListPrimaryFilters.tsx | 169 - .../rooms/RoomListPanel/RoomListView.tsx | 57 +- src/i18n/strings/en_EN.json | 54 +- .../roomlist/MessagePreviewViewModel-test.tsx | 58 - .../RoomListItemMenuViewModel-test.tsx | 221 - .../roomlist/RoomListItemViewModel-test.tsx | 589 +- .../roomlist/RoomListViewModel-test.tsx | 341 - .../roomlist/RoomListViewViewModel-test.tsx | 546 + .../roomlist/useRoomListNavigation-test.ts | 152 - .../RoomListPanel/EmptyRoomList-test.tsx | 92 - .../rooms/RoomListPanel/RoomList-test.tsx | 78 - .../RoomListItemMenuView-test.tsx | 144 - .../RoomListPanel/RoomListItemView-test.tsx | 162 - .../RoomListPrimaryFilters-test.tsx | 155 - .../rooms/RoomListPanel/RoomListView-test.tsx | 65 - .../__snapshots__/EmptyRoomList-test.tsx.snap | 279 - .../__snapshots__/RoomList-test.tsx.snap | 1255 -- .../RoomListItemMenuView-test.tsx.snap | 155 - .../RoomListItemView-test.tsx.snap | 234 - .../RoomListPrimaryFilters-test.tsx.snap | 47 - 133 files changed, 19892 insertions(+), 5853 deletions(-) create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomList/RoomList.stories.tsx/default-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/activity-indicator-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/invited-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/mention-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/mention-with-count-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/muted-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/muted-without-activity-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/no-notification-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/notification-with-count-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/unsent-message-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/video-call-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/video-call-without-activity-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/voice-call-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItem.stories.tsx/bold-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItem.stories.tsx/default-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItem.stories.tsx/invitation-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItem.stories.tsx/no-message-preview-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItem.stories.tsx/selected-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItem.stories.tsx/unsent-message-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItem.stories.tsx/with-hover-menu-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItem.stories.tsx/with-mention-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItem.stories.tsx/with-notification-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItem.stories.tsx/without-hover-menu-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.stories.tsx/default-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.stories.tsx/narrow-container-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.stories.tsx/narrow-with-active-wrapping-filter-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.stories.tsx/no-filters-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.stories.tsx/people-selected-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/default-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-favourite-filter-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-invites-filter-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-low-priority-filter-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-mentions-filter-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-people-filter-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-rooms-filter-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-unread-filter-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-without-create-permission-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/large-list-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/loading-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/small-list-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/with-active-filter-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/with-selection-auto.png rename res/css/views/rooms/RoomListPanel/_RoomList.pcss => packages/shared-components/src/room-list/RoomList/RoomList.module.css (63%) create mode 100644 packages/shared-components/src/room-list/RoomList/RoomList.stories.tsx create mode 100644 packages/shared-components/src/room-list/RoomList/RoomList.test.tsx create mode 100644 packages/shared-components/src/room-list/RoomList/RoomList.tsx create mode 100644 packages/shared-components/src/room-list/RoomList/__snapshots__/RoomList.test.tsx.snap rename res/css/views/rooms/RoomListPanel/_RoomListItemMenuView.pcss => packages/shared-components/src/room-list/RoomList/index.ts (51%) create mode 100644 packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx create mode 100644 packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.test.tsx create mode 100644 packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.tsx create mode 100644 packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/__snapshots__/NotificationDecoration.test.tsx.snap create mode 100644 packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/index.tsx create mode 100644 packages/shared-components/src/room-list/RoomListItem/RoomListItem.module.css create mode 100644 packages/shared-components/src/room-list/RoomListItem/RoomListItem.stories.tsx create mode 100644 packages/shared-components/src/room-list/RoomListItem/RoomListItem.test.tsx create mode 100644 packages/shared-components/src/room-list/RoomListItem/RoomListItem.tsx create mode 100644 packages/shared-components/src/room-list/RoomListItem/RoomListItemContextMenu.tsx create mode 100644 packages/shared-components/src/room-list/RoomListItem/RoomListItemHoverMenu.tsx create mode 100644 packages/shared-components/src/room-list/RoomListItem/RoomListItemMoreOptionsMenu.test.tsx create mode 100644 packages/shared-components/src/room-list/RoomListItem/RoomListItemMoreOptionsMenu.tsx create mode 100644 packages/shared-components/src/room-list/RoomListItem/RoomListItemNotificationMenu.test.tsx create mode 100644 packages/shared-components/src/room-list/RoomListItem/RoomListItemNotificationMenu.tsx create mode 100644 packages/shared-components/src/room-list/RoomListItem/RoomNotifs.ts create mode 100644 packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItem.test.tsx.snap create mode 100644 packages/shared-components/src/room-list/RoomListItem/default-snapshot.ts create mode 100644 packages/shared-components/src/room-list/RoomListItem/index.ts create mode 100644 packages/shared-components/src/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.module.css create mode 100644 packages/shared-components/src/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.stories.tsx create mode 100644 packages/shared-components/src/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.test.tsx create mode 100644 packages/shared-components/src/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.tsx create mode 100644 packages/shared-components/src/room-list/RoomListPrimaryFilters/__snapshots__/RoomListPrimaryFilters.test.tsx.snap create mode 100644 packages/shared-components/src/room-list/RoomListPrimaryFilters/index.tsx create mode 100644 packages/shared-components/src/room-list/RoomListPrimaryFilters/useCollapseFilters.ts create mode 100644 packages/shared-components/src/room-list/RoomListPrimaryFilters/useVisibleFilters.ts create mode 100644 packages/shared-components/src/room-list/RoomListView/RoomListEmptyState.module.css create mode 100644 packages/shared-components/src/room-list/RoomListView/RoomListEmptyState.tsx create mode 100644 packages/shared-components/src/room-list/RoomListView/RoomListLoadingSkeleton.module.css create mode 100644 packages/shared-components/src/room-list/RoomListView/RoomListLoadingSkeleton.tsx create mode 100644 packages/shared-components/src/room-list/RoomListView/RoomListView.stories.tsx create mode 100644 packages/shared-components/src/room-list/RoomListView/RoomListView.test.tsx create mode 100644 packages/shared-components/src/room-list/RoomListView/RoomListView.tsx create mode 100644 packages/shared-components/src/room-list/RoomListView/__snapshots__/RoomListView.test.tsx.snap create mode 100644 packages/shared-components/src/room-list/RoomListView/assets/skeleton.svg create mode 100644 packages/shared-components/src/room-list/RoomListView/index.tsx create mode 100644 packages/shared-components/src/room-list/story-mocks.tsx delete mode 100644 res/css/views/rooms/RoomListPanel/_EmptyRoomList.pcss delete mode 100644 res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss delete mode 100644 res/css/views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss delete mode 100644 res/css/views/rooms/RoomListPanel/_RoomListSecondaryFilters.pcss delete mode 100644 res/css/views/rooms/RoomListPanel/_RoomListSkeleton.pcss delete mode 100644 src/components/viewmodels/roomlist/MessagePreviewViewModel.tsx delete mode 100644 src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx create mode 100644 src/components/viewmodels/roomlist/RoomListItemViewModel.ts delete mode 100644 src/components/viewmodels/roomlist/RoomListItemViewModel.tsx delete mode 100644 src/components/viewmodels/roomlist/RoomListViewModel.tsx create mode 100644 src/components/viewmodels/roomlist/RoomListViewViewModel.ts delete mode 100644 src/components/viewmodels/roomlist/useFilteredRooms.tsx delete mode 100644 src/components/viewmodels/roomlist/useMessagePreviewToggle.tsx delete mode 100644 src/components/viewmodels/roomlist/useRoomListNavigation.ts delete mode 100644 src/components/viewmodels/roomlist/useStickyRoomList.tsx delete mode 100644 src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx delete mode 100644 src/components/views/rooms/RoomListPanel/RoomList.tsx delete mode 100644 src/components/views/rooms/RoomListPanel/RoomListItemContextMenuView.tsx delete mode 100644 src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx delete mode 100644 src/components/views/rooms/RoomListPanel/RoomListItemView.tsx delete mode 100644 src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx delete mode 100644 test/unit-tests/components/viewmodels/roomlist/MessagePreviewViewModel-test.tsx delete mode 100644 test/unit-tests/components/viewmodels/roomlist/RoomListItemMenuViewModel-test.tsx delete mode 100644 test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx create mode 100644 test/unit-tests/components/viewmodels/roomlist/RoomListViewViewModel-test.tsx delete mode 100644 test/unit-tests/components/viewmodels/roomlist/useRoomListNavigation-test.ts delete mode 100644 test/unit-tests/components/views/rooms/RoomListPanel/EmptyRoomList-test.tsx delete mode 100644 test/unit-tests/components/views/rooms/RoomListPanel/RoomList-test.tsx delete mode 100644 test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemMenuView-test.tsx delete mode 100644 test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx delete mode 100644 test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx delete mode 100644 test/unit-tests/components/views/rooms/RoomListPanel/RoomListView-test.tsx delete mode 100644 test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/EmptyRoomList-test.tsx.snap delete mode 100644 test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap delete mode 100644 test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemMenuView-test.tsx.snap delete mode 100644 test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap delete mode 100644 test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPrimaryFilters-test.tsx.snap diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomList/RoomList.stories.tsx/default-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomList/RoomList.stories.tsx/default-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..9cf03c0899ed60f1810afda5f601940dd8cb8d6e GIT binary patch literal 46838 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 literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/activity-indicator-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/activity-indicator-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..6ac546adbccfdea05bc0f93180ab6545553ec292 GIT binary patch literal 3766 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zk17EhMi(^Q|oHuuDvqTd` z8XoSv9%9(}QlwmgLr*P&B_w9nqNIiITm@p9X6XHoHTtRGduq?!a2x;9bCCy68yH)^ z-lfODaNzM99U!@wGlPMFgT;h_frUv5Xb&SV14Cm28$*MG12cnz!U9GH1%V3;3<4ZT z$`Y(z@A~(<-l%Q5{=9s%*{lsSIWih{{`vR$`|0oT;?u*E?QYz!uCMv>?CWaQhRhss zhGm7t)xUq>#{J##$3J|$d%G}(VaBnv*y}*+fAq>8I0N>Boe~RP zrxWSJQDtaRJ{lmS0fN6!7)>FgDP%N-kW`P3W|`3}Gn!>avkcZo_GsZSS~v`R;jn|5 of#LuEmn-KN0ehcppl&BKL(&F!P5GIxBY+|dp00i_>zopr0MGn5HUIzs literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/invited-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/invited-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..cfd3f0f556f61e591ef2007ff96671d63487bedf GIT binary patch literal 3968 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zk1HY-Ki(^Q|oHw^^yJb@) z8Xj7vD|-stH+OU>u=TbIu4&0wD$o-V5phJRW38CdFXk&~7}p{t zj}qy?4YxeDO(@;$XlD54WMiucr^@p@qv#m%;4^zZUpajC%%0t8n{NVb)JeMzB7Gc~ z85|TAFfu3zTwq`j;K*QL;9xOfU|?aA0y>V7mw}G=55<-}y5^7dIza}pE zyZZ5c#)gf)>{caY|WS|nxjE0Z7XSYoY z4>!}xsr!3Ldj9?opK7aM$3?qUWOc01$JZq{}*prei_JM19hO88ML{pt2W9{Sq~Ip N@O1TaS?83{1OO5ws`~%{ literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/mention-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/mention-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..bacce8176c82bd8357dfe9435300ace9cf0702a6 GIT binary patch literal 4635 zcmeHLeNYl+7=M+9rZd`AYo$dSSFV+4Sy_r=PII0m)ppCT1pD$6Sd;=0VA-in({t7o z5$XCoXF}OhBoj5Wbt+h}+Y%*6i=v z$SEqT#S^?eb(_1=4#huA{l#NG-;pnc!_ehpMyh#UGfERv%hSH*#qO|b?-Bb#0Kio( zOa#Dj!4?1(*srvjX6FLHnG7h%2n>XPz%`fcz?#LKRx2I<+_nS?>TjW@TeOp^x=BTL zu?wKA?>z&hP$Oqsv=FgzqMIy74Pg_G(BDSU5C+*HtgagjTZX;{sboft(z zz^GFhRhpe|`ANYq8Oo)Cml4*B#J4jsbt>)EfT=rI6&B4{1oG6YLWHOP*fctp z-o(I~$Md2ju2BsyDx%QakD03-q^HMRq#TOw<3Y;64K@sXlwiLS7kq4xWKtw>Nl(n# z42q%My*%!CzkCCndFoMidani_v&S{ge^#p#ofuhL3paFlxD)FQ&(Da=eFX%;A-u|> z!jlEs3`7=91_7-_FeBLY^X=ROb#2%EtQnM?#4s%kbt5&gM1qM@4#r?8X3$!vluzNW zThiGLO2GzXH2p|qFS?vI60Km0&-(F2dY&D4x!dYsvxl?#!PxN8N`_K@FZ>u0m6R!h z)1P@{yV0cag8`cUHgR2=YHFT9lutCvh;ejf{#s|Q2X~2irpW;c^1SU_yqv<}%v4I* zj1r&qxYC^d`1ZA%x6nzi_37-?**h!licLC^y`d_GV60dBO4oeY2@uw@*%dDsL7@T! z^G}D5E1|9(G1cRB;kBMrT=(3Cbvz$bfZ=(Og%!=r+__H*nfioM=+yY>`{ZJuNb-vg zXLTQSQo6c+(g9}ZkibNgS)Po*G-C;+wI?;rp>P(xZ6;yh^uTll8xuz~b>D=B9E3II zzR`N5U=`V{C=1uq+x+xQ8Lbt1*u8yjKyv`Q`9y9NKVT?_+x{R~eHYV5(q8OJx4cp! z_~odhxfkN*#WioC1$PwZy-6(be#N)cC% zB2HXcej1@aejt|8a(!%3PqTu^c-&`n+YWGL3llS*?oijD8QQ!h+4se0IbNjS&2HF+ zknm#X6km$ACNyzU;%ZFm*d6|_OZKe8|8b~Lh}W37<#go**v66{#X>k|)8qlJ@3qouwN;3%LTnZCpMIw{$k-rbgNzL_f7`mg zyW}vHaLojOUCWzUz*~3X@2)%lUl)eB*4c{1k{qz?iFE;elTQd(>_Z|i_3?sPB_J#$ KBA6G1JNqjgKs%rS literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/mention-with-count-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/mention-with-count-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..773d314ec3ad49f8b7f193ee46bf1c22915581cd GIT binary patch literal 4791 zcmeHLSx}Q#6uvA&P*#`PC<-{%Wrj*{KtP~?l}afJt!ODCn-r1F2oVTNNVG!iK*@*| z5dr~GlwpZ1k`T7Av=NbnWgzTf-$@Dqviu=`L#NXRA3AlWk9oRx&YZdT%sJop%{k}i zZ{3`Ab{^OX0MK#y+UXJiYD@rByEIi5mab3IrvT6gE>34JW68^-xMiQ90Ol&RG}MzB z6A_W`$j#iBUD;ihpKBSqmo{Ii8Q`WpZ`r!9MW43tc0}VdCt8rV<>Ux5eb=0E$KJYK zev?`mNcFQjpFOy|_nYs=ps^oLl%2eUF1>q6AG#>>IkzT}MMh!Rad|>srgyzNGm_6*_ZWr1-gSNr6lWWz$ z`JiyYScBr6pI8~TPY_x4hhwoTnK~>ZdjS`}Fz=CW8?gAi9c4D%Pvj?bINOVv!ZI69 zmfcJ%T3*OOk*4CIb-%3f-|PsH$MaRr2M;tq8I2lErrJtc9=XLy zr&CxozuPM?UR+|8BwZ22PxZ_ov{}Hs%r$y*yoNdm5%8qu#@CUaFhHqi7bNF1N1eq)8s&&1T#>FjC(DYfpP%eMFziQJc znti-n#-U!rn+cmjn=7oyIQi{}1i-}Z?gB?D$0_p3=u}eOT6I;5E8D~r$2&JnJDFpP z?o1)VKhB_iquvR0(8zKNO7%P)|Ai#NU2p%s%`{QNRPk2ht6)=`zzr|7HQ$-S#jj-S zDR0EJCXwh8vX&>?;fCUmN-YHQ#7M2Y0MSH?=DBal*Rrov6}fo}%bRk}1TDtaL|jc= z^=pXf?wNSuftR$AMk?ZALSwK{`ZBNK{i&eI>gF0-PW@ztiIsz7P4k`;n0#649y6G?V(BGh#r%ZP0&ClPh<%Hpog#mTc^k&z1&+#vz@7RRt^k-Y`vYR zO(Nm&T=T-B3%34wJ~{o@BA22CTLTcUA!bNWzxMobFJb8f=Spvh1>9LJgazUz{uZt>gl#DG)dP=*w(>-X}zZl6be`C8aL};NYHv^4z}J{q59IN{Zs7T#0tVp zVPcLUU6hRWwu4>|k11+ZOw3Q%=w#Y9uZMPnOlYQQS=S%a{GIb%~4>AO6ruI5HR*I9N;= z7+9F3fR13~WngG*U}I=-aA0O|P*}jopdf&$tVx=ULG8iR>t%cYUp#pAcWZb3oS*-W z$JhO?Wk{&XS-`0F@L%uuefwVDJ$?GLe}46!+3(N2sY-uu5fc+rlk$04p%H_DB$HHv zkld{;AD_*>w(rlYbFHndZ=>aBpA9Q}f75*SSw0!NeI@@MdF$I*GZ^p!)oO|5MM#|g z+unEf`RBsdHfCR{<>e&Q^G8$G|{QUaz>ggS&a_6;q_Zr>) z{qf<)e{W}Q2AZJ6VsaqlPvy%?_1`xxJZ}4?PknyG%(ZW*Z;MJ*uJZ8-jAZfkH?;G%eO53^@ibq3D7Q2>xwU%Z|0=lapq_6 zY35(a;B)lS&+h-$^4~Ayzx$WKkRS!LQRPid_4)J9>-OHdtbBaG#D4<@1IDGhCnAz&4|0xA`5*e_`*vpX=}Wcx`I!XYTVk zyZ>&B4sVyfc7J2Z!$%AUzHT!BMW@=c8_(5`7k_5{&dcD_1B$u>+A`!uERpd)s*JpR zGa7EA;fB9t9ZeylDP%N-kkl9%%`&4|W;Dx;W*MwKmC?dsv~a*(IQ$3pxc~nTy0La0 gu;T)wPq|~pTUA~liCs2gJ)78&qol`;+0PSkiQ2+n{ literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/muted-without-activity-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/muted-without-activity-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..d4dbca6e3921e1a547802f6979ccb4a9497365d4 GIT binary patch literal 4018 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zk1An@wi(^Q|oHuv9SBIra zG(6mz&41*LiN?lRrW*{r7J0Ga{GIb%~4>AO6ruI5HR*I9N;= z7+9F3fR13~WngG*U}I=-aA0O|P*}jopdf&$tVx=ULG8iR>t%cYUp#pAcWZb3oS*-W z$JhO?Wk{&XS-`0F@L%uuefwVDJ$?GLe}46!+3(N2sY-uu5fc+rlk$04p%H_DB$HHv zkld{;AD_*>w(rlYbFHndZ=>aBpA9Q}f75*SSw0!NeI@@MdF$I*GZ^p!)oO|5MM#|g z+unEf`RBsdHfCR{<>e&Q^G8$G|{QUaz>ggS&a_6;q_Zr>) z{qf<)e{W}Q2AZJ6VsaqlPvy%?_1`xxJZ}4?PknyG%(ZW*Z;MJ*uJZ8-jAZfkH?;G%eO53^@ibq3D7Q2>xwU%Z|0=lapq_6 zY35(a;B)lS&+h-$^4~Ayzx$WKkRS!LQRPid_4)J9>-OHdtbBaG#D4<@1IDGhCnAz&4|0xA`5*e_`*vpX=}Wcx`I!XYTVk zyZ>&B4sVyfc7J2Z!$%AUzHT!BMW@=c8_(5`7k_5{&dcD_1B$u>+A`!uERpd)s*JpR zGa7EA;fB9t9ZeylDP%N-kkl9%%`&4|W;Dx;W*MwKmC?dsv~a*(IQ$3pxc~nTy0La0 gu;T)wPq|~pTUA~liCs2gJ)78&qol`;+0PSkiQ2+n{ literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/no-notification-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/no-notification-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..9f58a62407b42321e1382e699e8f73b601b1bbd6 GIT binary patch literal 3522 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^!cyxh~pF{EP7n`?%Q3)d0RH^SDLW!}C&`|q3X^UF&&{>%QmZvH-7+tsFq#>U1#Tc4#T zu`n>4nccv~(BR;}%;2D~fRRB#-~t1K07nJ`0|$!<0|N__6wr~3ybKJD$jZd#&ac0_ z@n?7LWAp6t*sZ7i^sX>;cfU3|pj32Q`~00xmoCfKeZ9Hf*!b~dKksQW8;Y(jJuX-O z(eQXbLxjwKrtGZH~9{Xkf+;T}q0~1DGgFWB( z-zhuxcYEHgZ!cwLuMDlWD|)BOzAna;DIws$6r;hG>bUy4cU5xI{Kqy6GB^ugU|_C( z`ItYi;$i1co#UI$x7Qf~RWeB>Bz)id@lpQf`O9WU?0nebeQ)Qhbu+W`YJOh%`uohU zjo&NEj;}u7#~?AKflcDZG5h-=|Gw9k-!0$rbs775`}E7-O0)T+x0kFlWZ?F9VCH#q z_x$#AcK=NN-aC7K-?sOi;kE@&x_Z~!em_-S+{+kI0kW@JC;sF0{<4~)r*p$)zCSts z`r7=WTThPI_b^68faKlR+^hapc(g8kPt8x$a+y6jzmCVd30v#WosrFR;1I|o)=6LE zHuuNp*M3}f_O}16g15(bf7gFG7TjOEF}V-ulJoK_8F+F|n_mvz9`pAq|NZ}uKmGTf zzPI#d>FVkJK)>`^0}YA(y#M5FKihn})3?p%te?loroMo&t^C|W_xZM4>)xHMFqqfN zATi6`f{}OeCfYE|Hht|Z_jV* ziGTc9arb)5itVQA@+PJ`OKx&B9CZXG9|J>BqVgpzB@vn6MwOxEz|jC14G{cg^k@nh zO(CNxgrsK8XqFkxGNV~$G|OP^^Nbb_qlE+Ig~JDHV0Zujf17eOb|8Zd)Zu4l`2Fml UxO9Z65Kx4{)78&qol`;+0OThY#sB~S literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/unsent-message-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/unsent-message-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..c2bcf320c59f347878ce3ceb08a80703715042fb GIT binary patch literal 4007 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zk1AmOCi(^Q|oHuv(ubx`U zaQx%*IMe*=0n7)TqE%LGk-ulU=Fuigq zAR*;x0Eb?dsY}+9>ni@+I_79UseW16cD>tp&gaT~^#^7juAH~I%xpF<1H*x_%V_xZ;ee*FK* zCY4b0I`7uDmlN+UxBKzp(x=zi`RC@|-1Gcl>Fqojpkekwj5E5D{omUD{kS}Q&z8dP z%(w5?B}07C^5@^Z-~0FcdG+Sasimnv|G(DOkNN*&9%Dl$FhVXDJ-qUAGP~WM-%nO{ zzrUVyZ_Ce%?PBJ+G7K|#z;QF1gS?m{GJ;2yjRr6NB49LyjHZy$6hcz1Ihtigv&?9g z8O<_So7AI)!)W0!S~yU@aQMf_!0`Y7wUg(89d8CUP(PcQ!R1fa_2Yt)Y(NnPPgg&e IbxsLQ0I3DV?*IS* literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/video-call-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/video-call-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..9a6f8b5b35282dfcd688b33e9c1305c8c5589cbb GIT binary patch literal 3860 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zk1K&wc7srr_IdAUTul7%5 zIPmfDu8lv!*%k&!+&JO1@x(Hf`~wQjM^7c0Zi>)2nQ-KhsKiq~E=k?#Gycd<3A z&YgFE{hpV<{w(RY{CQ>Ef$h(2mZz5)&E#QVIB@C=ACT7}U}2I1TF%JJ zz|h#h#?av4z|7#Fuz-<4LEr)dg8&C^Wj?LnT}%Uo5Wf?>}1x11&~g zgMF#>?mwf?Gx#urU2c4VVfBHzo1e2YENgCHleoWY>**T)Jq1sWZ1%psE%(Nj`q$eS z8v-p4F&gZ<8@)|$_p=k{Y~QcHC&M5yTNY?>tXUfg%i^ Lu6{1-oD!M<+je(= literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/video-call-without-activity-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/video-call-without-activity-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..9a6f8b5b35282dfcd688b33e9c1305c8c5589cbb GIT binary patch literal 3860 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zk1K&wc7srr_IdAUTul7%5 zIPmfDu8lv!*%k&!+&JO1@x(Hf`~wQjM^7c0Zi>)2nQ-KhsKiq~E=k?#Gycd<3A z&YgFE{hpV<{w(RY{CQ>Ef$h(2mZz5)&E#QVIB@C=ACT7}U}2I1TF%JJ zz|h#h#?av4z|7#Fuz-<4LEr)dg8&C^Wj?LnT}%Uo5Wf?>}1x11&~g zgMF#>?mwf?Gx#urU2c4VVfBHzo1e2YENgCHleoWY>**T)Jq1sWZ1%psE%(Nj`q$eS z8v-p4F&gZ<8@)|$_p=k{Y~QcHC&M5yTNY?>tXUfg%i^ Lu6{1-oD!M<+je(= literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/voice-call-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx/voice-call-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..86f5cde837ccf25a264e080cd733d413a0899bd8 GIT binary patch literal 3936 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zk1HY)Ji(^Q|oHut4zP?|~ zaQq|x-(%-9xqnOxYr{WJ(Yp-yIo?f=j0o*Y4Q4qaz?D zW{aXoooP~b+B5O}C-?ony1ne?)9*I5_uG#@{_<;&{r>}-{pa7){`|8BXydl@R1oRw zz|7#Fuz-<4LEr)dg8)Yc0|N((2?GNQlN8WxjJymCjSXxJ4Gvh99T2FqD$e?Q=H=Rr z_3v-pi@om4(DSP6XN?`>hMe7Tc3VE)|GBpC{Jv=W+FwUG8vVNa&XD~=iZD5l)bny1Oh@Sp= zRS%Ed-3&B_GowM#rs%=Co3BixAK!VlUzmYe7^ri`+IKbo-mz~xcG&%%P2snGh|0h{ zCGVTR->vz5^KAV)`#-l<_7-;^=HF*u_GY{AbUDTdtM5QZq&)uf>|lA^|0mDi=k2UJ zfAKhQttLn=B>=3gm2mU8_F6f6t$qf3F^LHl%*$W-u?dw{5F`FH~1&mgt@X?ir=M#Bw%sW+NJMpMXW3L&YXFq&mXv&?9g8O<_Sdl93B!)W1v tuW--<_EZ1=XZ{!S6xdH@19gy@8B(SfRi0Z_z8EON;OXk;vd$@?2>^Z(u0H?( literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItem.stories.tsx/bold-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItem.stories.tsx/bold-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..d36edbed0c8de3dc1216532ef731579711aa0e4f GIT binary patch literal 7391 zcmeI1`#0P9y2rn*)tWu6nmHZSmN2tBXG|&Ct#PSHvZt+@){t>48j(TOkdhJvA?cMF zifOw`-CG8AOQ=hTCbSw+G>KcHiHK5yh`5B1NV4s7&Ob0~owfGbYxS4shtK-_@T|}C zdOw%X^V?4zt_GhT{S*KIg9|^L^8x_9E&$lg{bcvf3D+ol8UQ{6E}Z+`C$VTzlp5!^ zep9j{-GB1V-Jk9};Y|v@Y;*qMd!Dubq4{53IJYiYZ`?amYorF6l3zzMN13)x7mImj zwAxUlx#0Lv#OKsK!pN}8*PY9J^-@1E)ql|Pb?ccYkIikqHmFzYT{hfZzV)STh-BUA z*AKGOH6k_|ixF;KMEX*X z938eWVb3dA*J+o`K%2p?j3iD`Bl^Wpw%}hEnOhZwJB&EH3OQi@^}Wlb8s&(DO}N zJ&I2~uY5lH+S5+{_;3_9@RWQ5YY1I3-Fd2Ii?5MJ6RsKxF{uYi1#i!L>(@!>n$;O= zKa$SeM$5~i;*Y;&qZb4k2v1CS*)`4LypW|uR6W8vXrFhK2vVtD-DOrV$CSyg!H41Ch=sY$?zvP6 zcVlF{scO765;?7BZl16tGLX&e!lM&-y*F8gM{w^A#HdbnbE~iTF>Y|aJbk|@jJUAa zK^hO3D;<*AeCR_usc?=A60Cm-Q}Zn^d5oEUS0F%QEf@& zxNWTN^cQEV(D*mpzOq>bh7lv((_+0Z9Id7HXRhEF*JWcvQ>XdhK=H)j@}#wcwYB?W zh>k7oF-C||S#-i~5OAYg@cmu|&DsRMF$sk@K{B~DXw9mvSeHKaWeX3kN_1P_48T$7 z*9DJ;dwlH4iEWLmiX?dSwWQUhfMJ^gqAJFvUy#Pj?h}<`dQxK91DJ3`wH0-_p`TnF zKWb-t-Uw8mRsYfpOZm&N%~8S>_kuUe@I=8Sa%5!S`dC4B(8H?}ju=7JY+(FVS&64c zS$Z~1#=WVr>c>dVdK|eVQjK(5xoOz#3q41?!3B0vK8>^QA6bJyg-%WvtuYHE$j^OQ znI(aAMddKT7ywL{4qP>it1eGncm<)x$HjbwM`xOpn8YmJsAU!V{vmLyuNP(vO*e_* z*EdRzwbVyd^vH=TxsW^s?eSC3Q zdATe;358e>@{M zt>K|A{(Cll!%L}$#nx@Gvwjz8?-Cwxbac*mPqnaVLvnJ;lgIyohzS~ixKR}FqMl=ZNi(gT+g6o8|r!uE0<5@-OG1wy((0xjD=1Y zrRJJd(7uvr>&v8+;SVPzK}~`lPJT+K$rI>LeVe zE^2$i-A^n!r5JensKXvJV*1MgD=QYFBk{_50hXPwyzVC3gWr~8((Kr!D}xBmR`AJS z!JaAw`3T{i-LSsBC~2MP4NsQc8hU&h2R?dLPk#p{`LA>_VaUiFsTgG%aE#JlDZ%+r z?VXp*;F?g)t;}yw;#2F$oVO-O6aaKZub9nA9I)fcpOk`3iy{Zi=H%zl%&bD4Jd^{A zl{R4nk#I%18{28Vld@^LK1=SZS4!AEL-HRJ+X9 z&(nx3_}C==Z{exY<9GR;+nv$Zz)lI#Xw|Yf@Og57r(&cnOX$3L7`TCd?Dzy7O%hqg z4r(61u~0-2-b<Fshk1OyPipH50~WNzUpL7o9>yA2vbH ztI{=EnQw_>RLpeqPEp@Dq_60p`9%rjzPBEUENRA~Y;)N38^fFwzthk8q|~842lP!C zt%}YBfEV4b5%1?jbe5D6O`3mi+!_ae54M3cDtp#5*+I))x84?`QijEYbZ5o?IGVUe zH6l>f37;z^pSbnsif}XRwX5!o2schf*_d{FA7?^!uDfz-jvw{@+g7lK&8QtbmR)TP z0F$8qCf*a3p`CH_x0Q#m?*^f@e6*(h|C$DTcH%zPhll6Q9?<)Z>G*4Y3+=dm+!~zF zjRwDKkQ7@Lo0JylDk?bGcgx&7#_H|dP3Q@iMp-`PZKt$FS$9pm%r#z0nY#;OLR^KM zL)vw}QehAPEN}d(N2@8Ds)>1+L9L`cSBGn*0jR@c?MX63VP-V44=xwo^JSHim=8%EU>2C0k1w?0PFkYs0+8*kimk zKr80fPkYym4u&mpnuY>nzV$75lq580<7R3BUP0 z$rNRB@g=sJCsc50%8#s&kfO1M$d?SZyUPp8I*fFg5Rd(e}e*vN>dD_4h zd^$<8&{B`&*{E5d@^IotpKeRcSwQJ}GjiExXTJ$b);3IFd4*9F_{tkleAR|XF+hu& zL4K!O1`B7;}5NhEXvO_mY z^NoW%CT;Af^#{`{8O3&+9}3xy_@(FaFP;*i$u_F~0AG|Kjr;CJYp^=zpj35f{V=p{ z3JJ$p7RL=uh{anM*D{$WkEwjVFw&eyQVc3)6-8m=2gOWUGH%VW%5-S zgbQuwaH%cAfqIRmD=1yuqDdZtuR-SL92|osWaVEyoaMIR40YD0 zO=Xy8`w>Qf!B{FoEK*}w7E!nUP6i5l;M3YxY5ZK zS5RZ9co(Du?dRjOsKt?=9Z8B+Vs&-ZPn&VeZK1UX6c@TO_)#23uB>%GMD_vpUq^TP z_t?K79fQ{VLfQVC_Yt{GZC1BA=JqOATNo1W<34C7L|;p0geZ)t1M0rKK6o@E=AtfF zRoO~+jgy|9Y@4burY0OJE+n=q{ZJP9kdG_1hEED2h)yB z2L&|NRY#r^1yKmq0@4+xXM8u!4$dg6X8leh8tETy$jcz!PV=qXsc%Tp@^nqI0>q_^ zD2B3Ed;Km4jSVXAz|^u&;ar)DSM1b`(e2&^Orl>RMeYRl@1EY;;ofxK2sn_w>gtYx zkdz&%drkdBRYh!D4-kC$nbu{hBHqL@?Ov_A`{0^o_K}li*wk)Xo^<1=m#;(c)QyUq z6fF+v#$1u0V;4`lx)-g!8q#Md#y3C^H67c>^mReAAg+O@ra$B1B(o9*jz46;`Y|6C2-O2#go65vegS~Zhmzl3?TpNy?Nr$RHI6BI z$pivRP6{X4>v)z{R+cnP(k@^by!_P}0Qh9=)y`!5r>5C|)8)5wZ#)2C|F8f03;2(4 z_@k45_Mhb6MApYZeGJq`^!_udz(=iq)apmAe$?upS-*VbtB-Ty2qcg(Y40R)ihdW>e{9V`!|g&Hbocves|wNmkbfr>&(ii(H|0&?8+*?+LX5IspW-K9P{dIRV3#N@N7a^4jZuN^AW1oHLP`tmvmCNfL z9njY~JVkDD3oPJN9P~41|G^3pYrB6T+9XLjBf`7`4f+MDFbZktnDaH!T$4qa8 zw6==FCG;O-m;-6rYxAR@<|enjteA?IKk8341AtY$Z)9jsVae2LcU>O#HE2Wqq{4-4 zp<2iUYg7E7S)AmUOK)Ccm(2`Jr)&M$Ri!JPn)Nr7Ddp|Wt4?(JfZV2qe45V)0rh+O zhdeqFTU~rw0`|k`bTDX%YW&~jvc_CwoUYr}K|R$@#6iR*S@4xM!E2F`fE7dPm~$j3 zK8}Bka-y$7MSS>a+LFo?KY2{(XI_2NQ7?IMapW1qc!P+~dp)|}O#&^$eHyc))EQTrlWDyxs$r3Gnc2Rbrw4Jd|a zyk5!}(TLNLJ@vZk}xU_Y1P^uKT+g})3lV);jz=5@lVbAue&kFTK>dkwJ*@Y+FMbZ zl@x!-GKpI_Z+}t|)Bn*+{Cw>ivs5@gH}4GAr#t@7p!ySTClC|m32ANi2LRxJ_km$! zR?VrILCfstn!Nr=nN6B;QWQ^EF_d~c{g_bRtL^Q~>f$4GN)kPQuG|<3_lJ(*3uHZ( zAe-WZ!5}CMIgKgYZy1i2_V7t`_B4uJ`B)x?+Gwuo)oR;iS;j(BH`_XP)Cp{5Lf0Ul zq7~&$(_%j7LfH;2Itec&3HO!X&-ET1@>d{tcljTBfxoxyM%m(0$G*`R1Hj3ocecC9 zycW@FHfev{K8(mdQ)Rjs9K+f4^P@E{aQCtskEbgZKkyw=qp#zbnR%E^@;u8UPc_1* z*}fn6EkM6Tj@&x=0!&nJETF=4MZubdsJ`@u!(~?T&i8rh86hqe9y&tD@!r~M(d$d$ z;`$mir{sxVdEX=PXF9mD#-J}{OGRsYr8g$jxoJ?!^qYCNuQpk|hI?QF4KGR>*&G4k&PPsPd)l0arX^o>GTyL`f#lSAb--g`OTw93h* zoHKQ;CWj4_x+N0s9s%bRGprSbWl3s}lJleQVk&{*Oh}LhT>by@L zL-vu)n2@wS8~gF~9C6sstBalZVpvlx9C*D)6CN_q4N;Ms(P@d z=k?1uqAO1N{vqZYy}r38UsQP$A^Th#(WqOXDt8I2FX?jaRY5B*#?CG|Nt<%RDVP9R z3m`O?mjFI|wJOtN* z9CkVU1W}h-`k__pJ%Z!usn?)S2s!xJXz0@+tN1k|@rI?T0ocjEv6XpKrSw_xxn#3Ys>tE;GOlPYOn@IZqMe z%uRD2M_Mo{Ue}~l-TxrTX2rlTpDoQp3t6;FPA_zemZrL$o+H6zYpi4}+ADVn>1l&QPtcV#Q3khNnwO5mJqz*5emPFFBl>T7AGl(94p$B~|@a_CkDm z!@~|Bhn`^7EWuOKk-ID<1<|7NXgFb8x>_EcUk)141}T?!RH z?~X{4FHM-HUy+5%g_NT*@#Lz^p#RfT*l6_H$KGO1D`kQkyRfj*p>DSF^7U<2{A*4a z!9ks^Q$BXe!Y{MdmoF1aX?~tTd6FgYL3$$B$nbH=MeWB`e+~;3b~_{w14?E^NVSs; z+g6(Hj?)ttahwPmNWl1ut%+A)Nj|S*VZz-Ad46MA>sxC*-16ZElqjxCLRggNA~I(_ zS@XLbCEhHyM8sAKxx_`hRX+i7i>qDGVQ2Qr+cc4qk{pJ6?>3cby5np?Gz9rcg%2tq zu6Q66HcWT)s?##(N8OOQwWf3ZcdmDE^~5knXpu=?_CM7dniwO5N!FFUgPs9NI)8XE zD^+nus5J3>ow&+t34};zNqw+eVF6qo?n}!jFYumPX`{%(E3GX&)1jle?XDq!rhM(X zO=@ivl(D&B3#K-My7w5F;8K_1s-hrR;vgNmpZzoow`45GiAot9fw*Y6DkbV_f9W2x z=~MzV1`~zy8mW^I?@^yDS1z)vuJWH0TNOWR!E6Q$k;O}gMI>u4>x*exqCgbh+FY0V z4}u4Mg{XZ0VKf9|7hl455Vha_51vF)HuHXzL+wxzr*(BFQI@>(-we8Va_R zyCC93@n_`+lkeGxM5)r2#7H*QU>oom*3~xcqbl9jeON6hm9WW0)YJ)PuJ9CAM(s9a zREfCH7y*t}Ni!j|hwoS>p}!cMlch)=rVx=?I~VH`2D8|z13zAvhD!^$#(Ob~8PjvK z>IeBlZi4D#dt4lgG{X-uHL5i1?YAbMJiXbz|6Of)Tai)h$W2qU^z=4*JD%j9%qOmB zRAv%_olg-u^0MN6MosnsZV1O?=+b&7Rx@q;go`Yk|4nECk`=dZq*p~wluUl#juU^n8@8`R21nthO21p#?3TxSi3rIWDJkzUMy zca-psrA`J32m_k$=A5b5DOS=ygxMQE=$ofm@qo}Z5lmZeQttc#`^_?EorX|vI9Yid z_H%o%F;$1`X@Vb}N?_czG^&E@djso3ScW0+8!UzV!%hI$rXRunH)P;Cw$Ugx8Ucg~ zDa1uTeoVa3L)oj(u=D&~{e`IZo`L?3q;I!dVe1V3L+Zfd?i>UFzIFTh7O++MTi|bj z{~rSW7VKNFZ^6C=`_{0#<%It~C)DZR7K+m$8#za_)3gQNj4o_ucAI#L>?Ujfd0 M`uvXe} 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 J#G?k^L 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 WY1((g;Hm(Cv)+E+H=O?I=l=n<873S6 literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItem.stories.tsx/with-hover-menu-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItem.stories.tsx/with-hover-menu-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..24dc3f42d3f434974be46f7ef851823447d8a53e GIT binary patch literal 6804 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 KDZgC*{eJ*0D9^(H literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItem.stories.tsx/with-notification-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItem.stories.tsx/with-notification-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..0ee34aabfefd9fb8ec4e0e5e5a8b99e9241ef2c0 GIT binary patch literal 7271 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 z z_)v&A=-)wrilT~%bGoo|#>n6x7Au6^`Uzt7i~3@7-X#@_ZOTEdQ;S{q5Dh~m2j zo=s0H5%+ta8(^+E2y*tb-g#y8P_j=y2td&2uui>Vu6!u@OVgqu9@@g&GIA8&$-qeF zZY*4z!0%qPePQBlMUcPKUSzfV=BFVgyv=c@5EI@$IsiG ztt1JCIj-nxpdglKMoZUpih>;~f{$nPA}C9z^xakh)c1(DqGXMig-tn#qqK7GghiugBvo*8<; zjLImbL~gsfj-bw_6;J#0RNkyN^g{Mh`^lA>yOpI#QNrailA-+kQ}*P46&PuNxm(J* zfzEJsl;su=H3?=OQp>)rL&S)t5$9O>VROR|5vTEp_^m7Xo{niT^Ph&AG!~nu#L1un zK#C4xC5CD3D;}Z8d*2nWD(k0vAW*@f?enR^h;ROC%n_MNQFR0~Hl1^oK!Z5dYU-J5>I4LHWjo zNB>g|wkJSEWzY9tZ~Qu*|DzlIDvGcFng6RW|6hoL*ZwI{Mde%jf6fJb^`Zaa5W@dr zreEjyb&mgigypMxzS{Kv3!DCGDPJw+tEK#Zz6p||lm{&pihidwTjh)0|3oTPm20bq W`yDCBdgT?B>sOpE*I&B-$A19IJWo;p literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.stories.tsx/narrow-container-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.stories.tsx/narrow-container-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..d392b616c77c74d78296ba6a43e7278019ce70a0 GIT binary patch literal 6620 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 e$#BOO4FDA*R0q5z@_gzzpc3tdh0(>b>zwb literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.stories.tsx/no-filters-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.stories.tsx/no-filters-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..9f58a62407b42321e1382e699e8f73b601b1bbd6 GIT binary patch literal 3522 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 S>#Vq!vAA~D+AOA1)Y@k;F literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/default-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/default-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..fde6a23cfea31051a86071267fefd0ee26a0d7d8 GIT binary patch literal 42126 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 z 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 aYif!o)R6{A3EMI9$aS;~G>aj(g8vS*{5Ed@ literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..bcf28e7f88b0ae22d49c06246fdbb2bf84afde0a GIT binary patch literal 18683 zcmeIaX;f2L+a?^!QmUksvJg=bD76q}RGG)6cEL2Z4SDS=_kp5ShIufFp)? z-fi-PPku_harKpDhHQ0i?yrI#odbeVfK#;e|z8hn=MhCW(W!X9!bP> zboKetq)f5a=xtZ zh>Zz-@L|rgmrd?Q4HYk~BX(RzD`_X`C=1%Ovz)<=Ssen_!wFyFj@KilM&*R!Wx`k9 z?46lDGba3$RN*Q+C-W0?Bxr*5DT(h;G)j5hqkz8}T`W1KY0UB{Fri>U zmXQB5WciI$lzG8c(nLgb_h<)^UtjaV@A|ULP`O)uA(96@u+MpE5*!zq)#4>2eJ+Wr zYCyD43V0`=Yj5xmVJ3r||m``UctyUxL-q$aeAA{{C2>9%Mpv#B%@H)|ID0r-(n@ z_YjVDKD|BfbZ{VJ5ilMp@$E7fL69K*`O_f2$9f@$fja}C7JV*q$=F(49w7u68OoCd z<{%Ix-t_SDOAKRVYbKWMox!3aFD2u! z{_(!~+xrfVO#B_q-d@%WZ+nB)sfkIS(vSZ7_LyJtY2?PK^R1eQwFu2FMxKVv^^ANN z;={aPyS1p3=8k&@*l`$`^<$r{$&-9nbRY3iR~{MG1rzYGYlz>6RAe+tj%v*Aor1Wt zJ-!ER9yHvu)0-O(z-Yc2mopq42_C1P)!5Zs@}AAa* zd3!Y9o_GIGx!9f7(InBj82?;2n317Leu(61h3!*QQr?-3dpac-jey6h^WRnbb2h|5 zd)acoy?q+`TW%I1I=b*z7kiAwi9|kYA?|!07fe`tx8I0+J{8Gx*5=q|wuU1wj3bx- z>ZBwo$3I+}3iyK(LRuT-zNi=rG@@PVdQEN#+cIUX-!wa4nTeP$9?rvh=os4{ zcTA&r@4wU|zV-c7lT}O`!xXvQOV=Me(CEo-^=rcEXvqz7+R7oJG;5rZ-6=);XHQ9@ z@VnzGl&LxdzY2AXs9NHRU@uCh*)#?0b(P?#0xRnanWv3}OP%wU>DIZI!!`-_q~khi zws?M0&wI!b$Q~`&Io-WkTybd))q!2dve&rf_Y8N!k&^ijrc442BR7LX*&4ZZyzm zwE8K-UY@r%hE>6juWPrhRq*6JmwtUkad2(@pbUQ;qC631#Bfq?4E9~B5nob|j-D9X zuH)tUBYzvJFR6z0W(nA{BV<*%$o0f=Nw?#@SjXztrQaog-xbP#Y|NdP0S$c8^0|7@ zYa^RWjTtANPRrHLKNr#U)nTM!6Bqx+ut%&kM-k;a4%f%Q5F|s5W-(&YjNrEUT}VV->{|%8 zZuVX*E%nKq_Of&-(y_cPeuGUL``bcMLi~ujh1q!LpHC$UGbll_qs_5_X>eM8oAId5 zg0GV7eC{M;Qp%P7m2-uNsdU}KT%qmv_6^B(cL>&!LVUROVVsj>yklDZK~wRDK1G?I z&SYnKc-VvW#tGreA2&L^13ft@F`pDW*TOxNr(GQ$&xn5j0`quh5}f>sl)Q?%@Kv-6 z>Nrd?HWH1d#B3+>F7(FdX{!XVSM%CD{iJ&!_wAgr5S8a5M#mi1d$c=0NgbY-kr-F} ze0V59g~18Alk9!n-=AM+4p+@1uW^5%6n6Y%JH@JHj7owT<#v8ARjEA1TS3s>LXKqU zrWEK2dHGi{!u(UY&MzJ`my8^%f|QkHKP}Cboy=lhnrdo)$Byw3uTfa7fI4rj{wt>1&JPu4Tue4S_& zJUuEmp@8TygxxpTn4L=B)G5gK?tFu_#Rra=hd3LD@U7%=~CFAAD5(F)^lbLTRYikCx2qu|LSXMZdk|`bq${+PB@(Q!G?Ax$) zwd~%#@ds8MH9arZ^BSx*yRnr}C`35etyANq$#v=}>aw|6B1HRE@i}dN%xIdP_({Z4 zK0c!-c~@{Qu*y7gc^IwW*!p1fDaYzf)8;O3*X7sTTi$YQE&8C3exZLWp6T=^?N`9j z0q&)=83*q6sRY;Lw!hV}Y)pHn=JT}oN9(XaKv_{-tT)7AqeN1RE2BTI#tgPO3_Ec7 z{CK?%_Thc$;WVcLSMYVtZ8Pf&6Zf>4@B9-q6=_XteyJpxVTaKT)-=N2*Od=Xj-D31 z;V9zYLi`I2!5I7v^1%H!Cd6Q&v9JkB|6~B-`ymwy09ImHA1P zI#MdfX*P#5Q2}1_(+VegR{K3kNphKile^&3w%Q9e7vBiFGZ!<9et(_ z&08A~&drY>&!ULW6z!vC#4q1-A{GpJr1qLGdIwKMUz9j|HFGDE|7ZKb@ys;KrSu}) z`WfD(^Ql!v27nd{zjzlijWW{(YMsYhM4Vof$%$jTRqKQ>DE*fJD zT2I#2I0tO^>P+NlnthNFxD?^V zK(_C^wn`I54!&r6Fo4=kQPtZ#FgEsj<;|_SU>b@!D>osQX0Pa_uG}*Jyt>w2(2MNQ#HKvRQ#_wxlw{sl7+Y955ut$<3|L$(OYsc2S9tj)bZAt zxs7uv)jWJ7a0tJZ5szj<>?Z968~f$uM+wcasd+vb)?+TQ*#~#jmygg zWd;IEoCACCR9)6?!Cy5FI{AH#4Wx>+9;iz|I;V=1h|O-oj6WPtgm^2+dA}p{3mI17*&5 z;c|;joNB;Zu_qd%pAHx#4M+Y12c75HVXA@bUe8u?Q}7}d*~8EcN-D~nkD>|!`>TRI z7YzgQKKm_}W<~zQ!)vLOnR-@j1T*PnUnk$H9eKH;<_d>0PXqS;>8N~vb@1`s#3D|X z>qOT-lB69+#9t8IkIuirV1Np9a`WZ1v1E(0sP&o3bY;@f*b!zWBOA-oY1{vIEC4pc zJgvGM9nN1SK#1A~zmy{kQO0+U12<g=?sKVNn?ItBL-%7eMeS<+>=cVMpoXs z%BNo;>2f-{V7HSEcY!~1u#}zUVW8%@0jXStQQC=;i{}$V=Bk`!Z3`)8f%o~Li}l2N z@{mE2*cwm?lHMH(Klf^<7e%{ISRzEa=o(n94!U{>yD?J+bkbDR#Yr^RD``?ocxj!+ z`Ppd>x}HtrF1PI2j36&b5v(%Sx29v)@+x%miYPK$b~ z1WStf3%WkI<2pC}5*FzY)Q4+z{spP*o@CPwX13zaqJEmsIBaM#R?WC}ZXg3#jiVu2 z!IPsc3^C@QJ{Y|4HyH%NCfM5h1DS>yDV!EzP+Os&JCg`q17ME08-Jk1m5=T`OKfr< z28vBvnO-$NMa{A5dildB+~R@GEcP<=&;82X~s zjU@#XsZM3upeLUhwKicL+`TC2g^a@W$hBP)13~Ps0%*|xfCK$c z!J&T*{s*+||GWk1H~Ax@c@Yf8zyDxsF}_$^ih<@h@~9C-$9MJT>D_|^ejiZ6G#}*c z=Qb`5uwu$r2aRmj3_sp<+T^QDa~SAJm5CUkXu@x!1#TA@esV;O$ZT-kz;HYKx^_R; zeEVEXjDU9tv3U5o2fpFn!ucuCezE0}RNtAazvdRG8%GR(DM>KNDi;3L9A9(+qV>lu z3s2E?KMxeWB@O#^czcki4$dz}Tdcs;_qqHmcc(xXeGc6T3!eKcrA^A_6nv`AhcO>k z_)>rm8B&=~B6^PfC}V6VH=>3KnQza3o_ptRXxrvM295uvW@@fAh_!xx|MhFP%Ylo1 z#yV*#W)37Fm%Ys0K2Yu`c|M)bQ zWp&)=VY+JZ*YGn&OZ=4Lh|3h?hJed-8Ap@Qot+_N2`DY<`uCt~7ZuJ?O>1bGU_!Jo zrPCgL1^y-HnL^H_L+$VTnzDH*3e;V;?DIu)udb9dI<+QpDKS38hlN!Lvgh*i6ISz6 zEQ9eOZ8QAk@?(Au!#56=B=5TFcy3iV9ZkHcs6u{IS6tJggAB48AF*}xATZ{3N}eUF zyvsU8Cfb z>q#|_`>!mH$QkBcF3lb_cr&O#g9aGA2`8qJlcOGu+NS|)-I)WRgWqc&AY?eKj0I>Y z_o@=$t1(&0eci+ND#^iH{FhVum?}3i}xw&D`>s(s7)-_Li&S zGeK-}juwnlaK5wc7+umb&{YTRZ@s9(&IndHY4Gqq(ffkCl;nldz1}x_F37G9BRA>f zDxI7-5NPjF&o!p0@PkT~EMaB~OyxbWaLcX;`x;^Zv%Q&CpH`n)41o^6p1vc?3G!ra zbQ0ZFGL_>d_1LpInXB!!z8Y3CNlg#NP(cMjT!QZOF8JYdVYm?OrhnO(Q4<#QMq&N@ zW45?$XQ- zjp}RXB}7dTS2|;#TAg@ZnAi0K5Z2p%+BT6+Y}w7KJ-n)-j9cF~#BV0D{GbWS^l9UW zjj#l4CWZS|Tw#c?^%r_fM@N`gAFz`=)dXrf&MuwZh$~ zDj8t*vN1;^F`=d5;rLdURn4!hcbotC)b>nzM8(gV-}m-dhOKLQ66uc1Nx!hGnF{OG z)z_f_bNeKTQ7YF z`t|jbY1~?#9mV?0HttTZx)G=Hl%$8_Jx(mv12OB!aXjt;rId>+r7tp}IVlnwSs^q@ z%d#SmFqSm&{u?8pn&Ei#2NT7nkjrUK*m1R5{;M*LCQs-mZO)Y5#Wa*pmz?Iq!+nhi z>f4yCgQ=CG_POOm3!I0(o+fNddcMOWvUfN>r5ZPFg4)dG^=KH1$_E?U^9PS9Xd_YM zBO(*V2D9h{Eo5ZOva$&twOZbN;U|WCGAq~+a^K5L9CYmilqi^za&o(>GxuWDf&E1~ zztZ)t-U`^)&G@o($k)-zG9-6o%g!$eK;nfWXkqVbBzUz*7$=dnrd8Gb; zN(Ce$=z9Q~udr5|eNgVwkhl27F5i;vBD%eEDrCjhCMh&j$}X&VbzBLH@^`mT8@;bJ;|>M&G^zwoW*2 zANsal&~{Xj0&Qz+*xVx_G%Ho{z$C$eb=zybZdg)%&2DF5tzhJ(U7jUFM=3lo8oAq} zls@9SD7|2A9Vf)UtcjW$G~bBcCF~bg()KqBiXNfnSgW2+iE5VZJ7D^>pCKfg>reQl z0y9F;nV){9HkGC8M8R#Jxsx?fH@E)Mjy%)~Tls1-P^y@W-dehg0qgInbZk%TwY4Z^ zR8HTgy&mCBN_FInMhGO5jfC{orSj}-kD^@g{&wrQtOthkHC1{gyU!S}75w#-J}Ysb zR7lB1j;su1$6~GMii117aoKah$p*o<=*?5O7GM5HzB}iSc}&=nYBZs+^-7n#)>4Xu zZBMC(0c<~}G~DtYMeIB&NB1|*OFK0t4tc!$BkQRV z;hYlA$ICP2Iy3r(C52PNGJyz~}>(Fux=79u&s z0>^WH^p%d%gXDXXjSmJ3a`Lle3Qd|mEE8Yyi^Q1slKI8C^Cb*NORPJZT0n6hEaOfs zqD#oDJk}w*k>R6hSKu3De%2$(AR`czo4&jV=Gx2$B z0FX)4QO)6g98!r+q8c$PTA1#f&o#@v{>1Fe>A=@&FJDIGqeDZS9kd01nrR&2Zgjds zP%9PtHD{eisw#R#qnDr0=fKX(Z0dpiJb&Z`Ui9H3dKUVb|KIvuQb-`!|K ziM|sU&3Z-a8(+70bQ|{*x6}6dQdLy+>RLQ!UxwJoRKw6*8;VQ2$5YkUr7Bd={_b9% zn@jaIw6Lg=2&JNimc20|+4&yAvk%n)Zv3^HG&F#`HL~UuM&=H@9$Z^}mTZG=C|%wz zZfb3%zflkt2X|X0_eMe&Eo5$zg(V?hzlg{m`OimeP4t58XbI#?RyRN(^S2HAXWO>R z>_v;5Dy@zKs?9U|OtZ3SHBt9x`xCzPH=$m1PS&m)^ws2i<9xoK0=yhLgR?P+*ko}B zNM-4@&CuScuvxTKLQPgcgVt|Zd84XK)T4~X7Vv~c^m=?&>8d%3`^UMmf{-xx=_~b& z0NK0kW~>WwUN#qd;QlyFSG-?M!rc&l1h_STy%pY9@)Qromp?QW^(`_CkDMOoraHb+ zel&IU)kgN}D|f2ms|THZ@uy{RYf)jn?z?CBozKv@oG(44Vg*G)fkk_hU04`>39OOb zs%oYYrHxb?#ML+3%Rw(KZ8W z5WTDQ1y5|eVe7vTI34@VXLH#0O4fWzYTomaWboc2s&j8#Oe#t67$K6RWAc}J6LuSe zR`=MI%F~;p0ce*!5?;uDZ>eFh@|iuPB9BJ+<0Le4`*q76?glH+A`G3K;dQR(BB+gJ z>dKbLJ-?{{&3x0r<_-fQGtNu`qK+=C#9`((qg zh(LB<=j>8_uqralKHDQFKAPm|KKUVRabrlL3(UVit5o=oF&#bISy0av#JWY!j+3;j zP*p3Rk7QvmE;RS%86Sj#mwP|9MIrB~SK;(iM?fW6dlDw@aR3VeM6qeZ%DX#}F0Y*~g0 z;equ%*56?rSjg#$s;PA4)%41Tgl<-P*=azFkX=j6xv;g1FbD-6b!=<5a8!j-;Uc@U zJmN!+CT~m-;q0!F3R^e!e#VKGO<#lWKzjeEuTVVGgtWy*`qTBveQ&nHEiJw5!)yk? zFJ%q$0zBBK=szL0#!Fx$MXpi>nR>oo-keQF&n?|`#vei5!mecq>G?2zcT1(a0i9#A4nMGevzKJF zyAUj@D(_RuzWV;b;-G#oe1qu%6Q zNML(>c=F&p{-&dZd`^buYWz^Mk_jUnVjn;mY2`oU{+pDiL-!a;=SUe=IK{*Uo3IAE zd5AdAEa|}LomZr##qbqs1@rF#n#naOh5d98=*%yg{fLUpKM#Ta@$JVG(0_m`u3`4i zrRn6cdPz*oep_p6#3Fm7MYt0Wlg;a(5VJ7{Kr`&$5AA>O_rT3@MLnzIqBm0NE&G1~ z8RYrT6!o_l-Bx{iT$Dyd!0mCv3hCQjk|_^7&i?7EWSy zi)3rXljPwxbK^91V`02yY`CSClg69f$oX+8(srld%e2S%$%4blhVX5i6xn2dp)mWR z@=4>3HNNhW3Ab&vIsGD$_En-Id$_E6wUZ>=8*IM#DiyZ&)lTMUl1Fy!W*RQ9IeP(o zQ=^s2%agFTrQt5Go(QZAuvy?JT-nH;XJeeeyfFn)U7_Mg{;#isPHP3VtvthbP+FiX zJ>t2QL7iQFD1LwVZzRFC$S6>Mg%*Uv88{tnkt2q-aZEuw$356yDoXL5OGcF5}10WEO6(O26x;P)2^b%>$3 z^UcGptycqP^a-KhYTPQyE4Zoj?Z#phdwT5t3GIR~8$60?!Nqu>es|toE~~uMR{=2* z&_s>pVI?p!S1()@GfzMXU4!~Fx6 z5H57!W10lSDjO;g4HzV%;cr)^IC%wE$|AQNy}G08TJ#ySQRs05pY)U*pCyY)(BRKT zA@g(WX4|zomr?Rf@~rAVE9hvM9_bTix&|d=vXa zTgzTzB@%8{RZ$V#+1G2h)F`hXSDwvpiOEG8w!>6)i=wF?>bq0192jo=>sx(>zxQQu1 zm0u>zu}B$E{N~(`$l6k0(Jspvnvy16C!G7tLWRo(=mMqTS^)HT(hUzg<}yLMBDUqh zm_z-QfwGHm>z+BJZYmA|w-@zRc-8x=>LR+6J^Z4GtT*euiZtCI=Z!I6P|+t0K! zlhr%dU4|>(t*(b73}22|Vm;~}TADWN<03+vqt?5e%4A(lsFejiB5~T+RhKtTxQAgz za?}sP-qn3#ud}YMZoPLzP>!#S6A|#6M`_g@`vn5J?HqPlI zVVnJ_cq88S&>|^mEfu0krzEfI88?yNU1&aXGHNRzY(qeK#x+jrBzIdNj4ZMZg8qlV zX7&{QING!C`i4KsgYw@Z!G`Dd@?H?(j2P(W;;CRX=Zkbk;!C{`OT8KEI-)^sP_Y(JTYm!k^D|7SpGDUdzS=>6!gjf z0?@j%|H1G7SC!}gZ-ZyV#HL=4Hvl}$yDLKg`vb6Xg@KdjCK>=%hje4E7|@XBfSsM? z&H+GiypCMIb^xVn^24R^YA+k;)r}8;26Jh_+k7DDMMEG@-0-vugsnwz!e2oxrlzJE z6~b{XHci0FysCP6D4gm6C@ohe^ofsVSO6=TpqKYs-bK7uUiUzWJ6Z?#2+~w~A@}c% zA-wtbpvQj&DAXG>D<|*N?@tLyC*+3mG10=qx4a`x_A_PW3@><8L0n8R8=h+mF_^zPQbdoo>9zvJacfI}1ZZZiPsVwa=#w}&q|a&X(VtkiOUH)*f5 zyVC~RM|Nd<1Ongv{X-S=+53*Q4KQ-djeD4r;Gku-SiD!|gM7vdJA%vU*-&khJ*hVrc(!?A0 z_n*N$pzXn35`9>H8NA3%r`UhsJY`C&qUF* zl+mb2jF!Z&bxCw2Hvc|H-r$bp*sB{V?sN#e$W1<;>(99&L`G+^mSpt;HhcO#pfh z5LO5FssRRbtibyFa8oaP*ZUIq-#=kXOQ%~o)A(K7bt7rP5=KS14aQEA7dvABHCCSX zP!;y^`ml3?lkZ{kT=XYrAF~5Dr^46YIt*8@mBN`#ZFv!V{Q^q>q0oVY3zjwPr5cX-qWGEnBy)^{O zGwMm|>J54nZuI~(^TW^ulRC=H@AjX8iTCf`rPh*A#Gqyv(`$eGQZ%bwQM8>*8>b(d zGN*3!yLY+?KgJM^FGh4@qYIUtcZV!=J+v;vhriY; zKeRtM(5HCzplMY$Vx4<1ZJ6DM&cv_W>XWwQ>V9}eIirvaEw|n<$l9BR3 ztWktjdZgOwU}zs~LF^BPYH{JkQv@w8BZosXkJh>4}voIxK$T z{Vtv!jtn%oU3k|JcT4(+yg?)6I-@1RsLJgw^>llu1N7>&Q%h|)w+EXf=zH5{qWHAx z{vAA{MW@6*sIOsZ2@u!62;Do->_z%A`n&`1n&z>tDCHd?kEsxeflWJ4HL(YHO&_2v zQ*ch?lpujIhfMoP7T&$kedsq$ZPxounvQ?zniei6QP^!$ay4fk9 zJyzDJWLOzYWl^oQ$<{=6Yi2ixoB73RC*~5)*cm{^+oYuv&=EJU4@29B$j*WDAD;m& z3YFFimi&9d(T+lgqWrwirH}e8l5Mcn5scCNqzTTgxrAwt`cm!WKgV+w(GO{Ztcdom%gDOfgg8 zok7+s#ZYd{v+mn{r``5u;}iHbVPw@@;bx+^=nmrEQfDVlO}UYkSgpuA-ClW$ZWYMQ zYf4aXagYskC9b7q{mGl4&obKH+dXyBLUG~pEfY`QD< z0l)7HTD=^J+S&oT>TPCSnveEN7cFh*GuIlD-aXg@K%~3UL~J;T+C@ZuQP*DU;4x*L zKI}D=sS^Y39#SjBtUXD@sE zAe*cIeEfeG6%BaSd)m7L{Rwj>q#Lm#)Fu%*O-h7|r$8(Uwsu>?r}=*B#JSQ=+%$sg zBX!QCc6|~YSuj^|mS$jLQ2yT&64tWLZLi#LA@<>o#yl`qMK*jeMbXla%vFd>ieq9q zQ}fAS6D1{H4(y(YmP|%OPB1s#Y}K;nQ9rYy9vJVzDw(W9pd_O}pCjRUruU2_0Kn}* z!|$JJBO+E5JcRQbQxXQ@U$gYXMoBU`h%K7H|I-jj0qypMzCf6GX^f8mxV9v%z{Qh4 z2=;e9g!}Pll~9@nh*tQgK6}-5xNIv&TamUbCwD=#RXe$>N5_y9mZX~@7L&MO2%xa}6bs+c1br+T zx{%aXZAt0lvY5RQJI5gY!307fgw_U}x39y`zcV;t3$6m>b3?q97gfd5!A>wHG+dc^ zS%EDJnz0XX6_Zn0r|BMDE*)XsMAQ!l``_Q)#{eB1&1crT?SX>(K1O1>c;~>gXXZr) z0!je7qKtN$mG&XJw;4tW)C;*rj8QiSC{gp8hOM)0#$QrjvnKf}w2~4O%-sgM1xW6x z+1^1m(0j2*kD135_B1^2CX z8iYSUJJ{Ip4Zj2Jb@?;I2%bZ{Bi5PEpMRTF+>Lvy#nh$r^<#mq_+2{#_tdnsGz=yO zlVhZ8n2E*801_-k;CU-)6CbwLv+~)7i%Z@xo5glw?*n<~tCLx>r!jNb-W^w+(GeMr z{srKqY}F4u@gVrRpYfpW`&t0)ATQ3_*La;1uC#=0M9uS7GDKSx6jFlCHu#nq*67Ie z_x7qu2NR}h-v$7^JLg)Jo8@y>vsddv9ebCW_gkM4fb94Dy-Anj1Ar82Zhciuz}vmJ z{0nPj5RhKY$Md}FgNgf75#K_8S>VC#_^uJsiJjFqw{9TJq~(Wzlv<9V`zQ(6GONIg zZz(NKliXzoV0*tfP3T5*5HP{>y{YGb<^3>x@4M!A)hmMLeXr>7go)=zWOalj`&zJ& z-SjMB8U=U!LVph!XvImKoiv~_TciegB!4?&>4BYwVj zyJf6PFE;IU1gO8bt(@ERuEp2dKsi8yWDp0@*4C;^(^UZQ6+6ri%cJoR4`}uSa#aW{ zFhGmw;bAg^Awken+N>2A4GB1O1^!H1h>U=E)Kj5z)MA-+pkq2rlz%{c%|ON79TTV1 z)AGl1k0kC=Wl$HO#P`%fuGAIp83U?)>cYP$fY3{lkEK2j|(ou+);g7;gtVSA2lbaJ)GLYV>QMR4)ZU3x<5LsTw34di50XokIO7jn>8mKD!mq*d7$|tM)iFEFTt& zSRbBYiWbUOoUG9ob0#uN4f7kIV-+wD_vV0*n>8Qa|C)Bn;c~<$soMPFh3B7qrVbVQ z_F~*!Q&kBqztz))E^N+c!F!kaNV?6C>|Bsd`ia8k#dgJ<%8K#K9{f2&=b!yTqmv2Y zi4(au?%liK^`kgYr|a_r-qsp~@BKP4@Ou1&T+lbu(NOo;T2R1FG)Dv!+2lZxF`Fwz z^mZf!?`ewIzXKaaei_L)68~^?xIy?z^$eiHxi0AP30S?llEy}dakdLsT<$Ydo}#WQ zr1jnJn@?gKIzOcJY?(P`w%H##u@-D>TJ!h|mxWpahcl%luu*@k);(KkdF2%=1Jy9( z^vrp;m-FP|OHEwO4W6lEP55w=&%uFLzXNaqba4O3Kg*c($7iI8X|%D|IY!yF?};KJ zc5mfKs);acwF)gILh$JaWs(xmknN`uOg;sx(AJl7z+4pBW9qbSa3~x~WHW`1(Sv~; z50nMcK@TzLPM7RU);)E({I@K~B>b_nClnewGn2BA?R3~u{P+f1(Gohqg@OOhL`)J= z)w#%;$Ngh?U#ruuzXY4CP54XR#96h{OUE-?29JSJ0L2F=yHxVeUo*A0r}JCB)c}JV zI3j#5-m>Fn?8wB9EH0SlF;)f--z$%%=Q!62&zoHF+YpW2!F#s-B|3pkD#}CiKAN64 z|4Cyq%4)f$CRG?hH~*3ArpoKaD5dQ(`)41w9;t!4uXk6aCmV!$12DjYi7!8e2ifcb zdRZW+XX>_AGwh$D`SbQgxd!2sm1haL$p@n6z9?V~9zFO%SFR0o1p@15*KexWe_+05 zWP6k36c9hxi`Cc5&Xp{u+L}aBu%afm)L#l{#k1J_j>5oi-I@OuGyni6$!4*_qgLI3 zn$o!d=6}YSC+)?$qvc_4!~_lbjGU#*HkG4 zH1$Aij|Uw5H6RpAIXeTLs^yXJ2p|;4F74-Va@w?_kV{CQIdSO|~WvWU-eP z*wSQU5xp$KLdS5`Jcl3xJq*+179^X(u|hp;ciGbM>slEpg6$S2!-#(fa_kB z_Z|#a1y>FIEk$(R>C5U}Q3I0RzRo^q-OPC>-^m7GVSov3s~L1O`+;Uu-l`qiR&NI~ zSmHc{07aU5U3m4itJh{}$(z2kur}ryVeTqJ)(W1Q80#M%?(ct{HgXma?*nbb`b&pE znDo`rwy z`~Ol(-)|CARfZMu>Bm6VeiiGbIJl8@r^4;O2mSo@m!qvo!?gz$}z+dtH!nXWKq2W`1jut&Cq!O2>bQM zJ#3!Gw>M&ZqKxPsP6mL3qt19aLny%4e|#5m{OEuGUW|Y3*Z*?A{$Fno`PVA_YnA># z;5+{^oByq5vndh>fkYzft1uve1|9mAiyQ#`)M+pOqy6o5U<71gW^<$RAHT=^7lqHk8In$$0|0&jSeRb3i^^S{ij7NOc)Y}Q z%xQXE?|kWM(~XXypXz@qck2=xQ(Jy`gn#ghyI)w!=hE+XUxrUxD};xABb}bopB=Ux zA{?mSbHen&p@_Y|mprc86MoQNDed=DUbEtRMlM|GxqsbA^-=PVrzL3Vc`pAwGCdNL z3C1W2b{A%x1AKUtQMk*{C1AqWJLmC)!cy}0(Y{lFM|qU=zPfQA*!0n6d6){+3)%5-!| zbaz@~A+~ZdiarjlwY+&#u=ovYyn&M7<2`qt34UT=SkndrRRe0Rf_i1!z`$XovcWNf z&F`l}YwJefwRLH|$(^SFfc9S#_Qr4(1s8*O*M)4P2D-l9%-nRCJ4_e0A2hFY-fC@z z3bCuJl@J)MDHht9d0AAd!ka-!>m>dS=bp5FrHy#rBRMNa!!PF ztPCO>ig1&4rLI2#K7Y`tJPMxXj|!=S$hOhRTy^T{v7(KE6qBWjF{0MIMw3oQqIloPM~j zK2W3mo-KbcuWWnnZ-7T8gva}~KlV6Yh+iFNV1+(a1l`}3n>;_=orz_%$98X%jbm|< zlvv0V=a33hN~s{GrcBy z2^DTUn)xxoW?dwC{U*Pr^ZKx-8zcWIM7uqGGAkJX(7ghSzuJgK^Vb($@06JovtRej zW2bk&;=c3MTpOw0J}~FLSF!NAPfV&r0I1qH2YJ zzu^u)krfh;)YyiS($4D%={`Wc){Bj!k2YtDHqP4Fp4z~Ty9u9zlx)iy1uvI|F@4kW zt`!h9#WDDV?CCz--;Gxd3Q^9tN00I^xzJ>+Ao=KhjuV-&X54 zDwum16v&UUCV<;sRu+i)JRRmFnvRjy$yu3pxmvZn$^(%&i^d>lJ+IC0_b9UQ9Oul* z#+FO|RauvU=KtuCamWo{`BbM4kK&sTOxYPRvO@~{aB|L<*!h$oA}cc`H+OY0gYai` z4pD#FW_XtRS)CY~54{j?2U|EK4L5lB%?Ht`+I7(1nNKziaprEOSnusima@lJnP^~} z?&8zIhgId_pwO`eX;^y=MensuC9F8eO=&@>s`F5W!B6PpR-7^hbWQzM`DuA^cI; z$3RW6fA9n3SZ!(9;w;ok>|9$X!dM$-I?!+65X9b98j+fJt|@eJ!45gc0sw+j##FCW z4<})V3h;>P?qK}_dNe}#a(4|~sNvx@uKTbb-H5nlHWl0XrpC_Q-hNKjr%0SuP&@Js zBGX1FD!AypHA*sld#k;v<)nuL?}ZM_AmB2wsYcm|B`+Np7kjPeQTyI~pd6qsF@B-r zY!7#@r@2SEUbv}EP)=&EJ?D^fLBZyA=5(2N@V(6sFP1C1EE5l(?Z2sL z=aP%A3;FlK7#y5PKFL0I>}|I{A$3| zvmm!Jic8`Vy7z_Gr#9M7Qc!KImB-PFK^Eg74}bjO%E+;6QGvMzDDv>e=4+Ei^O!E- z7uq@9@D)sC-o`RYPjf3EFpyy;N{s%-;Gergx=+X}ymay~%qWG`T*`92{4k1=zvVC;(qhGOcZIIq5K>!oOAd9``xHbjOlBebY2L&#PvsG~xAZ!bAzKv#X$s z6z)3vgxi2BrC-@Dps5^O&JqYeiuJ6R$6r|}wzshmcJL9#v(JoS?Py#>Dhjs7XHh`( zNm*EX)Xe8u@143sRcp&_+k)r{DRIor`)I9Y;(Tctvoq02$c)rcmUC!?Sb%KC(Z`NO zl8jQVJNM3^s;UD$hiZyqrc--K{BZ*;-?|U1(YX)s)hdX{zY%7uAo1SOpES&71K$hDa0B+oH`e-pD1R6=;ad|wy|I?#_!(@kIKQ)=ShC#gtw6DKi~GjYcsv71 zz~^gAk1o??>KL@K8n3WtO9V6x#+v36x!ZK--A&aUk;ca?wysS@8i-Is#3@5z@3-Dt z3L|}{hodFfhG8*L(b3VCLRKNNl`7hx0wN6(RdkYbfYgBT6Ok1L>wHuU5d2+#=nC!9Ut^ql;`Bq<&19#8yenP$vc3OjP*pb|emHm8B=7cN0#egMk?HIQXCOr#VPSF z6-{7bS@PA`2-e&{PGg&ka3z)@D54f3c1cIz_G5*XnS;YJCyk7FGxFSNzaXokJHyDu zXQvTM(^bFa4T}QBQw{DvQzXSqDW7=qO~lRxXI1wA9)a#BAN?ole)PXX@xK=TO>OdD z^+1BX!*2V#z_yl+Zr&VhYHsnvWvKPVUp#|Z$c-iFT=z^Rkhp{A&#fA5w(s*1?>>NWlDuK~Hf{!f)u39G!`7&ZTR*ziw~{P^+=AK5PdG=JXJxg+2V}xy58t}K zGiG}rb{lC&oDhs=giBI@kev)5izW`(y{zz2EKg_qbpVc$Uzw$|_9~mOjk5~yovbN6 zNdWh1d2tr6OFVWmjIcY3d(k=K<*GAREbn_E>e3OIg^PTJYUcQrF7wGKmV(qxVQr&| zhyKi<_!iho)?sLR13}T=>8^_>i;BlbsM;r2-WQhsFmUwnw5f&e^pnr)XglCAf!0=b zF~d7v5?!NpSj!Ukrq~Yj_QzLme5)%Vpw&ck4Z7#V+?c#0V`YN#)Qg*bG@iPM=MHu& z6L1zFLa|Dsgri$yFr#dSz)5FWozOc2vqKo03^uK2_@G-mU{h+~w?_}R1b6X)rMhLH zdkn#X?IIeK4DK8e&l6~Hcbui+FhMS?(RA!N?{C-T+qTL)q1Nmiux;dSDgut}J6%j5=@f zo~;8twV$ZSn@h`xh@D-^nwYKf&16u)xtJ_0jixD_7K72VGzR98bW_ z?g4T*L<(=4RD#ovxb`(kXFek!NbyfYAqb?-YSn9g?`o29L-cc z+ZswpK)PMtbv=#3ghbtoF^l;uu1UuQ;83gmk`@a8TOkc8Q-c zypf$$>=r>=@wOnAiq(_`h1&;m1BkWNp#y}{^|9oW8!Mvf#*IZw>=+&$LFu&nuJ|}Q z(iH^GPYZ+EpuhWZY*;pphQZx`6l6boBU6clT6FqI+INFQ)KX=e*f|c|EUa-HAWv|X zBiEnEUoHuy%8pN747yq(ng3lucV^LIZ(4GJb!(!HTvdB`$1XWT>88qLC=G2=&`VR# zYN@=m2cWyHFa|kwL7%@8&^K=F!FzaW#h96`ni!GhqN9%7+9j0Ex8(#i$2{WTFH5#)0L11hnsMvc2vQR1T*mkM^KJ_E%a!5 zr>A@fakE!EKt=N0bogDC%{cGD=1`Qag~?8{q}CWgsX**HX5Ycd9XAUo& z!rZzKSV|nca8n&KSCf4<@|%{9r%w4rxnuT5@!|FhB(>9K5osb#50qe)1ba(oyio9{ z-Tgok^XfX}hn%v~CInfS)g-)T+F1e>8FnwL$GfT|nC)oOf=jQ|+Y%C@VqsnTlSfJ( zN-lX9)bO9b?qD9B-nmt>{UmX_YrgtXnlzIXj$ww&ueGWaZXXYH2)la!>0ZlOMWD7b zc5~2AQ?bH@MKbRzeq-Y>{3H2fv%RXLJw8p{t)ckR`RdZYzsz(SHLjhn8y5sFqZL=> zDerE?uk5_p>DyyHJv1TkKM;9HjepninGT_m@^R^^$5@HK6}<&-km;^nxn3)I*#E(e zdb2#q^>#79@yf#jL=D-#+U7z7H!0!uU`=U5O~WPGo!(>@DOa*V)u&jhQ)L+})B6-L zf5d8e&>BLCymsBNUA1xa-lUwgw~{=KReA0iS|@z_e!)qFyuBljGapR0sMLE6BOhXI zuc#vR$^g&Hxh!SgT7d)!T| zx3hw|l7nX_pyf{YdP>@DgDr=84(uOo6z%MT(RZ(9inX>j{T2mX;4am(OwPJRZ^^MK zXs_9TioA{5`>owZ+u7vexKI73k7-=6!-@Ph#%~^b`4+YNlMqH2TRZN=b{2vku`>sy zH-W=9OFu{#T5io=haK=5Cg7pR)T?414)#CQLI#JmL7ash52!0#Mp>l`d#NAkK%hq# zJeUwKyjjrI&B&^`yRcFoJqlKbq)H>?40f*)}!zeA=l85kGT06@`qF7$Ywq%X&a zpGL&L@SNzu$r^|Va3;h4_GHCxH>+KDf6R)QHW&v} zjXeJOKuRYGp5*7`-drBzRmphvW!V)I5PvM& z$Vf`j-s}_XFHKO5JR4ix8Rk=#mE232PEJ02tDdoAVH{8HlwwTkZg)n zRE+PAeVVGsoiiyJbPb+cU)rE`=fw`cZ9b$Vo#}Xxx4j^B{hA75Mk_^k*8>uXZY}2@fv9RIGn|KgX4*RxVtoQ!2GQ#?L^aT zww~6;kw&%MPL_oBV9sShL5QPK##seuNc8ic9{_%+{&`!&A(RcrMvbk#AA=$jvI z)jnMP?s7$pWlUdXGS(Pw9XPhTi#OrEh;B0WFVBx0XnJMBNq@U-facB86sv=H6XK2( z!%B0D9=$qky73Grwl_BOZ7w>Xp|xyrCLq$iCQ^8BF37_LIVlwFGH&LlZLxqV*lziy zH;PU&Kt5+#C+%E0d%1HC<;l7CkzmLS4n&4H=IdN>Xk6{qx#c@-R;$?P51@2sOqB2rpV~#{3 znFbIa^&kk=)3Ei7DEuV4&z~@+Y@U%30Rfi25Bo9ndeAE1_437_xJy&rw(XH$bCczn z7=MSJXUShiQLc>-txT@pt8Xb+mQ}oSsq$iZ9^Uxf2!j&l6?5^qhrHv9?AG;R(xo>n z<7iQHKn8SGvDCXj`&H_F@uwS3TtxKsy@|)rzz0(bLr-CxV;_+>x@MQDkqyPIP3VxF z$($0LGrA?BJKuH{sFbVY-Mcof8=u^!C7uL2F)J)N%EzpAw^n>?LWNVC`#hqAwq6aD zo{jCk_yjK4eh{;S__rkhs2dvoZ+=}%MynKvX&wtay#LF(v zPB+mvBp6L4=Z$QpoE$OL+On!Q6yKIQd)V9CVnb#=GPV^XHY!(4-`YF5yc=9}x}ZU# z=8BuBCT5-?ShsrFd-ar25Bedy2hSgmGh4Q?`I@qvwRE-_9rML~l;Ju3?!N%xgc%tm1^fOU8|^sNpf4A{_9FA(Eo1HQS++FQ<5FGm$Jwr&?!%L; z+~~b#h_hCWQFi(d!~CZ6t=_UJ;uj=VO3T& z@@Q`SOzE#X2LbC!QbQ7r1d=F#@*32W59va>x{dSp0v>53Ju+_cNsr&YHzIP0e){PO z0Pxcean}p2;BqKlGG>qro^0HL(%NRp*%hIT0hp!6y01XK3z-D&a!ACQ+$V9b4b2O84g6TTMS8)=^Rl0}lLqXM0gz267v5hF{Rqb&@l zP?m=0bFfZe!1GXjq@8i7(>-QBt+)Z@dWA6~LmBMD4*5<@T;jTx!+B2coW(Ogpvym{!^elyxS$r>OzC_3;Y82ZFJT5*fb# zMxui3MvzLLvvH-#TmJwzt;nvgIh4Vr_|1te(7B)r*DI_{5G3t#9Te-Vy*c@K*fymi zf9e-4AF8!}BgBKdy#-AmOsL*HQ6#hJw{x>LLRT#%7>3WdcRLmNs2*cZ(i2ss;BBTBsE1fL=7Uq1aIK zhz15Cz3h)mCtL+b$T>xfkdk}{Jjlm1TH8QDqgz`JZQ;Bk;pLC6eH>?2byPXENq~%a zu(DHfK0rS$`$`#@e8AG75?;0Y%`QT?34iC5P2us{vh2h8IE@=gp|-3|RrMu;-Oi6y zG$hx_DYuR~kx|M$_of$gwl~g5fmq?GY8(#5+u9KAg_qA#(MrMyi2KpCsC#Uw=U?XH zogCJE@s$SSbl6NoM(XUQrRu&gKg-6|5efuYUAaYX2#ksEO#UV^E~9Uk3m@n z^&?&NGd!=zZT9!zeTg(ApkP{i>2+0xAgoxZf?>{3_C$Q(A3 zvN47OHh_nFxRk~1tDCrXUMqk02;lR*rHUQpj(#s|{jCZ6I!}cd`bOSm*A?wR+$yoq z)|bnSS8yd@@SHNSyr{W!JHe1Taan&=XQh=-LLAE%H5UId7f=G(vIRK|lv#rVyb6kx z(r+Tq2%DzG^=y2hK@MX>o5L;7a(UJpEhWd)Zuk^W$5o&MpyY-`1wEtI1s}7lJcKI! zwjvtCUv!mqm!OvDgsnwC90xN!-sZl_e-mG};#gOZYWI~qkX5Dl5JDsv?*=V@0UtXo z>pIcCPruiw7;-Pj*Vn8&s*7XlI;Mj4>LXo;=J%vFK*+C#H)R`rSQV=MR>0MXI@fKU z{PVGp=vIBB67J71O~qIa`dUd){5d@Ih7DOB%ihg5AQ{g>jX~U0`pGHyk_>J=1&|MB~4}P3vrVIYI(TIrsuAqh1`JKA|ZQJ|OUd zAL+fYBwVZHP-z6n0|Vx6kRGyjobEbGV1BulC7YFmUkL>~!o=bC#Apx|Vi!w1Vhhi? z!=I~#i;P~{>-4O=SN?uhAeEAcHo;HcI2L<}~tdNv| zn&`r64&JnUO;i1i@x7xE7@dO$PvvimWzq>+DrH^8M)6m@M>Y}c= z8b`HM1yVevM?kRcl{;2B)6ZJMXuCE7--wkdovqIDbc1AWIBpt_JKRjnDkCgzFXuFl zLt?pgK_X$AnW^CE4Mle&BS^(Xb&PoBv#ap zaPgx}QA&oPZXlVNq5f4le^PU9-fK~vLzL9q$Z53GVWOAq_^HS8i6qi!Tn&w+$X1{3c!fvw;Lnf8aej5QMcoU6a4U^z&@;3 z^EFw?k^rx(H23&A+e-CyixIpG%4)TyD8b2-Nxr_wNP6jrlI00rqa9beuP=p_?>D%5 zdlGAcx5XgVNb%qEx$86e(Ho6POb_h#v@G^{B>i31g4b6uK-w-5r`&9gW;gHRs~6@t3K@UOw)rnZNp=0! zPtFU%Z;Q_SvDE)jntu%Ol{>}=;6*+SJE=#@S}R*R@P^d3I7pVkjvX&*z+*o7nSXvY zfKqRvfi2~X11?zQc*OX*o<|1`^poN2-%oK)UoiqgNUPLr<5;A-ezX^NQ3Ne<2`1q`n~!c;%Gi%% z*}OKkIe)6rgT2%7KUH*wT>;jxe$lctnNJ*UpvG%+o5f^!h-&%F0cVfQ2Yojf!2;rw z%N6htM&h+MBQGZ0bcFQsYOn4ch*L1wKEV1YjP`aK4dTtz3yE_*om#ktD}%|%$e_%Y z>*)bB*Mk1T-5E%j3wD=TqMBJfG4bxf^OnOr`ZpWJIJJnFeVq0pB3OuJ1-X8KmLn>b1N`op9TMd?`D zrVgnbmRr%Bcx?NxhBpI=;LpRwT3f)F>nc>k=BNb*C8|Y%>2WG zx(E#acbGyxkbdAqbx{9zwWYVsr{xUe@1xXGWKeM*?wMP@#ly#Tp$Oay(bUw+Vf>>( z13`Tq63#E1_Ud8=I5%#+wOCL&>eJpnPxq{j4~P@yXQkvrZtbiN^GJPU{ruY#4qJUH zcS_v#7ryP1K?D5G*_GKk7SP>yY~(eZxj-?h_8I6`DM;mwXoBdko|$>*ehTb_X~^Um zEq8Q0ppnE`>e0=kKU$<+(=c~?cZ15t$NBN*(DS{-$dP8PVeO^OzNC3fgSW5GKu&k0 zItgv!!Kf8ZQ+GJETY`qKP$6+-q=r}U9OIgw%^&K0p%Rwrz$^9{`Utm{fWPq`B~j9# z6Rv@sO$f5GmmKuqnhBS7isUzOS)n%Ar6T7@wHqIWN8ZQyxF=~=W8RzBNApK>T<_S> zWp+_wB9bpL5nBDB$IhD#2dTNd?7f9yI|Pg`rWVYl6exv5w5bqSyk$28nNb(mN!Sgc zq=L<|L|}`XOg}_+Jpi1!LUfM_v(f?gbvG(cM>T(_j%T`qe0=NC4!I)! z%m8n~HqhoRey5%x7+v=|eC8^-n?3x2jP%&<19NkA{KhT~XdR%tu`MLNAq-*}Vu;=; zmb4N9-i0^jLT*^&Y?a;Pf1la{RhGv6D9b;s^1@hMWe;FC*&vK!+GJi{q?OzVI-vuJ zXA=ji*0gd*tKg^xFfy*V-Gt(0V5X;#qOY_z$dvqh4?_op=*OJlbe1IVMNr#QS>J$n>A61{yU%wb%eomHj~jz=lq z3FLYf`60!?tK8%W2ATQgWFS(|)w6n7JnOk588~x#gTqN|9Cbu_wt6oF$aX#%JZ{i( zg)}m;EgpYjKj8D1m6yoJgS3CA=O0&!^1;VgbVA!~RyEbJ9{$!eQ>qsf?KQlLRI@4x zXW6#L#>IQ|y_BkOoQxgM&P~hHgaS7Xdv~?YLvpx7{i|DDX>xYW^B??DBI~_nGLa%H z^%c2(>2W|NHFE78IsC4(Fm3p8Z~qGEL*VeFMA~sY1PPvnS*ud&qv-B9QO`eI&MiKk znS>MlI?tg#Y8lH%tvxU8Cd!2MO@p?$~1p z=QS6tL6-ZL*Ehr>^_1nb9tb>8XEj@^bnAiyumhLX|K7&E#(v2HvZcdh!^{}sTSAI~ zDDIAi^UTc591L66%7~3ZWdq;|#-y;`w#8XQ3CH*SD<>r2(n*4m6xUfy8ZD#QXJNHF zI|*S>M?Hz`|2N10Id$ghEW3y`2(R?YOsL)qGGGm%z@olf!lT7UK8r|QX0p_2z@x9( ze|7o)dD!^BYD53OPfGu_`TtMGGyVhKrfUKIv4|y({CP*N`_r>8KScDIrZK^IRVCtYs;A0?&sK$!V-f%TiNQ z6vs>r50D2`K%jEWJd#I56!L^33LZf~VAHqux7Ysme*b;fUVE+i_kNzWp69;q>-znE z*YCa_U3Ie2+Ies%007Xky>ih70NC*s0NDQN+wJO=PkS?`0f2u1Y%l)mmRPhnfr=g8 zD4v&>?3=jOof+|+?nABOw#(qJ7>BYsakgh+0HoOsk8LomC%WI0?teD=I{#4Q%s)(j znQGTGwR}H%+^zDTO+WjYPgz|oIvF|g;|oi}n-z|CDj%&qJ5d?rQ^{&HXIF+Ne_s4* zVbg80Ox)S(Z~TsmW`Mh)94THT%D<^n@sP&W;;gS8yvp~#o76LJAM8|82mlln-z>jN zi%Rj^eqmq`+J%;bi@yWB_0q{xZvp0bZU^Apy?fsP?g6%G0RY>!A5?FT6Ld~R5TR8* z+)x#puApGcY)a6|G*}7BG^x3(d+@ML<{=fOWJ}Sh;@3w_Uw*vlxvIA=*&px=HgU3D zIl!zY(7f*!w}R%hbmAw!4%(krZm05(y~!fYVKD%}iQm(9$SN6UV-#CD%1M3YO8b6! zPE7X9?dSzL#47SL9nEJnrEE!E@}>Pp zM65^o{-(r4ew*@@h?QgLoRa*PjXDCWUjgow8twbUrbUZNIEvZ1fW%U_zPTb`%Y4xw zSCqlwY^j%Fay@wlVQF0X$oa9QTvw9;;VR5XC(Ba87GH3kI3DHS&u5q(LoP)yBqet% zmVL#0%KifYc<}1&@4xSD-pW_qWtPxRcrxf$RpS%D7;%wEsgU$V%cr49JU&NX^{^}I zu(eE6U&pk3&8F)iT0l`PcU2RP0sy-pFr82Kd8T2>iHt{kL~(X2tYcrxoYAtJFak!k zB+dlFw+PabvulenC5OPg3;MFFtM)7X8G|Jib*{kH%U z0ASPI^1`{2vq`H2W!3Bi*WB~W__9U`d-QtGkgpHOe2>=r?Pb!Try%`AwmahotRs5! zJ;=<_-NBEz)=!m-p>?9nF+&hH>Q9jLYa_5~izZp#6%GKTOcza_jE6wJq_nhzLO%U= zYr~wCTzWRgX{U{ZLo|>`p+3(8D9f3L)7Cc)0;nWYt2XL=o2?|F^(>_jOdQ4wP1bC~-P+dJr%`Q%9 z=U}dRP8Ws}jRLwtC?Gg%(Cb)UP_U_2^XIOt_P!5bo%lmSlcQyozl3?315wMoG_ccS zr&&r&TGk*l+}}S=_M?JiD5Ri#V(?JhixZ*|8MzAt;gy|MD&A*^;Lmgb_qbPJKc*_> zqPUIy+@JoDQT{!56>Da)b)O{gzU-^e$hV%@x=jvsIbP{nU`xaqVrX)QkTMS%w{^N7 zFVFU8P>pUa| z_k&sMnX8PsqP5-kr_->RZK%Du1#TYGnHTnqESYNABe4C%i0*f{CN!EX_U%uJB0?9F*Pc$j*K{d2 zwpFL^slF5271Tc9*~cW$p17v?*^Kts(&zLx^Z@ElU|d8*aDt0DsdY|gsUrZ7QjG8& z;(~&f)+w0%M{SM#vV;d>`tUqFDZ?VX3U0aisSRnhidcKmdQ<$CBa|ibi5ik~~(0>xkYS$_(wac>uf66Dk-kR*kCSU6Py}!I) zkpA5;->;YitIo;FHD_g&Q;n*D?Rn!3dz^Hu6<6eC59i{-jg|^U$47WOJAY4SmMp#3 zG&|;Jt1hu;Q4j8=2uX7}z@)Zs<7j21In6k8bK3|qrEaK=)f`?N=1;xRuAEji&E%0x za$OhpDOd6keaWBmg1}Z-)S#YW0E#H;O*nZ}UD;hpbJU6ZC9Vam02++F$b^gvJwu)+ zN4I&(2t>spP3UF&!(Q8Y8XEebGyh&X%!o^17**t8z)c2Nnsh*&XAjk(s-bICl=IBnGwLDt2~H7WU~@*K;}I@{yiiiZUAA z?J-vYE;%b3a;4^W$18@FJOsWZi`$_1?251&>&!8HsI7|hVqqTMGyCQ1buTAt)9M-@ z>&li}UK7KEya;}mTX0v0qmOs~Y0jwx&PEuH>uqG56>2Nhhd0v89RhA>HW(Js zKv6T#gY2u=2jN9Pfp(s`!L5eF+~mdm{)_18m~}PA-2=}t=;BXL)&wb>h4YgvRRa$S zk&F`-Z6nI>Dd{SLGkQt6gz(!aHl(0I;G=O$X(w`ooh%s03-JLt=^q$h8P!HU)RKQP zI1J5O#_;+2vvfsqU%_CdnrUEjyUM!x>&xAyl=Xa>VJr7ot{sX89OTBk%i8uA7yi0p zyxwI@_%Zbxz>h9{XCc!gK6&)%={$6*-DFCq0e!=!gwBg#X3}oY^ovv{lAw=^>ToBm zx3+R%JJshJDqYU_LWz%o8tWpJ65TqLZk%Fp|5MuoEw&7XmaQ>Uj>U9-L|ckuSF0me zXF9*I`2VEQ><03$%K`~Pf2A%!e3~26X9;_!ic$RaNHwY3gxef#H=}N?0vTAT7g9t) zTip#G7v28ytrrcb_aCOw{cb5yQnQFdw3Z|XUK$nxy(+Csw%~K^@AMI^@_bZM8m`ey`IML6wI$nQ6H>w=8lu zAaY-cs>^_$h)SEIDI}^hQ-^2uDQAk+!e#v{?8;XE)0Fkkdo^}-w0J&OV;r{ub=uCDJi0}(l2E0nkfFu-poK$tv-7*-7}I{rA-Ig zJ5Cy;FTaxX1p!X~(t>iKFN{c;3@T!dsjb-Hp4}MIW~j4aQlBwd3TvoXe-BY!W)wpea1*ez`O4?&{R)q!~S5Qzv$+JYPfQZMX1V z)AYxb{;vz{jlM+jP2{=!#|OXMj^jBxrvqwTzn;4#vqX%(S$jZdI%@y` zh@Vex1N`&cztWapody8D`t}Y0@a;c7sm1d@|NQ&K|6O|je-oY}O!C!rBEH1>D25t} zLUQ>A)ZXTi=Xt&{&ai-0<-CZM5@!*^?^!BtKT_2wGH3P7YKrxYsmJxgSX3%W(90a= z65~E^#^}#y>gxmU1*B^`oy%vM;^}a=x-iLyhZLE;NIupCPdbu+kRCWZ{@rXRA=S+) zE+tzJYnO|!y(NeUYqqfVx}ru=t+jXG%^$(FL`w_Fi;qSkFooo`C2jcFaIPEYO|X9) z`Ls;F`Qht9sJS)ps83RRj{$qPp3arUan7YZz*|QNVY-W>w~#zPY_Bhgw$a=6KCRCW zc|H9~464Bjp1H=H=86j|rsEcMo{jcafseNOy}h+}Az{u7^y% zSaHRBtzCtcp7y0G3J2IBl>}%flosm}-1C=#BWXY&_5*3di)zWCE8>x4m5)0|ydbdi3%EiQ5O8x2Q z){>vvX4F`-{_D>9BL^%|?W>I7)rSY+-mS^qS%I^}W|NxZBE}LmNcfV_Xke~s!6gRA zA7#dNOc_Yxie?)#Ie8oQ*P$c1g{`+I>T#W=JUsLIq3{>z3vFd--<9~#liJ3k(k`&! zzO8izC9hm&%TMyQN|zklarSw`?)$)c{*+K3e(YY?d{Ow;>Xa=V+>kQS=n!&|yzsO+ zB~CGv-dt3l_zK5oRe=_L?o8B&x1%2Jbnmu6yvPyCKBQxW;U>YAeRw+q8y}y$-&ort zY+TtZM}Q>!(Fs~a)WkwZbb+OgeF8|op$N!9u&CDau(e6-%oeVjS@XI(ulFFE9W{}e`q&{;x0Q|?%4)a{4}W2KN-c&; z3Zn*7D?YT@hke*BJQFIeV&{)k$m>aB2Kq2Frtz8E6Fm=iNQqG^OsHG9Db>CQyOm{?DS!FiPWv99FhAOn18+AW!xbGX&3+Xjv33zUm zWDAhq@rtAP(KljQ6poXgSs~Rp$c}F42ZR0PHmoME2(rGHpd|T<&kNdNa_+87=d(Cib zpC8Qf`ejDLh!HENLugeXYobf#84}Gtzjm&oYN3UE_R2l(_+Z&{#I9K2kjo6 z{`q|l%>_p9g`oJc?fesZ-+m5MdMekCe^W?q{8=)DE8Z!cuDl>eyLlykR_>Cr{vCEaomNsPzeQgnW-vUCO7GVe>8XBY%5B<4om<)m46 z*AGYAv10MGVh2c*Lr>~fEJ;Cnc*Ju4qbb)V6Ql*g^6J4<)AdGTf*$&zy&fn?IFvr^ z=p#g!WYUAc;f~Vg-c`)eTZqOD%XkPo-!0b=_Z_pA zyGdGUS77!{Bqk&*{l?mHxDJ6GJsJIB%tTNT6#ca31w9d&;^NmUtppvkET0re%pIHy z5tLH6vUrKlfN$}H>`+E%;03Svv6q6Q*JpMQaqDpg3Hw}aPYvdo7CrCN=Oj*RX)Wbg zaN;6@7FayCg}LMh`>#>G&#&!Zzp4&)wlwdy^kcuFL`^i_Ruu}M=(co?7#0~Ls2U`I zeaX?BdUE)2iibaXuT(USC z|LTRbEo~5bW@8s%+4S2xeXl%&VlMN?9$X+jP+M;o zzv#X{{tr*_UkRJfpUhkjosGo@*OFp0EDCvro&?4Ma#8 zEmO-Q2w39%<|ezh!P-2;*45N@b%_GU#;FPZMfk9LMsqVyuAx1_r6Sq}Ebq&ms(k7i zsFI-rn#Jn3l(+$}qrN zp_%e*Jt2Z#5lapccx&EA&gchNg+zXqcH(ur7p&sI2eeUIr)h&9|@501&Q zr|R;M`~2Olu2>-3duAzCPFe^FQRE+|=qc=OV09ZZyc>C?;$(H8yiTM|*Sg@`T<)-? z=TfF#&N4?TGA+1u@i9KM#+KI8SPZnWn*W!3^@x8!ZYZ~@1-@~M)Kl~%p|+qH9@Y{P z#5)B>pr}*ZEYAAoQ{#oIW|M$uz@RL2WPha3Z;RX}Pw7V$C^3 zsHvOjcq9zvIM8@E>Ou@HHp|C@Ffj~_=le+_{6Mv>FzyF%d{c*EraHHOt%z)G$9ZWN zgcy(1wuauWBPZVepbLRO(v*d+)iynzv=sj1^jCxkzv-;N(5ZB-FoJ&mLhbyWLiC?? zCLpB$@BkHJ?;OF#z>}6EV?2DExid;$8>Ca}nosb$mT!VFZ)%<<>ym&0_QS0kTAIe) z!A};#<$!;^xw^gdM zFpR7ka>w4Gh1l^bN>IP~;-3|grtsz@3>-mT%5ZGX-S?zQG2YcH2+lNBo5+c<3!p#k zuX!J=G+Ecmi~5O)YKpmsj`#65@;mtt;`Dw_oWOl=<^GCOtXI>0?#+`TJQ$>LTa~EPi#3 zA2H<8@E;|9iAP=Yp1Yfku0ZF+@$s?ox}oEB4g7Z${nwaQvXvU@mNOqL9-4Q#oN4l8 zyJV5e8cmKAWg;<*!1L`o&prIjgTh@FLl967G_s7Nz%-9@vCir3@7^s0(kQo7JA8y8 z$8#f;caSzIJwuNQW-back%iv7g%j~=B3{KmsVM}S=fpx1(e}!%1s1RYp=aTwf(_qNko;0<$%3y(&)S=#Apeu6*1L zxChiBgTBvWnsVyl-a*Pbna+NMnS;;ipu~`yGo77kIGuPRs4$Q-eN|GO>t|ndk4*S_ z6zh34B=Xn4Z_kwH_XV)r>cRz8kNEGio+K;^tG#@z{EkXKrb{!L=M0C3i}cS<2p-pv zgS$Ta-RD=aTsZ>_+e=HWUjywfOiE0Ah}FiBTdvbg`qg4CHBhO=^;vUoo-TK_Hz#t#lrn3g5cmT4!n(7yA(lzP3KJ%OaGI!em$qX zz(|N+|G~J5XW|vP@z^qBWP&QigY)}7zOt*9-4%~8aHO*sYVugNZUpr{M$*J-Sgv~X zUrZr)jPSn0jT5vQ8wQ&dpF#CQYN@bmcm)Khv(G^_w7EE&GZ!(QP@^KI-Jeqo^`x$e z=kx&{y}Iazr4BsMM!48JeiO6?D zI{MwSPlwQV2L>o`{DCi=IlbXy-LJm5`^V3JYUH{DcMuKvV03)XJnGU*9cjulu75V!_y=<6`q(S)%T{zz=3s>N!7#ri#Nhz}BBL~1D zJTfl;2n)5~hgxPEM-)J#L%Y8_yaxa{KYlUMjvOzA_~a0^B)X#`Q8#?}s9~6ZSxdEM z6BAVZZo{=&)x1pO%!P6E1^cxGY|$G6@kF3_m6#FyijtvGC>muJV!&|Ug`w20Y7e1m zE_Vk(M%n53G<=c&al_G`K8SOj<}C>eKrJ>2Rnp0}i6zoh%{Xptgir`B4d)FV@RP<~ zvdVdEto!;*{CW4;k29b7RO6$)uMcpB;$H)yu=5?M;`e_X09IW->Q{Lk8<3@&Z)=EK z|Glazsw~69`2bNcxnqe$;$!a@w}0;n^Weh(_l|8-0B3VOK#L^uPkiV|f2+V=hra5m zSV;d4Q@xt@>IM7sM!jQYlO61-N>G=4(vp*S%W;|y>CiL&!Cp6c!BJJGz=$?o29<^} zl|1)FU)b_qV$CW=6mJf zLJE0juWnk;%mMek(7Chxt^QPN45;QyXTmYrUW+Y8=E+2n-w>E9@}*! z4lR82Fd?D5q8Afa)z3}-tKS`1J*eTWlQ0Ly_Bs@J`5kmuT7sZ?9!{9TiHY&n%H#Qj z6X+d)&weL0?p`%2?rJti1+iK&aKGj#MU~G!(4n)}u309%f34mp@cMvAjU41je95+t(PdXP2wj%LEM(nBxXJNlKsv>s#S zf-hXst}*3Nc3Z$%6aEgX5F0DaTn}e`pT$wCVp8!Gnut^^6hGB2I-F}{5G(vR9pQ&) zF|P}pdR#S0Qao*(u^H-1T&sDUib{u=Xt2rl^5>UXmA6;rBE+H{vyi?Gu#Eq9{?{ND zOCa?1%n^>MK4uce>0#(n*#mGwa+0v6KHWHJvK*9G z+f1699Xy*>$k*C4ksxRx!8?vIA~5ns$fsVicUKVDjk6D`R_l9)lF**HhgeN_UAwgd z68@@pz+p5#yancPPN5GM7gKM<2h~ZNa(%!%cOYO6-Mir-%5MN~DKUK|=%rGfIi|q{ z_MkJgn4#}%{V7ExQKC+rxP)Z9WYjETH*&#v>P(q!G|SM;*uFgbr^Ww%UD9!Aej8VI zT^^w7Rb8?EB;2Ap9}0sEHBJljrbZ1K!jx0*z;>Dlua%N+Yq*idm&pme6JaAT`+!aj zp8&RaxI2EmC}5+#*gkA{x-}1*Z9fb z;raVwL|s|`eJ1Jer1(22{tZd-mh{~&cO8px^)1qGU!A_-hQoRg{MCKrR|W6iU44b! zb}zy49B}3}4DgwHBJF&cX5{vP?oKB*np{}3wsKE?r1NhL2761SM literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-low-priority-filter-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-low-priority-filter-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..2efcd5267ed8cd07ec4ea992b25453ea05b04f86 GIT binary patch literal 12208 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 literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-mentions-filter-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-mentions-filter-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..a72a03d73b6fe69a21d39a05ed5f5e274fcade0e GIT binary patch literal 11930 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 literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-people-filter-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-people-filter-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..88597f19e1bfb1c2036029de38bd7d2e76c483a5 GIT binary patch literal 14889 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 literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-rooms-filter-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-rooms-filter-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..de8bc49c558e03073bdd5db8bf5fab944af4494c GIT binary patch literal 12754 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 literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-unread-filter-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-unread-filter-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..844f532cf1080721ccbbf7c27c92a76d209bee28 GIT binary patch literal 12899 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 literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-without-create-permission-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/empty-without-create-permission-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..fdfb2a4d125300255c16e54261e0df0fc89bef95 GIT binary patch literal 14855 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 literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/large-list-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/large-list-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..fde6a23cfea31051a86071267fefd0ee26a0d7d8 GIT binary patch literal 42126 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 z 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 aYif!o)R6{A3EMI9$aS;~G>aj(g8vS*{5Ed@ literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/loading-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListView/RoomListView.stories.tsx/loading-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..bace5dba528426aed09a92ca7d8bdc5167147474 GIT binary patch literal 14916 zcmeHuXIN8PyX`_0M4E^S8p>8s+=75oLJx|9Y!ED)-bAG+NN=Gex@n3cA__tI1u&jDPw{fWsll0|b0Padv_)b#FNeH^_AqzoY26n4L z(8c{kaN+0mxgq`CT!IiJQh9`ElIfK<#P5`%g{>{FW1`CV;|y=3ign3e(Ix!!c?J9n z$<&RrRd$`K#wiItUYmQVKa1}0=I7hm&lXEK27eray>uzNUF6!W5Z5P@k?sM9X@_Ea z=hw8btXYwm96_>Nt5x~6cQ)t9UL|^Oqc>}IL%*t@DCX}EvU1#h|25mbs6|CcdY?Dc_wvw4((!`dTt3pML8>E8S`XjYA|`vmg9 z;+pni!%Xr}kLpASZRyQskGXb@jp?t>ht3vCii*)<0`O74Fo!-ljL-Db9&_W$6FLt^ z9KF?A&2=~1-bCv@A}#F%7<^RKbJhll zV0Bw{6(d2famz}iA~$+uypK5Ygpd7D(qVOr#aQiWUoKN7qb6`l)hj=hM*J6lw86+B z`1#;b|3w3Wwj8;?_T~6Ecocj+%gWp45cKRx>8`Cx?S*|#(mw2-7ZJZ*dBKaAo>Q_9 zWcI^{aJThnPkU%uHRHu$l?{Vp#kC8^sF|5_(XmcG3vJ&P8QWFEL9Kg)b>s)vpJn)* z5@;)Fq)hFGZm!1*P1hEe)GovfylSnR{f6T&vh=0j5B3a-)J073cSU@ewDNwn0VjvH zhSxSLBZ)uH z†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 T7!9aP2r|B8cCq-v&A(xf-3A#@T#DAJ{O zLa!2P=t&@uaD(S})>-GSbI-c#-hcL9E3;?zJG0+;%kw^y@Hd*OG&h)UT)1$7=GDs= zIu|Zn`gP&L#ldSADZdQfi&?&K;nsy$FP`c7rftmzndyOYj?N@ymV0mAd`O!`oBx6N z?e$tNBd8vEL-38mnt6L^zJW<*{p5HtLZYE&V=CVg<&dbTRt^2hS&0T1WgEZdBHvKI zB6feJ^TY2Cy-}jwgbxN0YrWH!$&DGY;i00dY0{#^PN%uX)dyqqc96Hb2lsxlb}hQY zX&mp(OKMVts#VP!JymsfaaCXR#h<4pb?%JssG%F|1f+nsCnlC#dgEi5r^a$U9axbLm&Ee*;bii z3kF!TJ*&IH9zb#3BKeeSS}^3rlpqOJ?PjWtPQ$^=XZnRFa148kpMXgR~ux-%r@FQ-vuK_x3f@s^foX z`T9PVF5ScLA4*o+=Qq-xNx5U3u|>M#-Bh9P4#KCZY83p-49JkB;>T`TeXfd0%L1li zgt|@1FNQ0m3Z5&L*3i<5lpa306yItW)~70$E__H;FCBi}!xwWeRkoVCPMY-d(YiS( zFTjGV)v0MxthDXQ@qT8|+8|&)kI=X|L)y?C2PLZVR^Ge%$r=S^n}qT};HCw?~HT#M<>|fPitSe!bngkF~stJMeKu zzxy=$)1<4ceM&RS$e8M)I|OVL4AOhHt`|Mlr_rsMrgs&M`Wyy#_7Jx|lZ<~oCRVQ` zyRte*^85QP56P^AAy2AZ#t%BzL-)L60p zRhp(W`>-`Z>&lWXvPU<-(#XDfXjy+uT+mQZB2&s(f0n1#Wdxd%}g=rXCU?t^jt@+NTOtiI)K`Udltb$ui4<13N zt+KIZT#ckd2cVzw+N_F4k5ef@=jv|_<-Bv5JAkZ%6vYjrj(l&&HvdLp&c{J)V|YKJ z2ez9E0NSG@{8UThkMeBpWthO`HU;-g-xQ_59c-jL+l#omRpU)J=t$ir$B1e@bXw)q zn(O_h<5`wmOCdf-4by6Kx3XiTQV={yKET1?puuStW`4`QI+f97Q^TYF%4EIlL zw7u$CABj-`xcvDMA}?g~QV*R021M>mH`$eST9~D{Fc{`}wer29m#lk$-C`PsXO-_g zuaSMw-HdRdcVB#NijVqS`mKT3$t^ctfL4-^>kt|!x+@uzt$aYyS#E)7x;TIkkJDrh za`npmLj5j{dLfGDfwap3j&)yPcK02L#i?r=-teaJbAsYtZvMOTB@RX^P=|f@M_^w5 zgd10N7jLL&`FSK;k29S#CX2w5Vuu~4ZRHeg;2vaCy4msr$+ew*xgSAdQib2{Pb4Ia zO{r)y!_c8OlSR(P>@(|PA6sewB>h}>KZOfRZleNx{5E(63Q{&%l0U*egRFs_LEuYn zm(L(O`FQBbQGCjGwmWy|e<>*P&F+V#I45Ka{!ZZFUKZB}GO=IBb7Geb!mF_Vh*cD0yG zamtf{=uhiArCo??;N7ew3jl*lVXuXpAJNms*90(ZcgnK-4hzi)k~X)#6>`iKaP(ze zSM^kmXCfm>VmZgqYY#Y{Icn@wGbz;Y>veP=W@)S4#QKFwCn$vMBWms_;(_;^eE*PlI_lrXM|zvq+T?>E7O;;fLA>#-Hd# z_rW$>;$1UE*KrBQ&vPYJxSK?T9SKLTP~YbROplFpkk}e+>ehmW5mC48O;O2|EoQLS zzEfPBQYCln%jgVw$szk5V>!n<@KS)hsHLyGX>J8e=xJVOM^n(rZq+kBM(3G4#2R>x z#cm>SNY?p@n}yV7aBm>6Xm-%8W$Tp(4RQRN9|S_25Ow=y*P0VmQ zAQM~qc1rJZ=#1wHF|n9Az#~UGMHp@EI|MCI3LGnW7R+j`2KZ42N9NRnjecQ6jBD3D zmni=ecnIK6Tb~h%OCA?p*lT=HsRq=wvstq=X*`A=u6vxU3eLC=T|-UY5%!*&0`+8~ zOH!(Y&t^O(s7HkSl$XA9jAl5VUc(AL9I);0;vZC@+Ei{`=IQJC5$6byRsx%9!k-4?-e$L=R2 z;Tztfo)hF(+8B`5_^XzX%qZ@#lP@kIplU5Sy*y|3 zM4%B=RTo^h)3xZKy+=RqC*D20z}i1BU;Ey4%}EXY-r09&@|qR$a8QU~cy7dGJcTd9 z4l^}Z$@hxI$?Od|B=Hv8UG(F1GsFwP9h*Z&d@ojd8lmp%7+EP4Ir;N;e)rGaH>AXb z5lhpeiq+I!c04aIW|CW`H|yTi*NmYKQjT%YTCGG;l?B+^aDWV6J_*}kkxMQpDDALK zlC}-lt}Nh49Gew=L0UhSvM#4i0Qa}ojO4s{)3<0PxVG6*5&*2jZg`tme#gV zl8tvm2h)Al97Yq37uD>Xoq;lNSGs}&A+U1CRUnI84Ks8Ym}Dkj;Tl!{ZO>e71y6Tp zhm=KzR262sX#kj4tTo--zHBd>3ust8#Wr$?J=X z)805=0DUN5(?^XIUwiRBK~@gKxv`#`U(bkFd83pVU3@v(-Wn^|E%CC)tRy$^g$Tnl zt(0tCZE7)4GjP+R25H<6F8|Nk$K_cx;o>In6+>}P9lB_CIkRJHMjEc6X_m%TTktzA z(jlTF3BQFFYP3w-8;<=q!vq^1C#VUbd<^$!v&&xCNt0;ecj&l-xPL>(e$SWuSsh14 z@+z}BaJ?=C*mEenjA@koljKL6FzKdg_PC>oU{InERJ1ehv>MpIHV4f0b-m-@-@r=KoV_hm|&g#jk3OnV(j6%j9TF&X+i1{QK31^ zjrsS^yAgtUIz{oqQ&*|e?fM^2PE8z@&T(TVtB=c6oYbATGI##y%Ny=!njz0lO}H)R?Sv)JjwQ zqqv7~h*GYD0cc|wI=-%yi<9Q*L5!t`G=Hzc2fL&wRIn5Iu-uQhepkwOnl(W->Jy|B z>;7?6s?`>9J~oy3!z;Y)a!p(hbJu92@fkuE{hqOadR)k?zK?Bx@Hc{sn??N?bM;KZ ztt!(1B2l>70{~iAhYq@bYvQwtul` z*w4^nR@SY*3(k}qoz!q6QvR&lEi0$8cxv-|GX~`~rCTtQe9a1xGzEV6iN+*g4uANd zXPs!h!&WfaAZCM5C`D;QB~=g=t9M7`JscqLu3Ici*j(+DG?T>2Y z{s%p;QV}AC^g}yH2l}fKr=+CD3|(=9710EWd#wFtHX1stqnx*r#6SL7;dtsO&BGdC zAt>gJ${wgb=EwoJ-P3f;r-=Y$rpW?_zlPXWePI?xmd(ve{j}ULwWbmxT#wb-f@X?3 z&1LyyuvTNS-(m&ul2123A z)Vl*4e2%c$F-`Tz9g{$eYgEPzpNxTe zDm3{>h13>pv8-Y{@I%VqZ??Nj2v05krR`(KdKukx)`NJ&ei;K^jm5+*YVf@V8)@nf z!J_Gi)z!M(NXK%!>M7vPd*>0iKhW%?(?#G7TFNG;_YFK6>K@YKG~c#bO%^?Y^u|W4 zJ$983=~UA%&WD|nnE9I;JDvH=#34G{O zQ(k$c2FhK1JX58(lkpX9`0~_r_dHbtqHV7VzvXV2wd}rKoKhXOB2O=oei=*eYUcd#d*KOd8PUtG2&S8%HBd zl6UGU_Q{L}ZTQfc_3dLRf3ul)l-wsxI$(TN#5x+i_oL?7rB%U(ltsIh3no{poe#4y z3UitTx_;M}5V?mHA{SZhcbyK0vJA*q6lXny)(mZVd0n=#c?QB%%#=LQsPvLaxSq{Z zv>8NaM#Ry{DM7BG1$R5>z*`iELg2zk`?ABEOkFycsz3DF5cRDePp0UMx8qu&* zI!lbsA4Jv$Gc=E=m(`ScuF=hARMkta)>$I;tL}5W^wP8pcP-e-{l_%1lZ);Q3GuVD zT)W*+kiDIpZ|$2?h8Q-XJ=|X7e$7=HNMC}_&igT*y@0PiM#plu9#MvKl|x)K8A9sr z#`-DpEC((jyt5YU)tWGUI154asYZ;kUmeL$F~rAt$Fq+zZAc$3j5f8cPv5ux#qoN- zAyYz|qQTFb`GyguKlVLrc4CjypHF#Sl%?nbu$E=ev1TJ<5EaYDXAlZ%cl$Z4(*T_R znAoWB+NZ9H+^1-pr;%Xl8|LlW--bKD?YV5j>I=J5ML45{7yc6q7<8)2BriUF7RRMA z1*dhl1JlvPeu=^IL5mM!X12P0bRCS9Dd}Ici>alb80)#y|ISpcoAabz>11hmKP~u* zK+4I?N)c|`0uB+J(ES z=8Xc~(MChUbC`mDu0y^7?M_{0myzE=Jck_CJwCngri)#?OWy+Ag`y{06IF_ZpbrMR zKA-!Tn+BIPJn>t`D=VW$*aF8AHjR`QDqgoJ>n!>0u*si_cp^^NlSs;xv(U-+QXk>c zz|HwcyY=cVkI>qY@`S}QWYP)dGS*P2oX0+mjIGUQ)UfC4btG_XTDTUb^Xs_<#mUKp zRO=H_#dY~Vw?2Ol#eMUa=X&JpYgAmEFsJYBn^dqp7x27I-(kqCY$k>=iDxH!UppJ` zj78Fie@TTpjO4!-%T&Nh5ke8WxV>-EE20$V1M(10$R87n)amwtQU{0(Uz5PA^dw(>5n-nkI8EQ)SKI)k88rvs{r%1pW zag(wMFFd+6NV$Hve37zoE_{9Z|BSF)7Nb1y@5c{U|Etqyt|bGVe z5XzJwV&tbmuWv?f4D-JHUA4jwf$W+lC)zGHkVgmHWrEO{cplSAwWK`FsJ+{iv3?0W zlgT2aM%uc}mV3_o^jFylnL!TUBR!|R#m)exWTBQOT#&+q+nZHajnrDj7MFoES)q|( z5eIUyF)0c}?R?Bj zdGMdn`^(EwQ$3~k#VeEKfhfpQgx}C{WI>-vy;YJi{TwChDx-^(xOOg>yr-_z+%#Dk zA+4LZGdY))!Sb6&Ucn!?qzD#+4%^;ytxT5O;-zN?Uu|a^5E=@-nQ}CK5Wd*GTP5Ij zd^F@{7m;xuWsg@nF8y5blgQQ{7q%(sauc=R%6#5y3@qrK*7pk$NBmr&icNVU*V5>R z`H9fcSR7~)798I%3l|!gGvJ}faX}I(d(qs~RMtvqBMobL1K~nif3p)JssO;hV`(Up z-8AKYPXA79bgYy!VEZy*#ukZe(>*R6<>A-gZM@QVez- za5xOjeaPRDR-&CseM5kub!Gf+^DBA_Rt9q)AEM#$$Y!Ry>D+#`%?Yp<|M(n}t8JhC zka661wa8z=t_k_r;dn+!rLkBSYF=7`rbe~Dhey7FDFpu68inN7$D_GjO9FoD6!HtI zv9iYbLuMDA_pE=ZbGaE>w={mVCf-t7+mh08O$q#RdLOMGF3g0atsDU2`ntM3;{$QAltD++)w`qgI$o=SuV5i)X~uav2FCVnJbWnPvAT|aCb zPEzhGDDEgVCOY&J7`NIWy`CZ*LO$e}zOz8D-6}%ob&7~A1E8nV>HTL2Z(|o1Sgi@v z)0;~bw?@zG(Oz>HZmn7}Lw>eF4X)HZX$!UQMTtmk72oN28LZ+K6&@@8e#Hg=y?Gnc zt2Nv^%dHvh=FH8EcAC;clgb(27vv4h`l=(7(o<8J9=eY>OMAZk9YGh5*WdPW0sCMU z0WO*ahN698v2jCu-p@?agS`|Iia|~|r=}5k{#C!31%>Z6 zU{i1KX?<}znCj5CD?MsZ3ZX=7JedP8V}y<1XM8r3iO)6E_dnk+;+xwBJgUyVWYol)N78TC;E>kOW<|ERn5s)AGnZ7qMAR z;naSGf+Clet4x%xW7IBT%( zu!8Qg3{+U@sTRtBtSo*lr$(d@W(}>!`9XGPu!htf7J@DHta*!zGAAZIz;o%OgYw}n z5Neq|8bqwtjaEHeeiQR69a|F(CGG5!cf%J!1W^OT*(y|iRf~`zg4Y7{=_IN{{_LGZ zW4T?-#@<5xjFVgBJE5ATGlca2SlZr?b5=5e>s&5M0h$p3j}!*WkX5HB4iuwpz7XHR z1LB_`uzT#P5MRP+`9k(Ulj6oHsH)-gfXKRYM!OBhlcf~3%7R!Y4hX%6bbzzie{P#M z9j_0HW(NKAIv8!d)af1MdsH04T~YwKudHBdnrdjVMAkjE7d*$;CBH|05G-=_O}MS` zc590}vv%OHy_R#@Tk4W3o=vRC=Xh=ae{Hcm0lc^NX_OV-;e#9L^86?}Q8a>Q?p!a0 z?J(dhZ=5dAva0xO=FMo`j3idRJv(EafL`|UBIT;`c0;bO<#P9ENo%6c_!UzkuTbn| z^dH_VC_l}3(=^khhJO7MU@LE&zTatdyu90}03M}PSR(*9c5LCmLC4+)l4FDB%{>?I zC#n?-T=XeNr8q#4ZXhXUIe*vuuR{gfDYD#-nxslIs3-WU$N9GJQhw^hJiu6EFsV5W z>E3N>d{B65B4@WA7GNIz)JcEaqWTH~`jsJcRyjn8!#Zx8)Ed{bDmaG}7v}d2up*yr zleRzOM(37VlyFtzGWRbAO+Ff&=KiI&LoO)$ZSd?I@KEvG<7~O$Mrx-{Q%v^6OJB+i z+@&K#v?OQtpT@$d)Y4j5T~dLtTG`oOUlf0CkqsPdRy1d$R;y!8$)xt9SUb9?wG?-) zB*C+TP@S1Vh8j4ZXl9-7vG&UoH^k}8m-=yQcx)Qx$@33s^xi~5yGt+6v!bkOYQ z?7on4eMVbnElVTk9q*yLwCvrDzH3WEYnlR=E^j+8`ycxGn05d&gR1ujthw>Tk;sY$ zpL?t{1UIm2$3f$}xG_H>#M(U=mVr`qZOsPEOa|FgpUq`2{>Qv-56(t6_oWnTW+L6c zPWp+tweHr_(2UC+H7zHZdl-UcyclY$YQ`lk<=p>d8U_wJkq@=5Di)j)7iL)@eur~U zt#eJ~+1N-%Mxzu1VurQghj{)$>b|CwaAu?gSD!-36Q#?H$H=7=`MZGw_W(XyMj}3{ z6S~%@lN&z9)WFzc881grB)G3vyAtdgfjs;1Su-QavMArHv+fXd!k4C5P^|oBwRY}0 z;nWJT+B+Qk9yqi zX3xzczLEogHl5iIN*U~eKxu!mKw zb9yst1vQ;Z+D&?6!@8oaYGpv&fT{AOw{VNOzIn$uKJX3tgZ0`BK#1j}q-)cTrq|}kW zJ-t_UDFCPziQ5) z6qY92RQ9Mq|6bY?E*{=res9Nea3{QVoNA)u42q z>^rnj4*OptxtDG*45$l14wJ)`V5xIj^ByI-ZT%V{R!Us7gnRigprPH>^M4p?9yS_Qt39V`CKHT8tcg zo!SXq5fr^!Bwgpi5+-^<^^WLj?@VII1N1SVuXdUP5v8?IT=rOWMXH>ZjRjYGWxU3k=zZ+^ut?(yx zkA5oOWh3}p$9wtCt)5})M%(>ZQJ|e+fv4OL8X@B)V?45BIXU&OZ9qmnnuQkuhx*i@ zxAvXC(haoDy$1E()c%=u8{;jg$r(n~w6g~?t8;0ecyGMjy}QBe276-68eOlV`fLg{ zBAJ{JKs-@B<*duv{r!TIsG@A{U%1unc8%wx(UG*@)bQ@Ea6UE zW`zG!GuEEwdwi89v}_VN>BawITkmz>0T=vZ{VC!0l8}gEz$!|xm5PaZRujA}PMWD) zJu8#&Vj+6%hr~ruDUKBV`4!Xa#&9;!Ki7ITlN&H>*%EX171u{RHkQ2;b|`oph^yGP z4)be?R|WZfnyDexqk@NosGA& zlCB1U5+@6k(9OXoK3)$5N)@}RM;1F8^(>ybyx4;PqbsNPFKvo=M!epDj90IXI{l)L zy{gChSQz_z!vAS?%*Mgdpw!Mj2R9!-?LeKCxvKVD#&f7{VZ$X;sYnfd+QrBHvPRop zH_8mKpnKB!yf%b{FnH;-@kam0v|NJ>gn2uoQxi{N(V4m5M`LB$1d~<9(aQXVa*=p} zk`Uuf2>{aTmhmdOdH$PEwYqV;wuw(~lSTG`@yNoD^YY}S-{uz6?n#8sSGn`Pe=6vj zdQRnh(86lU!S||paUAT6!P}^ytblazZPhV4he&dwu}_y9C{i$(3WS}s8ki#3m7x*X zA$cio;0S@8U|=hl@?()@ZIzXYm2uKUKUdJG;$MC7jlIWBe3{`&EHkS^U&c}~jk-1- z{;CSZ7d;HL0~~4vXW#})YRgOsI)&Q|_5#O_WW~lIo+jcI`R{{*qf?-hjZufm;$Le& zTm>Xxrap(sR4AwcSVTaL(f)9FQ}6j~o#8w5y-kH=IJ~=w6A^3?QxZKXk%x`jwR}?Bee!o1JYv3Fk?9 zt3u7YnL#fYM;oBz9P9q;&O(&^;12ftKR{(Tv=X&uPreTH(9i4hllj$CP}AtA{^mq9EExb8Kj^Y6Du=R}kL4H`k;9YjUOU^1UD8tPA_6N5lxNJCHlsS8`USCo+jNehPEvh{ng0RZag-5Y64e3(`zkpyxejxYjJ#pIXIJ^`< zK`ez3{h>;Vj}eGQbr!-d4Ros&~%sj-a9LDTtSxTDt86<78YSj#TllhE4x|moCO-!e;z)+oOg<%n?K-0H4~W8>1X% zR0-6uwb~*KM}?E3a@?1zljf)1j#GtPX6aq+nNCbnaOvJsw|Qb>{e|UZ>Zr|KyUFGA z(D%4hr{T6J=KINUDH+z4M+7hk^ZDps*ttxCo$c4WD7i9J%Tny>*2wl=dfcjhCaM7+ z>lPPwQ+#6spba{01xg1UtM>0gW|^G^wFqFLM-$XOHh(6fZ(ofrU&+@@wu=Y=wi3F_ zt0$iLc(~e-nDYYzn(h~Eit4C3iZJG{PWQ6pH>I;GShpIk167!B90-8;^tVl<9N^L( zbD?D-!*gGHGsp&xF{BxI%AZLmG>qA8LV>$o&KW`sjm;c!wrFf_`#{MUE|j(@Kiv@! ztoO>EHLk)Rob9KcKHvRL!}(GE?4UvKgmT3?Hd6mbku0o*A; zr1E2CT#i#F+S~!`r@2@0bm+)B zBy&S7=2M@Rq45?opbx3+4<@mq+Vze%9sL_DjCJOJGe^T+tw=$ulG>1|E2NtgP3QBx zJ;*thkR&Llc#$+f(AAXrjNNS%$&13xK19vVi|hw8lF3cqo)JPi$Q6FAlVb$~QZAZd z?vd=?e}LJ@0|w_ZS?Vy@6Y&1w?2sIsJ zdHaD3ru>O@U&hY?#06gDwKY`gi8;PvBTagm&guC{v`Yb6Z0E;}X2Lge==`76dh#*3 zpda6r4WW?{|6LGbkjJn+6pHt2EVFH$(U+F6l3Y!UkXTgVOzT4p4+Hs=?sJcSe!(Iz zJ-os7GHKhSMgiek96CNBui2#N0&Il2${ZGpW4_k9H=HYaX=3CD+Fd;IG`JtESaNpw zb3TqvVQ@8LLeWu8M12Kh<0J|bV3>W#A!~7}-*Q2y$a73JHc)uHKCoXUPV0zL?<(1g zCA{)=o?$*?f7M|=lFv`xJLELAkMiI+`Jxr3;)6}DfAgY~DZaupRcrKj(eh!@0B-a4 zeUe_?*>A>^E5a8j*7gH*b#GCXPgOC&3mMiVdQYzYZD=i9#x}9jhd%Vif`N|SyE!B` zccWVxMP&Eb$lGhKE>lz%4!2C6KR5b$J`c#M+2!i6u+AWbK|`~uYiHwhRAJ|E&4N!q zF0WpdlwHt-u7T`r*M!lhN_ddWC{4cv`az?P`Wwjb*7$6DkD9A0UK+Jw$1IelTGm=| z-kGN5hB$zqx>>@>#bAuQ!g-$yh$}t?`t_%8&z=#y>A#!!`@>ETjMArOM-|Mj3_e?4e)<1yebf*$2O zX7E=oj`a@;1tHs|i$iq0lIL*j%&St7^NsdFmj1snC;!7WE*e~4tFm*VROqiQU>sYL z8UDHO;gQOJa4Qk7pGrC`4V7FMk4?o=$dVK%^Ym)p`5gX}efi(qtJ$V>{cA0KET06} zj`wK&J+hVyztshfa#0ej54mZQW(V{+>2`7BlEY)*uRYQyfEd~>S%`Jfx!4d;0>_}k zi4QmdWHmOY)I~?NX`o$(g z6apt$++rdm9-{~?F51h~^p30GSuQ-WF)JqP2uX|KAXn_RJ^+vxN?Yz&T05NEi z=RgZmM0F2ym&~Fd+(ZEgH;0gFEvzKJZhk4qRqikt#WI0KX2yh*L9|{*p06RK%eOV` z%?P(_0@0mJR)a?Ti8?^nY%USu?!qzVWtEsO;$joptvJ2|#CwyLIQpEx&s>jfU#8g- zh-Q&~hAJ5zzq20OSS#u6KRr8*o&+7Fixa;pfC3urgF4n~-ED$Ql*+ZOg-y3GrxycW z209n2Y|cB4PAI7SRr?T>u2jd_W}NwcYnMTo^-%$}h^p4sgvc-4RP?|{37(#Nie)+P z;oqkiP%33G%lt8fn7tSce&j8f;x1BBg*WKf70VW92b|$Q`i@gAV>ge81E$Iy##`B- zD=@UZ&|K!=QRciA=`5agDq)qN(Dbs*`QQIoAHJ;l$vSe?jI)P6zxVWl7RELvV8rlY5-`CQ&%7`xp z4mcWy)?%;zol@{7&jXCE{@JO2L+#bDZnea1vVobRzxDe!C8yh4y97Q8psAuJEGCFSE;qnxPuS_bh zlvc;{$5CeRE4>BuwCUK!Bw-d?C2ZNzTZT5ADiJhrl%kSpo4_H(qcg%L6>Uc+ey8z( z-vQVv2ifjBd5MQSXUUMm-D?f?AQ1`cxTDSFl!aRjgQ2u(c$I-Io=YeAospdoeQ)b8 z&-iBk(2Ktd6zWN9mbYdLk`<~CxTY&bdG$6E8vs)6d8qp;DPUsT3!Z|{q&VAl@l?W5 z&?D83h|N+l@Rlxg2{XWQ_i*~Xvkk`S6)?I=0V3FfC3nDb3)|{}TAG3LQk3;tU}NMM zG|vCDsWT8E5JvZ9vpo^@R_}CT)GW1}aeKD|Aur1qJrCxS(Ae?!D=^v54e)9Ln+44CLt+{{As;h!XtmDU4z8{VDyj4s19 zL_>E3n|6D^0g+|%dR8vJOTRx#S9Rc0ozP5mbg8>$hkXrS!X>H)3jbh5x!Z+moDLT+ zaW@P_Mix@0J;P4nZoSgCv5sU_#9HhQt_DCdGE3?t^S6V1d?4?J>B7$ZEBDM33=AVS zI~Bd#N+xbEN#@3C`pa^@qI6%-T&Cda0?`5n^Q_YO|5lPqs}|g-=lX8t?7!C}cKzaB zX0xbi{5fQ$B&6-Y;?Kq1~nW!)SrV_m}I^_eVZY)bal(7EmbxL}2!g zc)ra~htwWcvo@M&ov+JkH*Deh%Lw@uzk59k=UrjKgiJAXZa61Ms{jaZZonF!W@eT~al zB{!{RIMu%M{*4Fvr~`F723(ve(DRGGv64@}^3woc-$$zWAE~&!S_!fR@pHh#vf2pi z&hJRsmJH_US;N{MlOF+kW7)cVHMMzCui*n4!URCvVAp|&lMyX{2GWaClOIVBIMe?) zo5^$=JvFvjPA-2plqjL3qPP}7Q+8ChMM;_#`d5gi;AOxT!hRJkQJ`dY$%flx3`YxR zK8SI{!zJfD|AEndxlgEsg6gNaZn6zcKL)MU^#^C}@RtsOCMki(uk;?!Q!4)xxmjE+ zTI3q+reZ8nNsGNtJ3c`s$UIv&Z|sVfI@2Jks_~ub6j8&^Hd6nx%9$x(Zv_KZmMyW! z$Yqn495yi`c{Act_a50(Dn zI1XABlV~`p=fcJ$eE5u10plCT^1$^>LCu%G19tNH%o?_;SUI1=4qO3@inRewpsxP- zcnvC;1>~PP=16uw_2+Nb@%J2zLkzNAwS#;it4>F!*A5FyuK>W_MrBZb#0{-8w(ENh zQFVL`Q*|&;P)7egbJ$c_&xdDpS0i&%6T$d);R1zUS@4O6VdQ`XNwtLLDM9n@^3iXkCGT%PZg_ozSAZs)1 zpqqqNx}LfZq)kk?b`IVG6vzNW@zbs9;!pG)kCd-S(Uq7~LoAiwq?Typv4+J46(=ko zm4fxj*&esU@LnwBwK7poy%sCbWg1_9ucysR$?NU03Zfo`pplbx*%(m71hVLf^Y6CB z#+GW(_D1i&;9U&AQ|#h$-8%FJo?+BTjar1@i-!>17Kjn99p7#jvn1a;nz5M>yuAH2jDz>-lD?ngxUAsv4IEg_Uv+m-a`K0>Z}TnuAr$#Z+6gOH05*-G)Cvr3RLAKj#<55_WnW z+?`{V6(6&VHHi09)Ar96P;!r7LMgRPj_%a0Nx5X1e{-O+dG{pW zTXbSPlA~MpkCq(s8&FnTHHY=)KSMIb1ItRw^*ym^CFN3b(@cS*F*)_}AqE9^>OTsk zwdeDOx!+x7<@b>ZF@)mlA|B`_RGxGznWC}$4Rik#?~D2yvK76FLFOMmwMx*hC2e$j z8G1OoDzctiO^xlTsZdnCq9!w`gCiDvWR6)yqGF7BnM<It6e8w}=G={BaSS~F^P1}w_lrgH<_H8J@+O53nrGcn;-$-x2HCo+PSHN`6C zgvQm z@iV~uTD&Fc6Wc3$Mn`NN7_?FCtpCAG^ci8Y}qPOMBUu9j0!Tg`x_)%&1c24f`Um}&nUn12w5hMJJJ!8Xm$y?95 zTYABH8kvsZ@WIY(!E@tKGiz#bvizz+QaLvo%WAZaGurv6>uA6^RTeW+lE>?=xBuUw ztdeyZ%Ciq_Hk+eHZ5-x4LPEb-Tu*m(@1Of~u6al4b@{jL7YH%8?J0z@&6K}c?CLVI)-p!r zMM;E-ALXyCZ@|F#dmJ!1De;S!e7gubrw)l0Ns$N0ne&b zU*EuZF%wGT1w;tfc8OPE{d=R0z)1>2O25C~2VTlZY5C>S4Eq4VRB=*((pnSUMxz6z zE@4Xedf}9eq+J^YcyB#nWk4@DyI_M{%vKx)db+KwI^gW9u8ZIYov^LGx1O{t?W)gcF% z-W%c9V$W=xO#An*$B`2N`vrSli+)81-jm9;m3p~Dk7pa?!P9X{89_Rtz)9{nyY{xe zaKgE&eL$IAKKxj@)WIMNnQB|F+ct7%fNnoR07bB-wf`nG&~)ei5NfOGyTK22%D)v6A%{Xih%xj4pBDh_z{`T zo-|fjzM??md;?zef|q@f!jluNDsGuQPj$}0Rnel|hB$Stfax{;O<``qjtqXJVcKab~{c+8vjCzkKea?^l zB@Vnd7;U&yRwWMUHkFnGPe0R7iliVkJw8qIZ^bA@taR;#%3d2akIjWvjY@UDB(0Uq;h6X)K0$VXwxyHToC;i+vF z#*UTOtiLwpiyb7i6$}UG=L>1jE^dCZ&-%Y*zpEd&M@7nkAGFQry0j~nY%5k^{^Z3* zkKB1PB)dGwD)bfo9Y)uz42rRF$C_DopR6e2>Cs!wDA^fCrbn&}E^EKKbg4f7frvm3 zYp-AmzwPwCjKMg4rpQ=JYnB^~bmq3?RccDh0$~95p;73Gy~D6Q9UUF}PDOd)ng8QwlqL$mdd`-Y z6HWWu>hMov(a=_ps&x5)bIkU4t$xZ_tCAS*Yc2mn zck|Kl@xPDWB%XVx|MAfK|5kT2Y|W6DR`AenKAt~xc}L%&k@4h7i>fUkkHym8@l~5_ zq^fc)=q9|rFFA3SJai5xU8r6C-^!G~>*&dp($atYU%;?^OJ<;=jjd&nl~Au{rl>>5 zXQYb`SI0}v>YD*%3?~oXa4Cnz=c2N&Bp-kS{E*|EeUIQ%<3%)#6DlOD_}Y(8f^A1<%Vug0kBV0AfuBq~V+ljp)%Ndl?9p%puD7D6K*VaDKW4=KdU?dNOY(c-gp2hyslno!=CLEeC ztw^K9!7qIrx~iy|Ii?jTzBzU!#g4JGXQF;dxfO%pDiaUy?gQrAF>&$F*F*N&)+!$y zta#gz9mhA-+P{Wax@z6uOqT|$c>n%V0S>lS?avU!chGoJVwwf-W!I;6fV1_*D`!$> z+FUb~V>sShDS0CjXKH34-R2HFs6*{<9d(oUn5}1i)xK#c>>Zfld`EBnDL#ZO9NWG| z4cu2dsH((Tg=?#@AF_j&g=8})b(&l}1HHboKiq@G$IeF_oQD%1_z{?=rd#mUk;KF? zJ!(H)(Ge895#<)vUusx04OC>MCpnNvbwalrWg1+aCw|^kK3UsT&BfZ-qPUi3z}Z75 zPA@F<6K3C`*J^M*)f6k~InO5NqCHM6e^x%%RGQIuuAg83P!5}a{A0zZIAiF1$1smF zoBsk>+l!kZ;k%xE)nfKrc?JeN8?KXLFAnbG3$-;2)-=zi`;f@aQY(-w~O@D{8Se41nF`DygGZ2YVP zN*z=sCXt_+dI=`{`Dh|`Wo)HVnj6@pJlePOCGyTBXnIObvR-;W#BVaOt^=+=bsp_3 zNTC0bciIfCat(Ca?ZyN(A-i4a(cQa)pCu{y=1c!-yRFt`ct~u3LdUeat z#;g~rTgoTC*71)s2XKX~Y6jNY&0C3j20x5u4w!`tHAw#^yEerv4&_!o?1+gxKwnm7 z(b!R(bPM$VTk@D$`ma(Kt6l$Pl64?%ee;Pr>Zt46t8~=$|Eb#5+p1`hWE<0P#sIbKC_>%o0@TIb#84=J(Vi`1D z(EEJGr*o9}PQjr@0zR=fvP}q{_h*+|>FM^4RS$RA$$MV~TNv{b@@>HBY!O@F-}d6R z_nh3HOQS~EDSbc8OH=UF1b9l10>;Z=3gYq(CgIxw-03SsnO6A_?1D}z>YICG<0PPz zhyNDjygno~R2vdD%dV9Aw3{!heiZZL{?db zO(L`*vIvOC8n%cKAOh2h(r$?&5D+0CZ6^c+C_)l2L^fdnA%IB)5e47;#U2@jYE4FkVw^DPVWgas5{n%L zH$?%wGIe*;=+TIm<78D7d1Cdr*%`~tM!vQlJn2ko72a7g6lyBplo$=_@D_+;|KnP& z=ePAck)n?L#HjfMntGibPu%AUUB6c{+{%B)1g*rY36qhfEK@PpPxxK74*OeHyJ#2F ztzR8f-z1}T-xG`Hk)7?O;rv+DgMcNO@y4A}xJxHyp~{NvSiE{9YfnBlD6tX$a5_iY zwyAi<)djlwVC&Pl%dze(Q#-SS64dy`B;p&lacHA9m%9IDF4xu~i~3BgVXvXC7OeaH zVS*5^N_N?ruzkmrBp)uQN{ZoiO5r)wrx2}+<`j#{n8qrz#>~*--Eo%ESHd$FiiwGp zg*KF!7vfig%GgNS?w;(l2c!AiRz1sO{`%Vq4lBMB)@bS$bKuW($JTYc{kR&~VmG6r zYwbu--Z;FKR@cq8vpTEi@9$eUH6`Binl;O3&aGqa;KmB^QS!gEMwinoVtdjVULVkd;LE!B4(-QSj+eEy3Vzk<*`T$UJU9R&Wm~# zvi0!96~rwLix$LG9q!+lGL1bIR&fUUz8`aOu&uGYJpHhFO}wx+f0@7C7F!SR5!f0> zeDDEUovUll4nM&JDkIy8cCb}WL$a}bCf(RJm zYW}9c-OfG8^|$lqG+zZza5gK`%M+`I>MO3XJTh=b;SbJ0-|7dXxjk-4JZMG6>DE_MzY2nnGhHNlRGrKjK$9kh%_Em zpJ%MfJgdgvXusfqyY zy?ZyHL$KWO>OsBd!yPytR{db0eY(%_yxG%l%X&@XIFt>nYivoED|YBKX>a>i%x*BC zj_L-`F|)e`d_o-aNN`G&m(!8bt7P9%enB|{u9Et+NJ@j<&;>2SOSb=Q85SK9+{&Rz zSA}X_mgS(+g_$sS;*F!o?`7pb+md}Cd2xpMW0sdf?4Ix-cVdvFrqn0#M3}LMA11C+ z{{sbT8;@H>LukkSlHDw^DgKpb_$aAg@fkg|w8Z>u9aTMS_=e^N4*YQ+{|NG#-u-Og zD;EIGMTpC|K+B1vuI@hKjh^h-8oa-4M$2Tzo5LRW%bQ=m&jC%Ptr6EONw10`-g`*p zIc)!TZM1DbNz-vZ=YWCuVB&Kik;v(6nvd?{I-@`QJ-oBuz^z5U$m`5jQwq961;?wg zz_Dt=36fgl?3|i)Ou9jIXTzF)ON+OUM;OgK>en%~mzNh&T?uwG-ti--Ka1U*ugcX! z2&?Q)ei(~!l8;ih4I%7Tyr0@gXsGafFjgND8flSBL<}Rx&-LDwd% z?|MoZrB?PLIUD^?Ierm-^SH<4ewQzSorV=t7Y5&s-Vvmm+$B>Tsc>3*_c9-ItdUHO zuD>YJ35U#XUckod7-5tr!Zyo@|H6P#C$tDuPRtAYi5C0v9l!j&!r?elTgh zFnv2a8;s4iZ{RS_8&X6J2FNmf<1id`;EH3LA_H=Di}5UxEzYgeOrLu#T?JmW=I*C( z+s%v>8ApZTVW9accJ%9>GGfH=NHw>*v3rKf&3`q9?N_RI5T|>jraR&Uo z?>XoF&Uc;j&+(tfYk2m0)?Rz}D6+G+H=MOcYfs`?%m+H`iooi#H)P`s$$+Ba63bW>CGQ`O5Ny{b^LY80h&I z7>d;M5%@8vMd20aW=i7E+tuhY(?L6k)=BF0YzR_Q)T`%)s)2*wp4>e{sMHD|r4<6E zzWIctBSmApGiHka_^TD^gT6>eCZfL2AD((oo)AAgeNmp7Jv@;JUS~Xcc&eG{`<71Z zR0#;3RH_u06&H^!G)bi)vjiH_FKHfw`iIWqZg{L`f6)!^Z1!(fSH2~Ho4zj)5q**_ zk=D95S8F@ILo5$eEgmEJ`T4lrZLe;xZoZQf@5RL)AMn&m%6VNnoNDtT`1Iz+6GKk7 zrId0g&Uc7V4XuG+prNtl$mQOfg=OI-LXlR^E`hJ?ZKc02irVz7WW!SZ$r6k{OOk=m zY>0ot3ss97^9oI<%D`c<)v-ap&&LO6zVp|TXr$&fkzx0?Y9W&ms zBp{F_-FzviCf%MoYp=OXfoii*+Gw90clqZ_5k$qcQC`e>i>aKJnFF4Eh)XxdMNG@3 z$)zO_ntvL<$h3iu-qYI~zUY&o-RPp-C{Ut(TOA*d`d}}&fa%<-#q&2ix@6 z@bK^gnnY~Y4)t}*G(JA8TpB(>G*w6pjjYi0*ueTs&ZY$W$c*$mfb)K>X02`V%({9S zr{jx_54;x6EydFPyAH5Pa6c#iq)LpUs>ksu`|{$~*~{ARP}^~ff!*WPh(?qCl!b(} zbW!)zJE%w2^XE6g51#=rZag>3yXsqaNVpKc%NKCYaX)t#y8g6i&B<|kzm`HqCQxfV z7sp@K6W2fGYt=zxi^YEe_60ERF<&-orJ{$HsZ5P0Sv5J&ozcj&G}*6qo+PG`OiWCy zZ`|>)Ee$=m>rx^c;|Eoh2`I~ChrDdsQ`rRA;=uB#vGU2lUEn6u9#dg4wQ|zVMzVH8 ziIcs<+k5oV!EjpYyIh$|U7r&l_vSRfITI6;_w`;aD*+SjgWACKZKMIE3l>J1=|O;Z zk=UOz0C{aw#g0A3-R9=*?letHL4A;}&R64}O+~WV=zA2*eCznZl`NC|B+r|k`{L3< zO4jg#tu3f`qvV6UG>)++Ntc`6@itfl(#}QAipZOJ1qXK;zm5MCh?{enGbin0;Qn#h z;8neLigAIsq(VSr!)uGQgNa)w)Lo!6^4DxkY0qy2qkhvCSv2}G|{+D!)RT}_}nC? z+Sf3e8Jx|#hmr1G6IH)--`p2yIC^&teAiBYb?4L)!b(Aav-P=}dKX^@qV?s=KNHj9 z_DxS%ILDnczncQ&aR~}3u+plIyQfXxuXZeZzdNez(JU7KaWSYZpjK@zZuGf6jqd2B zCV+AHTLF%WH-t)gAp1RPdIaGRn%(f`Zp!^-94S?#6o`wc|5ZY@*T5{FzlNNmmr0op zPyh0_;hq#VunM%oF|Hf68w4#h%kwyki-nRiWV~GGGMW7IQpZ4U@~nf=m2=%(n#b%c zD-m+Fwe7g#W{}17WqVzpF+Nc0;AiGgggU&H&iya}BUIp|THrPtORaEXJK=D4{_-f- z=1#t$o3SeVR|@LjCK+lomBqvgxZgh558B`1__T0*h*n~UOfvIRuIm{IjS7u%VUT0@ zcl>A}cpi(MxbhgbP|NS(6cSX>=*JTk);CNv?#&w5m+)h&h| z0A4RnMV$q_B4{&}O2;mh%_I&Qxx8>6sb|ZH;o~KR^0yqTRkjSNHE{e5J72?zT zd$Q7n06c})FQrJbPNR9dayxCYGHVedd}z$M@hb!bOfHeumtWPYHyZLpPzR6juQMnT z{(`QdGeBEicOTiI(bpa@mVtbbf z2K*$wPKp9R-Cq5d+HmXT$=*9-^{sr5V_1ZGYPF$95|#5nzKmk~$4Wmq3GMR>tkjxl zv}E3^0bxpgouV=r7O%4hL1t2lh{Mgr%g0U3m00kB^Ah-$%_oZyNOf`&br>Yiw9GzI z?~P7#5+fZXOrss?ZMENS*soTl(Lv217c`$JP~*D(y&2&3O`3U3wHV(bGQKzgcv100 zXH;u6{H4Bj)}%M&y+`XW-&V>&tf7$VD!lQXQ>#Js^h%g$tv39iJr(DI&m&S2qJjDa?_ zqX}JTDv%&*1zCKq7Rxpe7o(PDq1g1RF`XevF#iIDe`Y z4!@e`!e2ia&peI%n7HHY9UgL-zMhGq*v(Yh!qq*IJ>oC%O31mIuC^kaj6uROhQsk1FOedy;id zPJ`Pzj}uZZb(u|Tn#w8GlUy{HBHF9Oy`al_!s0v zTYfajOk1<)l>>dc8tf??K66t~2y`~PtCB>`zXIGzN_zHRa`78Zx1+p+`v~s6`kcMg z)>30RRR)7!u}155=B zO+Qxu;96b$2#?TY0;V>bPqvfs`|Qdl{aWbYeQR}GBKP;EC9Ub5)UBRKXaF4^4#H5r z`8|l0Coj{6he@PsNbq}n4e4Gmhx0CvgiIfdy7_q;4}gNN0(X2eV zioLkTV+~hG;06PHHj<3OZ#_3TF|lpC=y{_qg&G(bYzPTc*b2T_KH1(_qgFB9oYDnV zQ-#L4OHo|o5qNz(A^eqDSh{`KZM93zvT$iKFt;3>aPSqS-lF--25+~V*1gpBcSCdc zh+kh+wR_8vHOl%Gj*LqC$&CoEQoYWRGkB)c%eH3)=VY9-0v zkbbaMYc%iQiRjAl5&})T1dj(BaHtBByyWMxyl|0bh?cyTO{4L7KPI$q($%V(9}`9N?V}J0FSK#7ds?f3@UWx`1}jX>Yn$1 z7YlJk$!q7_V3U_nG{AyZ_lX3kI_0nVoVvm7@Y$r0X)1^N*-gqz7~xfj+WA1kug(3P z8?&&coRSq6?S=cBp72=ZB$$(2bVTye-llGelJF4l&_+QM(-gY6uxNY6(;_34cy+2S zuIDTAkc-(Og6T;S#~)aJPf{)IUrrZ9txwB1Jk-Mv<$5!V?iP_}8sIy``NlT#p8Hl# z)T1vi-=gW(MdTQHXXmYRC$S4c2BIeP{uR;;uKRt#02W?zw;i4IPCKV^{;!f)H_Kqb zA*3SWA)*lfp_G?VT-qd`M&j)3EDNx9*#DS)f5{)Os={t;QuNm_eQ9g=J@dsa+9kAy zRe^o;u&x=8=GFb0;f6po;WNi9`2|vu)Vr6DWk;_ps#&L{f}pZgecMHo>{>UY5)bYj z@h#TSj2>HDjB?XtN9m=>0$DC7{+>6KfKZpY66Yn#&4$Xwolm;=@kVm9Zp^cVOt0&7 zm-~Npr*I`QSGFluKJ?h9tdQFlRF}VPpkE&5+!d~b8qIu9x7kn1!h7#pFT)$nEEw`* zlB`fBUKu6}3i&TAU~NW0huP3O$q{|^&gO`L~rKrnJc%tRPnj#OYh!!;&#lS%UH; zlWT&vdn|tp+GJP$TP~SLd(V2n99m;NhucC5Kd1PRrYh@Rvomc!1QcwBdPYB(9Dr-> z_xZ=Eiz{cbc`}cIWw8c$|C}+m7I6kHE;dA-%r1UWE3gqXRcu*Qs;ZlBIq&XdX*swZg>>Q_ZT20#yX-z4Sh{#@Y=W0- zg1evarKyBD-3i2dp3`E@&coyS^r!BDkn)iaq)FRLuBlXhyxg$0^?b$P+;w|2HnWfZW=s4N11Phsj`r-&8FI2b znUsuBn4?KlX#m9j`pla487FIS7=9U9ZAZ4UmLY_8d~(OBT0@9aqfsSzgn+=wSx%58 zp6GXTZmax!FyNaH7@FX@Ci$^#ea^X-K6Ch@e>1+;x^Kjyy8rwY2?>dAdTnPo2B@-@ z5X7N|`hb>9TS)mr((m%V(c4Aka{}Y~)`e{{SLjA8HmyEB^Sq1xvC&okwV3nGtF_?j z<*L26OSM-mI9gL?Xy`1py+jog^_ALFrI*HjGwQ$0UCNCr*3@F?2~Z z?vq6=x8LJ{K%m!%7kpI}7=g7Yi2otE)l)u>LHg~Bgz@l0D>5?I!xIVp#qi^k>7UU5 z4=)S{093hV1WhB3Va(Aku^l(}Yz0|S#bLd8`Fa$^RH24hwr@n@_RU5o-Q)Nn zzqn^+@UR{0e4k*59Yg-jPPT_vV{d2E(WH{C$e*a=!`jFhvAgBcf)3%VD-0)A^)p+)mr- zbpcj-Bsg4$Nlv%&crIaFi+dfL!*aXnb$!sIA_@Krc;PFsSWW}iW{At;Xu4sN?9k7u zmGA2Jmp$9CA;=vKL|Rj(KX*vFKMz#;_Y>ftQ@xkbX(p=Y$zayi%IPG~^s`Z@A0cxp zi>h^MCDYeJIzXPHzCk}e$Sk4dLVBgwI1=r4RZ~X_ptZ~ot7IPyX{Sk-Af3D()DX}t z4N18N<$Qm{A&@rozkZwCs-hs9w!b--+2CSn`P#hJo%zRd2mUeBdM*KfY{H3qIk*F_ z4C2xIvkmt?yA;Yq+9Yh_$F==sP;Ta8XhZI78B~Nxwn!EYb}5F=4Kg~l-MlW3>Xl25 z27eh5K+~78!9f}gd{p${j0Sxl!REA9^qib4cN>t@s)%nl%~^k+ zd=Nh$LV?5G1d@zRTCsAup|Gf9d=gIYv@xHc!Tg|u1@!*kgL`F{%uAKZw}1vWI58;d0fO+-Ez0pMkVvc($FN#5N<#aw;SoylvJ z`((j`2ik9a`g1q|NLA~_$LHnS0wm1pl>}%>;?fl+tn@v{h_=;L`~@C$#U^YQ&)f3b zWI464cs<4p$G|sj)`%pIui;jfvxzfJ?KwB!?xh^2y03RTqv%1cEWKO2u#|u-4GGOp z-CaICT8+26WWR3juUnXMo|SNAkDRq$AWW9{5w?Fx`=7KnB%cRw7)S&v<$;TfuXm<> z2q>+u$C zf0V6W6U0Kpav06`;q~aTSzIgoD3^HnEWdoY$EpL}}6$la!Wm$>D;v64pmA87PnrA?A~`TBTyOXnUf zaCehFy@ke^>d@jE=HpT(nAl`K-5vowZt?LDw}wxr^OzKcaOB@-2Zk-&Hrgz5xJ%re zXchLZE$zEtwgj+RO~rM9%l`q_|BYzpF%Dl3nnq2&PWFgvEe@k+SlzImiIdSX`rT> z+G{suuXD?g=HILFPqGuvpVoSL6=pH5VI17_RzzUk*){H;Rn;zD^4QG>X~WRV3R4Aw z58%@EH_4ZQlBxu+;ythtj^~@-DduY;?T%%;;+ibt+3N%`xNI}NmabnEro1QRqbf5% zRXiWtM!PRJloKl~@5w|fjg5yE)a-r0c2iDeRL0!e_|r5~#&=u1<1k{X%UjxVbpE5j zUkWt=={7u?@x(H3=lJ%CKy%&jjp%QSSBNhTBhwz>zs#= z!HT_uSqmYH>eFAP)!A+Lokxf;Ol3j`gpn_ml?%A%@e<1SANKf%zJwRUNqLh(OSm?o zB5ke9=$P(oMBRs$$Gqu8a!MH_pZ^*VmT_&s6upgaYm27NQlI{u2!e667*ApZuwFZN82s5Rf)~%;)#iRY$fv1XM5WmGibXI^%Zy z>0Bz$-G<;=8ipgJTe|VQ1(Py5=EZjIu_b?7WrTET44k z(=TJE+&tR@80#bTjJ4L9h$*Sl^+2z%67ia=C+$w6g%6%V_6M8x_iovZETWq`>#s|M zy!py1`o|A*8moVp%OYeWrRCLpr2gdI*eK-Un#{8)9b3PHbKEYxzY47_cI3p6R;U3B z*=&#W1pxbV=^HMtnIps29pm~eL(gsJ6~}W`U5gTN#|X$p>fl{Jei*ip%JtEYy4Y>a zUZsmVx}VRc&z_`~*W1>{lWaLmj_0NBX`Ioq;X&5b8|f8v^!67`&P zR=~Q6C@^AqqBSsjV^Xo{pSJ)_ZXDa7oANuN99b?Ybrx^Segx^`8qoHC8Btzl7kpY% zwixL=Z$wB)(y06Qeq{3qSYhNscJJoBibL^W6JYFiAUT$u6T&k@6n=@OeQ--Ze^)~I zn={5p%sO0^nBVgoSZ28Un^0j9**WPbf=G&~3Exiku#tap^! zC1q71V6=i*o)84I=|NHEo+{VfQlmgsh$W*){-tUB7CF%So8k2w2@4q%*QX|?bObHs za{%V~5?*I#9>-qwj{|IgeMvF9$NheDJ+~Gnbaui=_P2+rsQq#2Q;E_y^qTDPlc1ki zC)qJQ*Q=a;KYvi~laO-#Zaf?e=EUD(e)&D}KrnT>KApf}+v2I}(m1P~(LrPE(ri@! z*aVBHXcHL5t2WyZIOswdhrAI#n{_J0P9)8|>{4{!E%Y;=n#%E6TxYMw)2UgnwS+tQ z`9R^IScjpS+o^;IR!rhC=&zere$A*&rC}I(f2eXiIgiVBI+L0+llNY5`zZ0%qDv#@ zcnJdUq+pAAuq*&K?P9mu21v@}7T?5! zI#RQuBg{Mk?-%68f7vMJev6|wWk&VBz~awe8LjVouDxA9{zqPPLvBa97cbFR!>>)U z;S>wY!F5R|4C?lFFJ#QOA|`tN&JnU@4WhYA9`J`dvPDJ3macBdRZtbJVqYtgHavs7 z@s;nG86FUEdWxy8P#ZW%dq|NI<5rb7d}4Hpa;ZbK=VlJEU#Tjto{)YH2$Yf%zl{$B zG-kGc(Y7YMS(XUz?ALCLK3V9BLKO*GHKW^j|NYLfvvH0se*K%H3n-$=Dj}o439YP9 zkyY2on{0TXW9`*r)jw&h;_LF-d6vh*p&N*eI3NF@A?d^P5P3mpr^$vMZ)nY2m*8M0 zxco+myILzXH(mxSs>w2bb`Rqmq9Y`hvoj(b_sN4$^GQ7Q>z5ra;baNf8fdR#t2=vT zI(kd~Oo;c8`bCybHVn^aHov1=&92#ObUU{{ufZQDUXL4jnS5U7=2q`K zIMEX@L;KnRL%I4nEj@I>`CQIgeq4knwUo6$`eq|Q>R8FYmkgS==J0ctA#4~j+r5dl zw_xS}qZt21P~>E6tkA{B((Tj)cSk~YM}r@h@Fo7}_*g}{quU6EhcMw5*N-;*laj@j z$=h~1Yt8QmhI}$lBQ%{BUpzV7K@S3P*z})0lxGz zq5m%|AT}4UmTW8f3eLT|b45uXf0X|7q)BkD<4fY8SH?j$g_y??&N7eZ#;i;aRx0Pi z?x_+O6)4|?A_Zcj!N%d)=XA1XnH1r z^MR1d?rTC(d-K(wIFR1q^Cjaytkhh&-oPt^QogE!o~Sx+Rxn{(d;A1~kO0tW9h?Oqffc<47n^V~LAr6hJJdL4u< z;2e~J!CR!|&;rV*4e4PEbu8a@W@{s&sc0@^b}{67Yd}&eE|Z@GS$(iHAr;Z~_odeJ zN4QBd4fjYJ?H%g;%FfxX^%7K}z*hSj z({8BeL#XL-Fm{A(8LsKFy|)mW{@SOBV;p*|wcAjpAXalmMR_9hX^G3MVD4@$Xkw!3 z>lVAykDv}Fe4EWXivBg)uKftfN#ZaK>he8qZv_!lnNnxF1EC`ka1eMBE#-UX8OTX; zClib6?K#5$RoCJo(RsR~^v7aW&&LwFxANjHuvw8V;B}k@2ColGcUaGWuxb$l7^3r4 z(-7!vBF@n@1N6xhov<_$MS+&j!Wa%blb6)N`n*-Ak4`>0u;N6CNTO z^<0K@NMJ@16c!gJSJ}$wh!Z{dJd)cHqOD$Q15Hb=i|A>~$u(=<@5^TYC$7U(;Dhau zz+G~~4?v<>hq?IW#Kv4qe*fVvL-e9o50t3(J%fa(#^o6$f6vr*ne=j$f(bz5Kdfbd{6Cmr$azsUl1wpXycq>ivMANoAG>!N(-GW`fi0lvF3UHlvP zy>`Rl>P{!hyXkc=7WndNSQF z?=4O8#q(>v!tpvYB2&w%g-2#iS zoZ?^`_x~Jzk=6qA5;ieAUY%r8$B%W-UAD6NM52kC_G9YJILTeX3a$i~NNE%L@f2>aL#fF0p_@H@;lG2Q*q>xM~j@wd9x76Q;tE5s@8^G2`*DytNL zeA=mAs53MEkbC1vy9jcHgRcrjg(G6^0L&+2v|qZY&Wxv2L!J4*%1jnMx9w$M`H~9d zo;hL1ZX%128geqUPog)8=WSY@2?AXmi}37#VlW~er(gA@583Idp}Lm4hE5=_Y0LK8 z6*a7#89CQ)QWIkWY;6MRoZWwOVbl}W*t#G3+v8CKv4+LGk`v14s-!dXj2!6Gw_>+P z%u%XzphmgVoim{dwB(G#uTwd0zZ4ZgA|*Fi>CD-;*Bb)Dw%vJs66@*7A3nQ;jqnhx z-?NK6+ zcwD8irF(t5=_fW;6AQi8-}a$nYq-b-F|jgO+T4IqKRWb_Y1Lc!lDF4$A7Y71E|sE= zgMMv+kN4W$!%rXwgR;>>Df`s_phRZlqgyPGeEC=Ac^$t~q2%=4lt3$$mNpsGu(wD< z(NP@m?aGi=q{lv#9{CuSLgI>$FQA-)6V{&;9|{tYnixU`?J(_bncpYnH2-=O z*_5M!lX0+ZV(hoh{qVz5?6KHCO5~EV-~SF{2e3Z5I@@c|0H+}VG_`C5AEwaSd4iKM zS5>|>oCZWn1Meg~hTqobd3KNA`QHQfRg^f|nk4#9+BDtQ42Sl<&nUD3GOLky11;`YUy#FRfn~-=V*2FBK4<93qC;} zrl_N6ry*ed%F&c2$DawT!HrqQpJK0Z+?w?blVnC05b!*o@$J z=!GF>)#hnk0DU(=TDV)a*GQ>%MoxAMO$e+|&;e;nqY@tCH|u=H4`1SosG3@PXM_tK z{y6CQ#})Gxd?Y`omfD?jLv)U)&QWbobEFx<=7@4HYgmhhPW<}OsCGp0mb zftNiZZ`;8+JZkeR^d~DkopgioF>)M+u(_gj<@Q!YM&1_{fGMqvbZ@<;2W=x{@3dOi zlSZ0)_=m5ql=|uj&0z;PC3%To`Yf5Q6%3C4!l8k4F!O2#UXv2jwFT?Zks>spGDvVlWdDaO+s5MGNuNqPnL$(P4k(oH!5Kf}D4#pwKb&364r zy~28xpP=`PL}?9dpd_{z$ScY?iV>`mtO4?q2+k#6POcp~zsgntE8jev#tw{f}8*bsozrRhq0qF>;v zV-g6P|K_{wp!#>hihqRgWS(C_1Z|2Jv-Kg5!Kn#{339K0Fdj@fP^FgOq_-C=cHCYQ zkw!W?mRu#E-8NV_R`JQTjO<3FA+`*_;%QKIzaeKx`Z`+|@V`Ro_ARNDU`m!+=T7qv z`QSJBT&(hi)B+QUcp5t$S?=@1PW+mA&jKXYw}NyMQ;Tx6Qt5L{5ytgZ{T`hBq6}ui zsvIXY{n!0wixuLraJ(2$fv)+zGFL?Y&9TOIBzEN2maUvhx`8yUqrZZvLyIDX7pfD+ zqZaRnh9d$?ZdjbQ82&_G1+rq;d)HjXvSOtHq|044mR<+}=S~02n=^a5Q#~wYlNkbg zZ3-F9;~{avmk6*4Ml9W>_S*Xil9$jy*Wj)~?EIuOm}m7>!_k?wJP83Kkk{|vHJg`c ztJARv^$~_9jO6o!oF#lijt}wN)rozzpo6XXbK=D*S~5f*687VbjG<4lSD$J7Mb=X} zOlR2H9-<{?Ne2EGF46nMd^hZqtPagkl3otyT>NS4$Bf>*Xd${z80l9Fdg$LGE!CSC zeDb$O<#F_I?gjUV+_R9e&4wdhNZSK@KfHhh9uPzM_8fonzp#KS_Q>-)deR?Cq%Q%U z`Bf!f$uymMZ$J3Z4N!Z{R7wh3+_(o(%O14Z7Pq8|IH)Vz1O~Z5cA2FD<49wsuzhP2gRm0+(nc+J9P}fY+lB z$7xFpUUZ9*l>Rn7Y(ErM`GJwVxVizS1r{hje<(QIj1l#Q4bHWc&TVb#oj>3k%E5C* zfUthomniINejW3*LzEhpkJP9LXo15JC+cLPDsCDS_6FwZV*5F5V-z?UE&S#^{NVk;?K-FBr<)sU zWG(E{ZR@Y~@AX+iyg+go$KA`*8y*kNp!y9#m{B8O+ff_oxaoBmr8*F(_nyAC{*&~z z&mQgSUHjOi8@s#apG$^77NVYYmS_{;u2o%<7bIzQ>pl&poO_>osudynO2hAOH&*^E zfsHP%s=#SBXtlbBWR;_=PSNQY+xpnl`@ zI?p#rsfalP417XF<1@&ypzjK^!0g0VxjG|m zle0jfo{l}}=aC3o{y4zJHmQW5F~H5nXIt)-z$l|tUW=HcM%(^ljb%rH;Oi&?!ijmk zTF)7a-~xXXfO9O7h9WkG`XAL+)ilvr@<`|9`JOAwF{_$xuaEk@JDAON@%Y{EHFAu4Uz|KETq-o$?crq7KY0MkNmlgba>+)5L~FY0TdUz|;* zR4EpkiNAF#ur{L18ha`A(pX8z~uA!m=RYV&^ zbDrrqM)b1QPn3}Iyxfn=^`Q@kRM%C4G_Yl3?O}Ah`Iow-Sz4FQxAjktsGDS*t{U|c#X|a!UI@e%xkN3Rb`TD|b^<7}(Ysf+? zwhxJskM8$Zm&vdRbRs$&9#f;|z*n#d5zhdR<@(hg_BnZh=K=8ExilkEe(s$73;<{q zm@B(ty&`FFa6}Q{Lil=&ynaxNG2#fTKax#JXFch5>PW9oLO3-OaCzakX;McFtkYuI zJo0;(fv@^(0#u2mTtr~~z;;?{z)IxPe(W0_iFpJGrCo2Bh+kzv;N=T&e@Xg+%$7{o zxoLs;f=2|IV0*_Gtywh;Uf6PgCu|=_)Z5-aQ&`J{maIi><}iBN1Cv;IQCCe}$)28C z7>?Fecs@M*)j^@Ov}6*yh*2>d%-koan8(SsZddq4QAt@w()v=|xH`@*;X4ccfb!I& zDKzv*DkNmmyFV)^;|hkyNgkO zpIjOW<{NMi4>HR+kfqt=oML5h=G$+>5&%WDd*ZLZ<6rp=TE2=tcM>6KwAV(SsJ5k` z6PG@wpCL(#u|A0^*)8#R(YYh9@ieAez0;nnTcrz+O%B?on)>!pyu1-2q+QHCd-{?X z__F?;?TISgv(yr?Nz4Nje(7HCxmrUw_BVWSDZukH*YfIfVKR#HjXLA_#l_}}mq%TU zqPNXM9i`U{;zpLNI2`E;{|n|hq473g2c3QHF4E!r>vqgofk-fg#h>aup&XC#G#~J) z!Wrfy8DGSGV;1?96Y{t9zngh9c?mubUg)cu*l{khn<~KmWN%-PhK@;06`}m;u7nPeR@Q(_$Sgi} zRV6{=B?{bacDw$G7mNN$8lR^+{E9uZQfBY3Z^x74&VkDM)+ zaH*Vz{gV@oNSyQFp+?2U(@Gq*e+5T8N+=R|#9Tz+_v*z#e80JtceZ;^q^SSYILz-; zdFx|aqKT_NtVVUK=$oc$Vq)6COrn>=={`nvK=MC@5b&sPTJr1IViw9>nsr7VlbiqG zv}pf>X!AFo<)P1osB#cjvgBaS$()KkxCQ?hF8_+J&><(^MUT6wkXhnmBaRsVmLjxd z+w7nhF)p#(5Tj<)XL8UOl~Ot;#7!!Ro`$o;xI7t5)v9^^bl{ZWvjjRsWf1s>J~wmN}G&L`4=| zFJMQ|1{5OwZ_U&Y9Dt!X28zY@a`OATq@<4I9bI&Y0Fi)AQ;fTw;ooAt(`0Rm5*tD- zM%I?Ea{c13tW9jlC^mUbqT{rpC0!qWivKQ&bJ3oVHlF_&x7q-=t zHqTCLYJ#VFCZm{-!$86yG1a5)KFB#6r6ILEj3n@(Hs|I|vPP}kRQc8>~Az@ZFl9ggs+JW0()IZ)=Z6&2euKJwsHP4b7IoNk^%x_P8 z3ITMh{1U74Og;}E^eWRgwH|FNTNF_|8y50)&;FJWQTk=%ha|nUE;uh(2VULMHx=gb zUL9rcHJ|N>#Z!QAI@Xh8hsSH?}R}S~NxFbE8;|}*2x#H3|?&3D;dl%n&z(^e0}Qb()KlV;ws=X5T!BI6P zK=t$gpuBI>Vap$pe@Ih9T#mtldL{!n5r_^h>Lq4+4eiF^BDhg;%f;rd)UD~^GB*-{ zf9#3C@4!X`{B>-!crcNl9E$-mxi}tPM@K#3pc1|{TGYQ?s)wokj63=lmZCXs-`k?2 zhqj!dznH6yt-@3EPTQoAOk<9ms{_+@{bw%54afs8wzq{Y2jYNhYJ_}uiE-(V!9I-` zQKlO1rHkkpaOofP`F_=JzWx`t;=k8FX$bT0I2(y>aia@L0BdUwMo=o6o{@ED$RKIeH^qwhhm&n_=G*a)Fd{IV%C}NQO8*p-EkS{P+{OO z^qzZa9}1%_$8vy^E}RTZb)^2AiAj4LrP-~88@^$iG#{oW zCKj5^-ysdDB=cB5ewt)R<^ffHeOUgC9AG)cP5cOka_z*0fh2w!>`++%YU z>eq-s8uKwaTWOH`r%lf2!4`TD0+kF)4BqGy{PLDmA>D^0sx&@1tw<(_F6i#M zO~7gg=7yZBI$6HJav+S z!{O!`0k2%Mu`-E7Hlc;h=E6nqxOsw7SvrS9bPS64i*0s)cRYT8 zwTcFz=|s6_I)#^a*MDIF&R;`~`85eyBAE1~$6`FkZ^ZQ>IBD`Aw~iF!V2j?58}C$L zu?h+rn5}L zI2P$$4j-U`#m(7Eqx1RL!gaCKx?>pAWl?*!Jqc}SEJ{QwJkx4R&;#h+du4ysZA_vh zJVtlj<7M)>GpFTsZ^?hCNq41T+;xWj89asjb z^nW?@gD(Har;z-qEjZj!=!G1Nn5Dg+f+kU!sN6M`>A>p)pbEo~9fp4cS`@r}deUm;4xx z86X4GkU`an7FCmco|1s6VP19?O;M*y`_TNsdG(C4bRD_Vf4w+87DR-j*R{`i8wf~* zZ%g#rDn-+6yl%4VSz_$#!aM|V>Xlox?)C^%LB@TK-&WW*sUAGZ)C`V~NT6qfi5<7K zt+WGCFJ7U~WkfYf!3-U|bnpMv-x>`buymhaTQ$(p#DOF$;xWh}Z^0=KHbYAIyK+r& zCZGCdR5r8)!J$)0p#@s?v@SbdVNw3C4JkyY5Yc9rMM`(Fr@kyTZB z7;C_LLoI^hw!^hdljp|ZLfhHI(qUZXcz-B5;MB}YZ#;!jppkODl_mD<&Kp5!e6;Ek zifIDOO(^-3Hfww z#*63$J`U3{LKG6#fwUCUsv`b|TX6;bjg{Hr;jZW7?|E``F-iIO=D!|Id0s`19G+S| zjBqPYOnAD`CoetCp4Rf~_dx@E>keXjNHIOKFiU`M_V~&O;+~)DnkHnX<7hlKON56A z0Ucf+G?H88(F;tw&a?@(jIZ4|-`-!ts6{axT>jHH;|uR0kUjKz`=NGxZb2)__DfWS zb!8E>f0!Im-N=n}C}b*P!8S{NYil<7$b8o`n*OrADqkYtc+#*Zj!Y8r8XP%&M$BoV z2RsoL7w_C3h<6W6hDd7|%d_WuL=3*adN?0dF^okVr)^D?Y5yP0y>(PpZMQe7qM{%m zf*_3`-QA#sG)Ol{cXz7@NO!{xNasy=cXxMp*G+TQ4L*DC_jzM{=No&RasDONy4Jd4 z%{hNDRfgHB9cS}QVd9-b<>m8wSS{CLLpaSTs{`W?2bYJfVW8DR@4M@(cz0?LU1z1v z-EBM^jG=hxRu9U$Na_7dD@SeW?BH)PX51~j&ANLp`E_)FE=Xx01;Z=UKeP8xgwUI& z!1g63TLFn&1V&hJJUG3yrO&uUalP1?;fOKHtq63|_%SP~j!(&{CD2;6>x;5{^CrQ5 zCsk!q_8R*cJkIv=$LykIZ4|4VSNsbDG!`cA&dq7d&P)A~LrluwhdTxqapRjNCS=oV zBM-QKkObN?>Fv+gvXpyuFRD~u2EDY~HzAdnvt8(TOQ=h+HpCuusOc6?@96HL7ZYO9 z;_Bh?+zx$??U3W`1|&gZXGRjf(x&t~g~b*+Yf*V>mKc@A#@ytQ>R~Cxl$vuN^7l^z z#{3&tqc5FN=(}xHKk3hh5PI3IE5wUV)bI#W6cnYGJYP)U`$E|KTtv9_Fp*hflfl|# zyldGqgsk=X_XwFp`@%Je)c-6a@Ne$IxAWFjCqlBR`PT;FomlRPmW?5 z%9VB$7XK2h2*s615cyU3S;i1L?lE-x66yVq1>Pwe9#@jmjtWq|$GDIQg zQ*bY$hI{42-ZEJhmPCjktghgH?E`N&6so<zSD<%eIqv7LAWhcoVcKO0Hm_c7 zN0f7QvRWAlNq%xXYD*M}X_;;bX_;H*d9_Q8pU!2KP$`eQOyUJ8tSWE((i!h4I(EVE zdU=)wI_QIum^z;WTx5;l*UTg}{ncxXWo0Jymy-MD*vy5dSZLlAiH%7NR=HagMNrD+ z9{oR4F11yAd;|Ve7;gfc{x2w(B#}_cW$Dk6NDWze`2dFsTuG|g5mZYf@`Hr$x)maCp- z8_#n~_=c~@6tOUpO|KCtO)A0f)?9O$Ty#ma-Bg!Zwo77DpfmH7bl86VBDIrmE;()# z!0h^?O@^1cbNYJpIbi&?M=Q} zW#(vkLoo?Wa!PRh_T48qY_+=ANW=aef279$mCd0iL7)tcv()P3t}}dXd(egtY#5*e z{KqmR$l&|BnLKlkUkzpje_Rx5DaB_4eJZxT!=FfrQt_!lfW+0{cbs@zoY!f?}oF!A6n_wX8N!s2&r(~kiG=@`r)wh3p0YGSE&Dv;JSWX%-ky8F`* zFo-G+GpseU)+2X(0&q_?aIV0edaGm%oYhrcpWklmnGU7WI0+JC6?19m7nT1>WYc3e zyN3}o$WHn7^_&e{8J6sx-Bw75COVuS}i;YHN2;=a<=l?%4QL z%GzLpJi!bJpKIz*X(TS2(Nw}Je5p@CdG)vYkouuO6HMmAfu9(JvrZ~6rywP}M*vTI zU7vbam*nOc>CV3J_enA}i#Y}fziFN0w)`<61Hx^QIs9-mblGr1iK=zSF{w-~Yapgc zFOmq3%;llzZz@53!km0M^uFT)@{2(Cj#dUj+M{eXbN)$nFnUDl)hxg0 z4~vsD6NzYb^~4+J98;dmVJtxUGL@v*FnKBLs8ms$iH&Yc1T16hf#R*VJ^~h#@mxu; zrpNUh1|u6mX*|YnPS@s>kBN0KPuhAE`}}FI3cNm2e1+@TmLTqjTphQYUnp$bYG4OO zy08_Gm&4chizb2bGFKK;;}tu zId>4FR_G9BG&D_(y~Z-#88$XTd8<$pRKmS#l`Dv#p?XjbmOu>QsiNc+GqY1uQ#D)CchmqG1Ch}6O8Cd$?o+){_p zVygI{TkV;t#so~|O(x%2KSFXZDrMG|Pl;uAYtNlj`L7FGdQ&!jd4?#p0P@wZlHD+M2zw{mTZ%&N=l#cz_bQ-%EEz}I>of22OKc%h@3DoeJ{OKET;Zfv-=*pD$C5?v_|uMW&(hLBgc>`aT(nr2!d#Tj6K zh5U3ZVyA=D+m!2s{hp(N9EP!6|41(yfMJF#F2T!uh=8-wL*laD@CDCRq$+zeYlJf^ z%H4Ho{SVWY;;oZ+qlXRw!4W1CSBH;ak}a@x{->Jq>`vuq_Mjd+=K5mY97!$O=G){g zR8h$wV|#I{zS)W-cDrap>} ztL$=-3oF&ZT@-c*@L;@_%F8h15;(*pkK+{KegCl>t#)u?b}G`*I(f%eTX5SsXS{a# z#7V$Z94PrAYv8BlMzcVwXf^u#BGGtg;o6G`HCFD{iR9qraS+^c+CAVqud~TVFwLMa z(Y#-{O;4=t~DxAt{MhA+Lk(m~W2r%#o=O<~FFi`+sT>&N5$@)gkJ z8&>EZ+zKijv8mcu=Og+5rnrEB1@CAqhYy-t!H~=0gT5bE_Gd-b zuNShofakj3)YQy&;-+JutKE@(99KS5@@2OilbS{ef>VT@r0e!bB^)%#?V@F&!rr*$ zU0fl+wUJUCeHvw$)Um*O@8FE`5MN^xZ&X+JG1Z~f+hZTa?u zRB|&Z{;JO(@Rs<72!hCLPg(GZa}^61ata^i=`%V%VeT`Nbpw2V5C%y@?b5SfC#m?= z4{z&>Y;||;cdh{SlQZRdhpq28fabV%E9zY0$C(V0N#g6$%InXi8s)eU$kE2mK?AAF z;I@{Op!TjUj7(zwD8{Q^CTLyGz!VSwFXJL-)aOqqC_C3OGHg2Wxfl{|tuN}Sgh50F zTw9f8lF#Mz(IVZ-Xr75w zYrg0kZX9&BdaV22Izt$dW;*z@OS6Lbf@vbB&6kD&DGIn$GkV`&sp|BuD=iJ3JU36yoW9s?^G^}JfD2QL{)o+C zncnGMS5UZfkXYZN6#=>*c*BW@tlk+szNAp zk2nhom;_$My=Kt#^3B)MvS~OQ3f)+`OEkFGWl1ipb8Bw6$jlA7gRc3ZgvkHF$XK%-|^p*0sz-tYjJ0wes82~ z`Kht=NV!@3h=1d{MQ*YAvZJ~SK*oQHX*iK;+(k!b^T}|gLP_N$(AW2l6ykBEWpUm;Y+)d36=(huuA!?=A$^AesQOgvd1?XM2R)e#UDROentnNzdxl@K3|fox95?~;0ZI4F8jeW~1H zdn*KV5|bH`=v}kDJmWkv6OE0q$mYFAP1+ZrhK%+Gb3-%}yX2dEtEg20}M#P#k;I zs{UT1U7hqx0uOfSC5(m@u~lCasb?Lhsq=aC(#ZX>K?E{NO;{+a#zxC4W$O34FxH5Y#_*F?7o$ZL*Eh+t`>4gjsN|Cw;T+< zQ@0N$D*z@KMXna|-1@8>U;FEC>%xD-umRh)aof_oP!I;7*!xR}!~d->ht$<3MQE$K z0qmJ*Fv5xu#SDd@%Ekyrw+PqGH#!nfICud_fk1IxBJeud(`(q?`T$V`Xw$-m&LYx zL|G(s!XDLhOu!mw?^a%S*B{TWA-#;*6>}5jDY!1D=aPvmb7m;u+pJ^IheTM-pIL-3 za>aW6`7bfC%!1vITt(u}w~m zuGJ|70Gm5xu;AVOAz3p4*vZDd7;xEK87xM6W%E9}XO*ly6l|@QiikKyog5gSS|Kpz zmtlLZx6G|w{zZRVFiIWm@Yy{&upppE@DWn5d3|8&UNtl!qu;gj1nJh)byeez{)_z< zUT4hctjqjb6j2uWcB12NnbycR=155Bw=g$VD$_vR_)vx*V4(B#$ik@K-LOi>xG@+`zp8j&)J;+TbvtgX;uemx2dy(OQgfE#3;Q8L>b+ayUq2Z=? z{xuz2%zOZl;urD!s3wbQ?rs-yn7t;^^TpBfE^uGLrF1Vbhb7lfta4x^gEl-3e8qf5 zAWscF3>-kw>Je=BLlHLbw%lU<* z@3Wrv67g9d&4pk4yUcI4^xc#Miz;AMJjrnTPUtMUN9>yNDTiTzJ$r5QfkAuB`Z1rAQ@aLdImCt7FhpPyaw};&uJr95#%4bC&6=mz(HO4Jy0nB zS5c3wccMk@Lp5Q@d|S1_}Uu;BXa z`-TVa%>8EI{qo6=ttIvXgj|3UQSYpol+)3Si)lCw7{-N zzy*abIr#wJdms+HyVX6LNF)fnbMuKd{h>b(xVEM0Zq#r&)0?zOys%zDZF8yjp5#4! zcj1E~L(3g%_4O3I7uHtEG8r^|9<$fh<;4?l8ia}W$8{p^|E22$+0*}Woro*0IOQ9k z{35#jLU;9pzPg!fZOi-)dz3TtzQqxY;Poamg-)Ox;wcq7xknrj53e8yx;&bkYS%Bx z+9t~_E=HU1`0yNxoS1LZqY@vDx{vz8H(!adsgjSN%F)>_P96Dm8Bs0TSZwf$SdW$> zh#Iw!kdgOjAf}dm&^(5OWaMBv8AM!oGG=&ejPVK9hu9HI1%Ap)qj&?OTGtrn3#ae)19u6$NVGKQ{-t-#(2y;3D2;Y zbUs3g3$ezDzNlTDSxTpVNDQq_7M^6L>FnxLHdtTX*X&lXqu-k^4d8HFQmibvNEXP4 z@$l8McXh6kH!j&kVFbOg5}sQFOT1uL^2x=SgSQF~zbP|s-y(cM;T!7kYG+@|c;A(s zTdUWyo4U5v`4D9(NWtsUInhrIBInyNrv3rdqVh?`C%)M;62aX3^Z?$VWJ3P6sP9zO z&pi=m@bIuKe`lYF2&XmZwtR3OmmosIr6V#Amvhz5uhsZRuZd(<-rifDd=C#I=*#$~ z-}Z-(!K=IiRQWHYbss3#|FgVl8ZsjV|A9W`oi+3km`x1eb_)?F0GcogEdBpOPlb998#*CP1 zd8u3gK;S4^{mf?H&?l`CdYuI*cRImgv+1&ACB?IzsZya~^|mu@eCL*LhJ)jQn;Zr2 z_sF!u)_C5t=Z?ctQOd0}M(Vpw*b+xdWaH=z5 zW{hs>jVo43CX@`T!?zXGsk+?6y^pEX3DOl7e=)pu=|zolCQi+;XFxD0^+j`jbr>Sb z|MM4TJ7v&Wnj@l1ErHeyhhz{dVLYmZ!#CPv_w@ zUMhF0lvVrkC7;+VEQ%VmL->BHBo97H5REp?M1G6Q5(&)*m!1wGyNI zYt=5NoAq#W?#0_4i`NQ3w~-*C5ZKGDWi+ZuP;g`(={QQ=#3x>^x=QxMvlxi>%%X>A zt<_lGFRDy1!SFMY^`LTW5nm-9ky=|NS+qQVr7F_G?rSdLtix%N;8|;h^hlH@!jj(~ zId9r45E#!hUw=n(@zZn5UgQRm|G1srD^7^4>xjzu#})@a0> zc85r~L}op^oSWC|=#4MeO+dX7nb2O^jhMxEvo^bq1s81u|GH09S|p$p9++UHU4 zGAFyR@_p<-PwB?9YJ!21GJofM{jpN~2Jm{caa@H!lgJEQxK3hTagl{C81RtmE zUUSv>&?Im=oqhjK%^y+8xvF?%o9{RBDT#`T+4uQn4VD23&ITQ8hgZ0qtdW0E;Mh#= zOZN4Q*5jx>IYweV<&Y{KKUPQK)^J%dS%s|(bpyB|D_MIK#=KuX-D9+s99Li1cX#A? ztUU&@OQKJ#a0XXC5M{Tj7nuQs;@I#FlXU+)3L&4)RY&HY%X2>0*fGN%vZVV{YlNW? zP7ozTK>^cw%fH+mu+tYWB#QORmbnuW=5TE-xUQH^|IT;IW%^<`m6B(1e^aE_acGSHXJa3))(GOKUcFY03BD=ef%Q79CZ^yctd6V~P)wkjoVk(;?M3>mSoih%W#qDl+CW@Yf6EP%~>J|L878etbjZWVxGaEJ&;2}%R z+KYiaqGXx$Prja3wJ?2usP}yR@!xe+gmN=B7pCepJ&F;7tNJmn_a=yjI6%vrrZ=$W z^OyuT+A0@|N+|Xfd2`&Y2*r#Is(<@*00IJwdv)rEJk1lVxrYqxnf|5lPll^!C4~j} zl5Kyaq|mkVh=z4olE5ZxLh@*<9{7TQJ2!W<*!GbK7W?CWIZr1J(hjEHv0IFdV5 zrUE8)|BH3~nd;?003h>0;kvs2CVo>8?uNPddTyY2@$Z^;0Uk^?YHA4*=78^qyb59+ zAM_zP`bC`z3JMIy5v863WyaC+`y3UkUrNsuaMsX9eyl7oyUPy!%ZBH^b@xy8?_H!N zQIh~xCV-d$R>6!_%EGYO#RVxDH^!l7yhl%ME~+ymRntyX-c}NPN5?fn4DaY=JaKJt-&I# z{ykay8Z0C0#M?y+vmwhiqOcNB(EW#n1a<=Qc*{ z0rs_Cel*rbg94WoD8=ClE#HTWZ(WSgI!aagkbYao@=J?i zV&4^nwWBRv*Bd(K0wB{UY&OQbgF4{UMkzM5T9WQ~q!BY>To6W1$Cs5e-$CePE8#gl zGg~N_vM$_j;?pxmi*;JUOO#m4ws+7dr=GpbF+9p2R%?69ZhMg4k|Ip!$5|v#?I*nv z$JwQQX(kN|7GXo?lC;z7?P5(M*58iTeRi9p6-X@L1rMQCib^+R4SRC4usY}i8&&ys zXD*n>pcj;ySt+3uGRTH04XXHR_QCuzNH_~t;bJW}wjBI%yru92K!2V(S9wv+m$Z1# zVT8O#oX2%0by`>LzmC?vxL=8n`q`{uPTp1-EsV(l3=w`P_v4SuY>g?yTC4Bi`by}6 z{{vEZPS{pX{i#E^OE5TG@Wtv5F|mz6vplHS9WUnB>yQ!go)kmnlrEuUHIEw|7ri+B z1==ijQ30N{kKz!7mq*sY9bP@NxTWl0a@iw!yZJmg1;p4U_2YT=#ds0b+==OVSYUBw zXwO1@XI)m`a7$7i+u)ac2KR~dynPmQY`;L~jf5Bxm7$YqD~`P#6x_4y^NXB_oLyY_ zI6;s~iL)!_9w=I1)89i4tb8d2xUDv+OV?>>xAg-c2Hk84r}O@ze~ankzGEzR^Yu98 z*GrUyr&3|<)#z4@w9|jp0;uYcA-5OHbfub?D>kaYY*nVV? zTE` zY|o-&xZ+yxJ3jC4qnQ|*ae0m6AfGqF1xd`2<#s$#;e{I+Rs z)K~}?IQ6!C zdd&A+e9o!$6Sb)mx0lcs(XgPzt9X|QP2NmK-OiE$y6-@rsW5TS+EpmE%l%Z+7ydC7 zWE|G&I2$$D$1xcK+*zX#F+V*5k^KGR43$3GRhu#X*}kc1-Lg=vv~G(hiYrDT!uaS?e0Hn-_X5$pwiR0iK#~2niSo& z7#T}+5h8H?#aKd%s`Fk`4E<&>5m<%CjP7Oa;K%!m1H^gURITa7!BUbx#$uCzv6#UP zI3gvL=T2JrPD0`G_$W3Q=k~j!KoC~%N>j(fpqK!1Kk*aBcBe1>W>xT536bL41tx>- z7xABPsnw>L_i}nU-M&Wk>0WM+iT5qP{#!A5CVv-3&L||I-GPiP3Ow`#>EL02_GMmbu#$ik~UpwN){`O1b&A=V&SE2?}&D9fq zqNO* z-P=pR>5C-oL~*EBNCfDNKXLekKacz$*NffkN|PzMwA$IIbxEmm7HvWxE0L;SAK(?mE!)UVI@uTHal>-V*srnT~+TA7*> z0FVD?Iiezi5u3!P#GejRm^7cwMOigg2Z#tOXLN^ld=d~8iu)C;v2W(BYqL-4!$8}X zzdjjTyt`PGoo?DSUJ~(kV>GW}cGOl~du#72!Toy|=hmJ@imysM;<<8qd@1(%BF{>^ zujo28mQC8aG&~1F3`-UTnT$@PA;NRQ%^@Dnj@j?dOm55HODH5@x}+tnuKF*2m#;>g zn=_-2^*0KM$X5l%v2^-oM&rjV4G?9vH_KqyloUezF}QzI93s=PrPY2wwEv-Nt0`Zo z3x16R4rNf%e>?7UG08nNG3xm)n^EnDp2SwJ@MI-nN{fdIuJa7-LD=M+Y;UI;Pn#Z_ zr>)^qwxQwDRi_|mrm3SBL;upYOjKB&-J9~7KbI{bI5=ldEWu%coi)%-D^S#Bt>^Y* z+|f;y_4R?Ip5i*5|9NRa%hbELR-{WEJ#BWL;8EL920B@V>1XceAt`j$<9jRfvwW~s zx>oFx2t^7zB9;4E^moJ$Jy~d|fR3QZL&H}NgK2pgfgxy0=&T^ui@U_T`TWuRRw`_hc_&+V1Pi1g0A;X?A3QuuwwM31R%Xbe)X0q$bn$j^7JzWx4!9Ol4iY~pR|G!nmrKbXe!yD zkVIBGranS4SoymxZo_!zu$zj2#7(ns)AO(_pQGL9f#=isS66S$I_6cu>K&{h1#E;2 z4qA%f+eL;M_nWtP)ZmFRwVOY;C&a@VLj6UL^)mAOjSUICN#_o8+Za|}6Ro@7s_AC&lIpcg{ zW>znga-G?gTh*HzNllCbnui^P(z~K8CfK2&O$$oiE+yb=mD!oH@py?POxnLTME(c! z=RWGcD-p8t3lrEp4)dx-UtP4m^}6&Vqz?Y z*dS#iQ&5oKelhw_MfuS;(kgCJN_j-_h=^)JhWS-x+)C>HlG3u#jx-6$A|Z0Cv{q+c z<_d28HId>6wB*#`F?*B+b_L1sqNP1An6e4-N>d`zJ~l9I7{$n{s-)%S8ZMc2wT0C* zsdDkPR!(%Fmag&vHT#{#b9qGOQqL&E^ep*K)p!Ea;a&gF9*fC-N&(eyKp?U}q-XZ5 zYLK&@2T~N~=E%6Gn&smR2g_DX<-#)2eaqDJqwvUOWJ|Z{5{_JOhsCJl+n&BQOTK7Q z)!FWHs*o@@kuzUdk*RuUL_+C6U-x`-BzQ+>rrsFK{<@{mO7ob{PZjq0+t$6yGaJBW zW0=H%lV~NhrdK23wN23*C5{^O6;iS*)A_(r!Kd=5M2GJt=3B?+u54UdVwWS;oSY#` zBrSO0SEmqe^yRlAabifZCmp16F1zk1>z#uavluc*$Po3|va zyE^;nblv5Z&}DEc%l1Cqh@S1AbANx`O?@LcBH3ecw3{z9QM34hsL)Jn)6}b>(>;mL zq=`jCZZLJbLpWiVoy!5|Y3fO#_qLjQh1z`kFnfN9#s=1tBcY#bc3b4qt!6VS=mz$N z<9O)B40=$WQiEvEUoe0oyHdP6DG3EJ#9!Ka_jVU>;=)(xALN>MQJaaWQT-RX24Q4} z!HDoI$waxQ&(H76@y7)@ALobvy?9(*`frviAMeG%4n(E2ALq4+3Ut(0XisiM zs{vtodA{+zdBrz`o*LSW`%aY=(#*01nLUn<-wGcyHZ zH#FJ$+WuVB)0_SM?OcWQ-MN_GS}`Z57a=J+YF9ntDfhs{H?njX8=W)Zqn79=XMH;; z#F&OfQIgnP=JOcJ?D#q-_$<=V6-Uhj7vA05kgT^p?L~$e1E<^R&Y@Rxes>%B6u*=z zjYPG+g9@6U9xdz@hE?$d(3kiq9q1 zlDdbPJ&_KBZEa7niWk0c8x28+cR&jyJXUhgZZ>xG>uj!r@9dAv@3NI@vX;Oa*Ng|^ z=-kL`h&;6pGQG9qC5v;{uLUA8&03>7?LHy~ zD>-SA$fGPt9>c-+&xZBHHn;>lofT&Mx!eoJ9FT^$WZBdIYx3>c2aE=Ux}8Wxa>a-y;Zv)c3Mod?$yne8&7t`jO??&Opbz zj`4}Q+c_PwPBHnFd6(h~GHiqtCKi$=Kr0gbr`4JBw|f5!5lImlA%HBCWXnA=-un3>X!Ul^0v>UVT6{>P?b0E=)d_ao|e;XS=*TfL|$&EXuql64!mVNVs=M~|@0D)-&TuksLKa-G{#~5PMxt=d|A&Lno z9d{p6xwEBJk+~LMBm;$cW|i-HTb++7AUoD7MHa1f+ESA}`^KYrAAp&R@6T@osBS*l=Rn(!q0~U zUsuB%B(&#EiuJ1$8D5SU5#Z38pGw0rAl=vf!LR-Z0EHN+u35mGwrRgO3lF&W^D)`I zu4%Fm#b3sQc8lM;4b%3Rdp-YKDFxcSsADpq#H+lz@&Ug^kKcU6HJGDOU50(613c7o zR>*xE3&l7dPkkCOl(=hL>CDAsU$caF;^Z6BRO!o<(7wGVz?A_1+k5WPkT_>+MMU)$ zpbU=1!a}3OM+fyliH;?Qk z_qLH~_bo0G5S&`aEFTx|-&`ggja2e|HtlFS!iQeaq8&x<-+~`QZ$`$3pY`BX<;uOc zhRfKWWaQ@sJ*g$XdG|7UO+GkW4)?$Y@&16KDgIAN$|1Oo+>U2{WuWAj{f*{pzLnn* zUDC

vTKERiyg|!gKCcp81hn33Va$$6n%u_{mL9UKka+pR-!Z+lZ8j&|sBXovznG zGIah9gE-AokIwxQ054z9mxy<)G%%8iT|2_#vgvDq=L6{Rc8lI#NyDFLfxj90#ppdS zSivvsWLSN{3Vpw3PcNq&Mco4p>A4sHfv4WhI!OkY=jt-*oZOVk@yV(W{MzTE^tc<&Ca?UuIwG;iQ__(}bR&83Fuz2UZwx)y)@2bZhGb8+nx zOO9bV2vTOJkLY_C7hOOSCXwCpYPI>8Z{z~ly?D!rc&b|w#byGO)~vtB(NnWlj*;|q zb}$9x zm#=Xg_*XEn$!R1zzigAvw_0YYG}U|v_i*_Z|ND5;>tz>bRz{E*KWWU2F!||0s5JkF z2w^g1@p<&n&G={r&O2^tZsbmw)vZ+2QP;4z?ZhQs0I{ca_Gz(>POMNJEQlk);dG1# z!w|jU5O(Zw;HN*|9{FkRUFjpx2dbl2zkuy#8 zH_3FS-HpactCW#3nqA_WlAtg*OR)cY3`OK^xn5^><_Lw&4RPFn7Z)e4WXtlbVOt93 zJUtIy#TEEpAPv>vXF+d{_xwqgkmX82FC2e{9_*RvxX}_C71`&jmc2jo&t81BlDL4@ zte>2j{o_I~NcD8o=q7=Nk~xyMvIXQg`I62XdPae80I+7}Cn@0NE%jgM#(R^)7*>j7 zjS{nNok_g@^RZPEr6LI?2bnPmHjS`4gC*YMVPi)-n>rW6uKwkCEU)2IDue)5dD`3C zM|l}o+mrm@@eLov4r8t8HntC%QW;Nh&bNL}E;HlT6v`S`IM&IoYH1r28JJ646%%#U z?@U{j$zD`E=Tn=TpeN`7Z3#hNBZ#{=-NMC@@$91nnqd~un+0h2^1Xogq41v|U*8n3 zj$CTvC>t0kPc{-RM!kwsOV7og_ib#u*KdGbPFXBJQ}gySVTj8&^aLAH?>aE@JriI= zf0u8wq&O-!%S1JgGKmOK9dHk>;`s?HY%)FVIdLaJtj>*@b~|KD6z^uIx^f=^>$#)V zE9f(F=GOxL_3pRW%Q%PqdP59^-1{}tRWxU6toMr)#0}Jr#h6M<<=@zc$CS#%m>L9``+Ou_@=8J*sbTYhOSa9y04{kv0r&{xJU5SwwP zPS@&sn|=4ZcAl>EzU9F|_M1-uyVcz@+gypk2=2>9->D{@xPai%agP^zy<-V8* z{o3BuVf7Htt)%EbHVrjfTBFjQ9P5}=@%g%N@a)d6C$41Gy8@HI3Y)u65eo~ln#vFn zRGk4{P>^Cic5Ml-yU-8fxA?PcQI zQ=E)(1>CEBpd-NESXn@|&1XKZ?1iBB>>YFV?>D0;)XTH)c1!o%FLU^I(>s{PuOvX* zkjuu6ouX>duncr`sJiJ}MkmmUW?SzLi#_Ne@NJDViXP8+Z+@I;!02E&GLx%OHrd)1 zcmeESriQ`QIcoO|+(`w$7#5&AV00p&&4<_}kdcke-qnC&;g5eqaB_{V@4SbBhM-^y z2`RMHD1&nxj_FzZ3Jp0=K>7p?Kn_NtGl9chSqrx|3(lt5;Kcwq9&z{F1(Bq_$lq~( zzjROI?v1;-g2-Yk`7Xj*K%r&=g4uK##EvcmYPh#sRh16wdfjC&Cin>+GzI1Bd}Rjm zxYEF})YF==L9d3KbsUm$K>N>Nu6KgtP(nMt8P|U|)6Au&6s+P_8aU#)6ZmC5)2!cg zygcW)mKtbt0{9vM%bA%8R4A)vl`wo^o72ZnS*0!|?WB=4l~N1?jRhQl&Y}j#8I{Mf+LND3)zrc*Zb`<>PYp)uEM)5*^D{jzFjk#>HGjLzFx+B^x}2fO5cm-wn# zm1n>?qf%yU4>&p_ogQt>oT`6Yv)!>j81CeI+B2QM3153_ezg|ixU3*?%aPR)9e1uW zc~iSn{ns?HLlbEng`0q}?G6;@Vn6M!kxF3q?3=0C*2$jkLoxudVBF-Ym~i3zT14*c z{I(et85NIuK zfifKLr+g%*n#H%Tu4Q%u&aemHREmSu-`qC$+JOV=XS0m{UD)g9Q-aP*s_7*DZj zQMpFR5~CwMC!+ojL5-oV>lqn=8hrK+at@`a!0`1nDD@;}Z5iQR7jib|XtSJmb?s$Ta6hN$zDonhVh!X8(U z%ej)`$=L7hh~DxYLfgytI$gDL2f#QUfp7tM`CAJ7asS?Z3zHZ7$Dp65a0CBjV+GM@ z*!Zg!@EODAk<`3%In>Qm>Jk+FDlO82gUM|u);rKFrrKG}cd+eCS}yZc zWmlH5V-$st+8uJ(b@lC`P+V722|Y!9$dMme2K**2$R5mMtu)4@8tGevsU5`~)^@4F zfJM>2rVKq&gs_%l^I*+$rFNtNZ+4bzwFIoMS?AEK(o zVz~;77mdy?i~>{cobweTZtaJxxe)0L5D*D@;*C@)T*Xy42ZGiD#)-}SyK9yt20H~y zYMbx&h>e8GvxYu)hZje}@LYSx%UEvC=z8Rzs2XWzygw$kCDQfnq0ejl`a$i|8@7_e z0^DZK8En^T_))1#*zAr?ybaHq()Q< ztBDgEG>?v)b9#p?YD!B>-6ec?G(TM!RGZTd6oh}?bKa0eX|{@(Hchga{KGoVKA!qy z%c=aEX=VqH%2d|>)82JPHI=PV9F?XSK*dS`siFcRB3L4#1PDb2LUBTg7y&_w6qOP{ zK|lm4ic~{K6s1TD2_-N{k4O_DQlv{TgisBEWNw1~@Xd49dhgAzxBPq8UEjL*+NJ+ni+D`P<^Qm%~1t!(p*3XRY5 zI(aD$`CUQU1+Gw-mB;~w__7Qq`UOn6rDST|>yN%cyK{H^?s%#|d?K*Io6pcr*lfX_ zaO;>$PQC(W)7{pXll<2Tx_2LtD7@ibIN^RR__!rTtF`p=6P~{HlhK^UAKW$wkop z1&V{oN^{#~pI$;sjn|No1F{c1T#Ll8H01$M6ewUX-|z>J2o0;Tv@A`ECg7~AqTM8H zJhbd)Ul&@h)!CQtN;#DxQcTH0UAkCMNr5%Q0Hcz><&i4Wq-9gLOr+G3D9h&REsq}b z5jPBuqf8k{_B8U9TT14MVy1;qB^!7;%hL~2$D|j>`wPzWy(8Tn>zHG>{Hk&Wt*I~> zHPetwT;pjg_ZJj(LMCr7Rd~#vpEaW;N`xsujkFZS>Rv^-s~$>z)V1>%VNOeVUBmL= zd2;8!*RVWL)+HW{oBPU_>7&kj+jY{yAA{7xswUHhN7sTMLxtmyj=&49Kk*R&fu&`~ z$t{lHbbKG5j`Qz>iAOPaKG}K{~C*$nwZ8>tKiHS z?HvZlyw3h&M6Wmg%+Q^{CPdN#E$P z&IA8BJ+@u7D(s)8$C}Vlf@+|>`UU6e?sGlMYwLLp#y6k%E5s9b5Cz4E`m>y~4JnqNr zI)$@Cct6G^m!-bZFnitm$xdkp2rZRnSxnh9-?upb03SWFCi@|{tR^ zBkuQ1SD)T{e!UzTaHd*%IZAl0*YqgN?1@<{(z%Qef&=)j#oAsrCFB6{>>1Hm=3YkI zn|NtD(ov1Q@j2iNa57(IWt<}N0=vw`qu?~`6;a8&;n1$erPlmE+VN z2VbO`hCap7VAn=yW0|UUN|KYd+VXp|A^~tE*#)0{0ke&670AC~CA&Url0HAlVdLV~ z{e!mH9_-v3DLgLJ0|0`1G^kr=eeHkyG@@pUDlI?l*ScVI24Xu`eP`IBt92SHlA_|& z{nESZT*w0LGg$4~^vrmd_T~l3sH%-Af{x;zfVZ6q;?`Sqym9_^&yLR+)q`owvzd0# z@vFRfq|)mKpA(?-jP+D}S%vsi5s8t~d=eNl(qVzW~E zuiqCXY1^soZY|Sl8`RyjF;wvj7B!e9um1X-V9e;OTSFhITu~PD9NnMAQjo=hiEx$Jry~gWnFDRaX`J0 z*M$#wQirdh#6?=mCgRv7fIy+N;Ru*7=vC7sj{s-80Q_XkH7YwM)Y2rJ`T@NK)9 zQ3BkX7s{BKgD>cm_}FkO=GHp7F>-oqc#s`>8W(XLY)zZBJL#c-$i_QrvIw z6(muWNQR`RCHj8Yit3rg+VfBPkc#jjiL9#WG+OGzPg`2_lGm)|5%_|uW<-+yN7IFPV7gxb!(Z)pXIby_P;A)twg$5YMM464 z+9SMxcws9$CM*cRLuJ^!DaG8IoD_7_Y&tKY0wN{J86DVmbD{ zyo9yoTrtnU`~Zu~qU~lr5H09-lL3IC6@frh^n=9C5HUs1w^DDUUh-bd`(m5IGvc%P zgxs_?h2s6I`S+6<74Ry>j98ljDDbRd;J5znfC|$9pcD3%JI}Kf@af-vpP%sWrvd`% z98>Lv6CmASZr=VL@>i1#4Y+AM)$wf-BsRd}&uqX=iUctD0QK(5VLoL)$arnP%DjIE zi&x2*gj|V~Ln+Zy5MS$J;3e@T_3eI)$}Tr2yTf(bVtI&nM^INPdGTll`}BCd%+6Ph z=G5Zb4Jzi4Z%QiS7@o8kFnK#J^X*Mz>#%-c72|-DJN*&y8mPrjwog4K;R9A}Mdg5r z9^tBsAQK55bKc5vlB%{`Qd*ceS}YU?=2cuha2KNHM2 zLyY4E3}cE8>;|5{Mu&(+Ho`{an5-;C*_J}JfB*9)(=Lj5zikfz$--_?>DsDU*!T!ih5Hv1p)GMq1k6A{AjTzq-+m2wfS-%t5X#G;ViZI5g8d>@Xs zFVcs@-I7_rZYxC&G!+>X(MXXKq7(5SM>in)=Q#akm=vpDRw}f3lF%s7WQdaRS6OdC z(_oBFRu`hgUt%eS8ps=(Q7@4lT?Gd59Y}nhC==DKviHC&ktbXYzWZ*QTmI51-i2(@ zyKq;|6rC|D*Cn9AmG{)VNrS41IYcds=`D2Hcz#fs74u+DDl&Gisb-q^`5xkAi6teZ zkQb+u75v<_O}P<^dYav0KmAzBBo9b*GHgOoQ}BflRMaZP6S;D0G?Ud&4dIKC9J*k$ zse-otamcG0t}%@VwjR|7j2~|(r5}7&5jSuERAPpv&+;9w=Lh(;JjFSf0$jWmoi(c7 z0l&q?Z(=2NxV5Pv??yC>Zu)@RpVocFU^TjSE1QPYzscVFvUA?>?IF^MJu=6?P zl*3Q)FRvoK)BZ;w8^08dw5^9`z{Gc+Qc z+e*tK7eO!W{I32R;je_og=NJ5+OGUg$dp$wM+iySfC2tJZXAaE>w@hA%P(byfq2wX zIU6rSdE^BWP)@rip{yJfyGN=l7)jZY3)ds>J;*(FYnt{Q-tlPB+qaBT*bb4`)gDHY zJ~oYJtD8IT@(>ngpAEJKD!*M~LlqL`kFgfxmro_4cyX$f$yKGP*}yNvW35P|&nG`= zePKBdtLy2JsnB|~GhWT%-CZIsr`3{$#w5BldNADTW-p$qXOWIqnj^MUH!$A*;O}^y zUMRHYeYv}h()#&ztTma%WKPEhNw(8)E};V)Dn&W=n%eF1>TS*dbyPqQ-TzD@eI_8! zJKgUM#7Q-gRWHB5@{AaaAZw|f?scqK5K8OGE&b}K@vC-jo37zQFAVN6h-6_sKf&)3 zML{llkpfd#33Z-_Qkh&65Nb&@$tD&<;Fv5 zopJ5Ufg5`_t!_sx*=B|Rn3jL+e*O(4+Awejps{qy2ip4?Fhn3UU2KL*fZ|(D zalm1K{vdu?Xvf|>;umv*GjCMxNm%Rg>H)TQO1fUGR<%`$S}m~_4h^#~X0gkvP;U8$ zfs-a9>tMI<4A{k0=8C&x({Iqb)an8FIEZbRj)RaHa2#7Mag4@X>ruY|qqj>}0y zOVu1Y@pzweaBZl`IxETpI_V3NB9g8b<5xM^YS^GQ`mNk92-t|cI%hc@_E%;Uk|NPJ zyTadpddGm_qF?nRNE8n$pg<`%+;;FQAsZ~zEjyifHU`9IKw%Dh576ugz@tE8vBn}& z41pX)rbtO`jp^;VyS+)&gyWD^bhHp&e~%WkozMU9G0|WR-mYBh2nDn`*5LOu&PjE0 zv*pZo78eHg3U-S+w$npPKuq|E^fB1bNP}^F;})Nl`5KMD60UPWsz_W%3$rE!S^QH; zmN$!Cq$cHdMt7P4Tl&zQ>M3Q;0$P`r%qtGf6RNK?*lEC)H|(y_2mCpX#AHnJN+Q#U;=P zH5j%*7xP8z8NQqtV{^sf?kR}DuSX|hMSfpn)5Y#flap)VuZ@RZEOu5o~gu5O$x~9NIS;-h*1D>&9tn+CP8Z)$iZ=f`K z!jz!up|JkIt71I;!f`}x@IoMHbQ3NUH=_I}HrK*KqCtd)14-^eXczx_P-F|7&Du1i z&c!WSi&i)r9xk?NXoh}*M_F2%u{@#?1*XB#Ds+5OYUU|TPmr-g67Gk!CP`au1hPvR zwhxaU%6a)K9B!1{zCQ`V_|e+r2Yt@RMS;;#q4ICpS9QMPd6bx(Xy=AE4Sj9exGhi!Ud`>%mhMlc_G zvT9eUpMc~~Y2-w`wK$gq!|MJB|Ixq=NGUiVdwvDFsPSo2T$$SuB$^jhMpZzw5^PHa z6_v{Hk$o9l)hQf?lRYJM3$vPhmzB=RutsOs%NIinsLTb}*5CLLl+8;dB$N=pRPVkH zd*dG^jWiq9B}cS9d}6N%Ic2lDItM5}?=iO8k$O4jBE`akgDl9%#Ps=HuZ-o``4olg zp?w{fb8x_{fH`&Mb|=6Y$2ck?8dvm~brG>_q>%vwC*7~k2_bZx+b^#&6+aqtxP-~? zM)@_4L*sZRia<&iZYs_sXlC_k^aA$|% zczQU8r}+pzdc%?`+_tJ6I9I9k!UEO8P|vA%qy04kpU&7Vp|JYjG=Oya&Zl0`Vruc5 z&;F1EkZ~fs$kfK5c|wnYg>ycz47q_XCrrWpBv?*=agZSSYvv5QX{=5ch+W zLck{h3=m<^;UIQIg=fg-Duqw!U<@rXWvWJq;Gmy=5|ZqczDvE=ABva3px>QPA%QH@ zxqh4|4qfoY<`3WpyhB2-5Bpe4SVe_eZ{Maipovf7AF3CZtQTY+wo&6 z+7}~Ji%wJ{Gi{$yUu*G`o@eQ>85P`E-+Dy|SM?Ig@b;2(>J{xshfu0bX@YuizFq^AONa+P%FKq2z$1F#zbcI;?Lfa9f{fgQmlz}q#&`i2qb5RxczVlpqJpa!()S=WP^_k3JR&nGeP@wo2vkF|NZ3?4lm41t1^+8t!m_3JZ+90UqOHmiiyC7p z^2utTiHs&k3nWDhw-MpWMG7!P`=T{sVBgQy&%R)yhM>UvmXjSSkJ;8!3~jC7f(chO zytBsx)z;$rI$XhjOOL1Ci|~+X@k6y$FalzFX-UB@MoRpycE(2gc_HEezH6$xeZ8rDIu|PoSBq`m|>2_}YebBI*LPF|wz9sRv1Z zb|`yb7tRm|^;b4)T&jlgarvovFDaSu^qsDG&xMWDa9PLsX%!I)dj%XEkVUVSL zVKq!X$HdTq>{|TgXjjL5F}q(K2Z4}mR+ef6y{A=rLKQ;VvOiCCG%lYPxCoa?Gu`H!!6Q`!ad)M-3Vfh?jlmEPCDEWSY!`_b71IA!f?~?X z@KbZrjiY&Q^7|l(YJYt)b^Hco)#0x3cE#uqDnTRa`rRjmh$N4lh|Vb*7^EvfEypDv zr{{27xl2w?>)!5O{ow+x#f&Tf-XWnYQjs=AzHp&+q%!56q?F@6cTt^=#duN|_I^ny z8{(RN5rZ66I9x1z+sKqG$y{zR+t9PDzLaY#R`w59%!_ROaPUaPFSob|_z#x4XPHrH z&oZkx?sWQO(+e4}^R=cQq z++6R#6Qld*o5R5^Zuhjv0v--rDR$)9Mu2t@%VRscMl?T9aBbweKSOk&JBs*r1*nm& zFU=$$iEILRHeW!u@*!Y+u5qmcoi71gU9Ak~NCfXt+VeM0cXqd75+Q}Y6RfpL^SPZE z1_C1f<`w{~#P9GwmsfF02Y=51XsEEdNwt114i)mi9+Q6i!u4M4B6b-GNApT>o7_4r z5#GJizn(#=ST792R%RiQSxhLgB^IN>lcc>Sgp$z&eZl0!dOaE=f=BGhdFb0`Lwxim zj2NQsj*c-B@vY*CTo!!+xB;HxPIQdV?l;8}8&A!%6^2v^BSc{(e8xa1G>@5;_KzX$ z+YFE&mg8qJ$6*{p(Kwm1GEni^?5L)#XTwvCbg~65MdPA+$Om<($GaXjXJ74=ps9xg z>R^Js8!!!%xe;j|$xxiR&+?oQ9s6c}2)z)8u03nayvu$lP1x#(w4*gWV#72X=XO)N z^ElGsevKDMK-zjE9yEfy=%G!yg+n+A6-{9<5#_6gw#dihMik6dDBUif)6oKFW~&jZ zcSVoyRl*()f3ZQ|vVdG_y}xa+qcd0MVZ60hyv9+5dj8ndwXNiB`*AbLG}R?EA+con z@gX#$PJL92?URd<_Qy~64SU1QQOUe{g9w~nkT7brytxl`*^Dk{!}Iy(z$I$va#*?TAE*%C|Hj@oOdR8kG1#c_I6F22X$SToL>i-^*bq-M%!eX zTFq)u^79joXN-Q_e5njutzMd!S)zL=CcU9CLWEuEZ7coN>M@cioiFFv-|?FbSZ5(~H?k_P%_@*tu7t zgTQ2E#2iJ84!RD?3XhL?sB?Prd7a6M)y!;ah5X}6D<%t$YE}F8@?dY^-Ij3vd;J?S z82SeZzF#1m3fBPYpPlu;hXIGxrc*Q|dtxO*reUfQ9y((+90*}jFe+}_su)|z@$pfz z>Pn|30S#^;x*J|t7|`jr4CTF!pDKX25$cM{MH&OZr6h;hsglh5A2~$3AbjPf_Jpu| z*{65F^7iO-s9#)`f5=Oq?Jp*eJRM9Llv!t*a?m^wT=Q(m#0iwp&V%W#G8|DQl=%2J z;?Io5_DUQtUWq(WGAE4(o%10feF{!n+#4J+rBw-xhyD`6Nv0cWMRrh|JnR)7GnKd= z_oIQHq;P`o4)2q>z&AdgjS(`{i`c$XFyRL#B)=k+K|SwuFmyN$fC1ag;^^$U zHVXHoy@!mf@^WVkR-U2u1T6awhl};5OAGt!xa9Y-QeAh<7B;KzDub(ssf$yJzV#nJ zqzR;E-1lV%U+yP)7AWS*;M9c)!#=yOChwmcu>F=n8@Hq8;t7MqrLO zcUyHtR0{f9#o!vym7nxuY){Rag(@!`XIG;4*gU!6?B1aEEecUag2t+l_W?F@o=yjf z%c~9D3Nl7@y-%RWYyE`>8YuRY$NdLM6ZmXyTX_Rh%XfMqgARXIA>!{y0W?(1Fz`2m z1G**0@vJ#~7>8izZFHx?=~3uf)vzYs7zzcGK>0`(q+4E*8dR~OBl=-@l7JWlr(8(+ zRN{B4MaWAePH&d>f6m}S{Om|QuFsKurA%<4hv{EXWxF?tF1biBH|x=#F8?KQioTHH z^9K&`l*Wk9!Q&NjBa8Jf%kLiYY;&)IHF8Upa_Q5oj&!UTQrH2uGYv3L&5tY_QS(M4 zY2p`s97Z1!fAN*l9s%4QX`8%QEY#vp4&cTAu-hV;jN1Clc7s|z>2LCe zR`Cr?_&3+7EJSP?eOPmLi&!2l&)Ipbgsm4^Gp_-~@_-)(dRH*G7~Z#!VEc>kwM2r!Ro07y{}xW{()hUUfkCtdKn>lFwH zSRk6;$!ADxcVcK>xW5}W@J3;Pmb(NiQcxdY>6gvPm7N$st%!0zZXi=2{w41+{6ryi z0oJS`VZUt~A}#R6F9YyAg#Slqd;s3d4(ES57U{xT3H<~1XeIn0>|gK>X8oQ^1_gqB z(eF;$!K=YL>d^UjK8kG6yfVMO(JEztRbbCp1GH-3?(zZA)>a|2a>k&E#|bJr7nkoi zL|LR6jm*s#@kpG9T=d?)!Gz_{_9`{`NOB!~MH=B*#1sjVx(rgUppy!_ub zh)CkD%z~Fj2_r)Vc#G3^2$CqfUz>3{IXF`|+1gS~x)?x9dYtEuoUex{1(jjjv?PVA zx7-u!lRXagX5#1_w6o|NbU0+pea4x+>%jDW<$@}tj}xq_?0s^EgrX=&1DMvkfjM8= z3qv&ustcH59h`TA&^m1mw1e$fY(^7$GQ(XuvqHK?6+B(y3CSGZtvT@&JZ&3%ga1=^ z5~PcwJBN0c?FCoK^0M;P8diE^2yy8s7`4-2@>G;-acTX^Mn9h$Q~qDZuox!T)hUh^=VWGfMGYw%77 z9yDjk6Qd)F+%Ov5^0KD#+1%^fR1a|5PR1HJly*RdaJ5j^Ru4eRW})_aC~env)&wmwh8j3^F9F zJR9JRYz*HsQEHOAEb}_!7B-zV0&Q(a)|%71WRCS)SGI-iGEWDNjR*IkSWRBDaJ~gW z%}47FS(Z1zC(#%j>^~*(uJLIsH-rkan}gg0Tg!)z|}8Qadv_c~tR_sp&7be-MAWU+x| zF$pTZnkz$I(yBpY2nWYn`*!_2o}tWsZ~~o=LeeBpU$VjCu5_wn>>YFuX@J_wMX_^E z-gohr5$w}t#VoQ6nMHaeQ8e*`?xU>Tu%>{n8BWcg^d{MiDO>&I9Vt@ za9NTR8E&Sswd$z~YIU+q{m^#S6_T{Ni-Wkm;ym7ylw_MN3_xX^Mj4WiR#??6%YX z6q~h(D93?bM>JchDXhbO&xT{r@Is*Qero{JJ70tCl#+ZVjyhE>{~g(F2cbi)@-1IF zK@=e(u`%y4>7DzxqHN)YWqjroPA)uqk?vsCqswFNIS0P{>+l;Pe`BW`9~3yhkxz$2&>inu_^mPmE`4 z8S}JF&8S@i;k#8th&P$}=CZ2xqOh-4>2$lrMx*AArySB7=|@lWdZs>%?r30=-WDYP znnKn`#~A^ z0fE`y!4w!aJ!8ZvEtD3ahGlzMs@Kg<+o-t2h&;tG^LVV6g`1PYuzDC_(UWk-PpuxhRBklT=EhONsX zH~F!66rd-p!^96CAHy7iP>)Rc?;ty za=Sxdwf^)EV9S!bK=tM><5HoRv0TxT=#eleK#r3vtpRCOOUW8 z%{8oZa5!5OB(|?q3FRLy;2CuO4lQ~q!GwELeI!fc!iOu9v`mLY$W3jLlD!Y7($J9n zS3uV5x36ccSOpz9ba0_p>o?PlrpA^P=i3SQo0dOD)x#_;Ulq3@1oiZ+n4C!lcmz?U zy>0<$)|Roy(|qE{{ognlB4f|I79A-6-KcJQyb;yaIw)Ftu#>8e&yqAc@}#3@Xg}0N zjYD9{kWnc>(sSwXQFO;F-q(J9IL)t#_?0iJGOgkhw3;L+*i(%=X(6}N)}F=pnlC~6 zI6%EpE#Jm6=(^Uou(2&!)x~LWZWgyqT9kwncM z8HShYU&k#1kqr$F?vQjc+3h0H^hy2S)mSltEMax~aKjI> z8xhRF=2@aFkM?!f(500o2X**bcdZG>+CO#c&(`^HFW@q@{Zx6p#u^$*W%b&iSU}dx znqSkmMY)HR(ohtJ!8T`yA^qyd%}+GUb?E%q^IbfFGxZ-Tq-RwaKB+y&0ypD_Sq{-Aw>kA7niWld1d zqKDtaA#!cYXCN2#o+R)#mXM!_Tb-@^_OiwU7=XjVB;pmXc`j7mbD&UhL_u!81wJ9X zf4QMEzR;G*wqXZKYjxmqOKD9L&Yo{gxczjbyHrZLCwm|tiki^3ONHCDo{~omL{=$VswHkTrdv2PjJN@Y1ZrVu&%!kEG7*9|Y zQS18(ktX?DwKvuLpq{lFMQ8Aj{AlPq~{N#1~#tr$Pusqzf#F*9Ls1IaS=#7~N{kqD=%y1oe684SG79$P-N z)cp#lf7d94lK2hAhvBJh{5pE``jhFR7u~`ijFIsL-u@dn3}<`hRtnWQ0?f0wx(>SQ zteU?GnCghWqra`KP6GK5Pa3t>DO}$2cIuCih2qsVORKSPAPBSE6SnHGyD}3#_*h`I zB(>sKZnFRnMq0b?dU$9lrB{Y8@h=_|8${ek z^z-D{-_bFhq>jwPJo)!28dozFl^Fv0v!a7Ct-t?~bP@j^4L{{FKi;o zu>7u{=5a%R241%6cZ)~+9}{Ux3MS1vk+&RzxS1E4GLZuHHCyrb-x70LzGAN4Gq@&N zKXGoBlAMk;twAGz+BO2a_c>mqznpNK9Yl1A1SB;K@a)SlbJVx^{EmFuF_W{`PJ6yb z-CTa5u6-v70oPc`f<9x zI~BG#%Bl9~?-Q8tOz~DGsFLkDsfaXZ)*bhKU-|NJcP&AiJjxGrX=RlDPRI9qfan@LP+m5AgKt$Go@EhH)Uhx?y}?M{FVQ86uK{3d5}%;xxN)fX$u9`mN{I^-l~o!vt!gt5i7S!QQm8 z#tWJ+$yXhXS!wf8_{5Dl{&&_dKTK716CF24u~F&;UWP65eohKcdSom6eAyk@-b<Ap=H!j{*9!$Js&vo6g<;F+Cl?#^E7eZJt0-Uc$T!U-V$)qmwBO+QOPR%#z zzA@ePRkg=n7M;C>L_~7ZJ&7n9;60HQ*lXPgVLQGxEZav3JdPG`aDLN@us^)yJ~Bc? zf)St(YOtIYgFZw*m*cUEY76kns;|F+5^uQ;uez541~fuKkTk4bc)j4gb7jQ^^{CfM zwhF2yQlB3stn>d1W}b14!8Z}riEt>?f^RoE59w$0ss%|}QwAQly4~@Q4`4U){u68j z_Lm!j1OlsRR_U`RP{*A-J8g5=U^1b`EMEsfPnp9y>VqRU5lXtFC=_U?4wj@^Ai-Ns!C z_!`$Dr1zpPdKSvQ!w*UVJ=sp#-yZ8lNM^N4E|at`(^Ts*{+NK%h(IO6gwVHO2gl@ABHuhcsrFo_+4IFjqb?7=gs>GJJ}y=0DZlmx=5KSF zVI)pT+Mzc>x&heYxJE*(9MVIcH4XK^_EGOPX$;6SSginU7r%uKl-h9GVe7xZ5}2rj z_b-s#AR0W{+6WIzeKKYpiIcdkl$O0mk%Ur3Q7*vI;TA+q<{|O#Zl-~~+7H_%(%v1= zYFrT(Uj_or_sAb_K!$xkeQp{#txUA)Y6q+40^a>gO!mOY}n`UK}_0+r2*7h)Nu; z3~fGnh4wKH9`bG1t%Mtd&E#zv7P6Y#ye2v}BU$f1OwM`=8NA}Og zjcy;;dILgR)55dQNLohQ9#kFnj@V*7>^55`aw;54Q0UHl0mQ+O z|KKEViriSJ!?!RTWriG@CBLQyJ`O6qBKinNGqrquwAVt(1+t0DbWc=gBWug$nuZa1 zi#84)u2G4nbqkQm_hH{kcaigHBUBQ)jE#yB>yN|ro_P(m3fu@9y^{o~5Z1h&*e*ZQ`*a9*@*g&||Li@6nW?U5+hF!7TjW6> zvXL~QA)=p7hE&{xaH}F6L=o zD{xRhnuVJbBFz((>9DikbjX@`u?{zRc!ARrx`0}Dv{_Gpn^`~lHiCOCAKvq@s7Mj( z^u}-ua!|hlHyKnAv7PRLS)(?0WBh0|_~gx}_UMSk#9u41E>%26D1CDIg{6FtCnKtm zZ_Sr4a6ft^)8)!egSo$CvDCl1ns0D$aIEY+!opP$vryvp_C2boabBw&VV7r8TT3&~ zgIP-%2y4|F=nks~lgz(WMs$gwx zE$bf}_4e&pu03Mup^Syr02eF&6{fMi%aDL5J18uHx1e!33dpYwC~sP}zo`>KQE$Ba zynGmcds$>N`rTF>+3s9``=%*?(oUhPH?J|_MoQ~yq}hKr7K-h|;CzFuOHbs5lyncF z#gY^2lGm|K6VC;ev-dF3dJjslIfomvHw(IQ_swc*Ce-TU5(F-5$hoYYvFB&9JFe<4 z;!`%^-xQ2@dJ|WyU!XD(W-f=e?O2$@(pqJtmXb9khwr>Ss6F6AKaRZUz#jvRGDl@7 zC9U@{2b>xz0=gr;p(vm3vyR5iR43Bh?R3byTeoTMjzYFmCG@AGPCi<^kHp{Rf8@_F zZmm6C?Zq2Yh6DMd0CdTIFGR<(loWOZ`NrgF2Xj zLzO1O5BAh+X3f_~^*%yA&+ytBk5_xY;zkV2lCPuzuK$GP;G++xQnZ4wFZVRhy@x5% z9knyb(D?B|y9n279j2VUxULoYR|UL^Cj{ibfY5&pkrlvPLcISQyOOp5y#AyxGoT@n z{bc=m3czB;4lsH1hYR?%9sxOsE6IO|dfKl5sFg3b<=p=+)SVadAn2Dg$}oZ}={p0h z{UCAFfpTqjU)2{Xdt#oc^n4x26ejQ+;3{u0C6C*`m;XB1<8n_iNp=01o0qvG$X6@o zd?GRYT>=h)7*`C?2B!dJ-eeS5=XtttsN~51;0(wO`a*>1l}m#23HQYv4&?(jK~k+iGa%|Gzdz zI>k1~6-Ucn(SHH|?~H{A3_5Kr;6`gpI8?qR6l!}N!r$AnOzV49Kb4hwZ_lR0|ZR0HGVk9SEAj8{`wz?`Hx_d?Q?sH{^&C4b>Uf-k#nXnW_sQi@A|$_+ zM`3HejYv|oyTEV6>XS?`{k?O~A#*sS^*`w=8C~$;?sKcnxk7$TC?9-G^p`p)uHqHz z(Ym&15tAwDWj5Oo4IOERcB5*v6BsDf%cK2XfKc~KqBiXbm zRrji^y=+g~+&}rT)7VpaIA+>wO}v8Xh)S{i*F3|~4ZymtDD_OwnJcjnk$zNt&*~D29I&- zmmPq(rMND#gIqN2`QjqcqWskY0p0s81E)we&hOBZr=iLyT@~`Hv*o=+du6JjuT?0I z?g=zf)`B*LgD9 zZKaR zV|3@+2KWL=zaphYS@p0LO}89*aPkJ>{{BoN`V|R#j#_w)btkpfm~cG(f?(WnMWj$b z>0f5@*GGfb8ATIdwb9Fb( zCl+hetkXT2-W7GGj|MxW6y$H!B0NH&a+wa|v|cVJ<^?I47NVs|V*LNx`G6Rw1W4g% zJ7AdgJOK=A2u77F@V9ptaW~XjBMO&X!o zFG)MU_r83mty1O?oX22qa4!1tJwSI!lxaa=({zL)UEzIre+u6F>X#9nbTEC~yU;1b+@`L2-~1PSfx!n%#K+}D7# zk!tA?w_35OHj+CRV}9pUUJ~&t{q{UJb7$`t05}~e)`9?C2%C5@_T;~C>#vXgN?5F~ z00|70BRp2$TGx|%SkHWq+Ya-L@6s_aMUT3VseyZyBjt={M0!bl5TzvXiQ)qVbG+w0 z0=%J3n?Kko5-hK|OSf!}NU+PzkNMzYlBqi=k8viphVjb|&|QO_{hQ&n1s`TsS7MrH z>n$l0;Ltigd#r{owV*%#bnX~DBx36qM_BA=kv%G}H%1#=#`=^K8zOO!bZXf9>L6uC zyoyHVXI%t0H-+Pfo_ARr8 zn(wPhwl(_2M|rN7)egt%z863Ff%Qd%3UB4&@BS6zFqIyQ ziDDCaU#T(WD-rh7;MZNaXB8*KgBbpOS4*+x8B=V2-WZ{9>wTxY;{* zKP2jPEU#~`9hwUuF%8K5`!e`CC?)Xg^R~F-LsFsoZIU0Rm30RWgW8UtlM zwQJk?C84__?(U4%SKMOAKW^xLSU)jqWUfH`z3ShGCBgz!2ZQq$2yrri6t|+N_dm1j zklYp#kKHk_(Jpra)8w_<^`QGa1{~7=1*`wSQV}Rb0RjI4v{Htv-@xX7nsJSAvXTi# z>mTwL4kG{0doV|c0tas3su%5)$NvjTfs`1u<`5^8YzBG%*QP2WK+~H59*)5m!OqEs z)@vQ!3gn^Pa%3o~ltP0B9m4;9eiTWi7f@$4MPKIGo-%K226VbR5uVW0ti1qXp2Yt> zpLmfBF<4;yN|Swfyn-Lce?_=o16&|O09EOMs0hS|NXYz4zD4Gcz_Eyz@zt}l%CLvn zdOnWd>leU&uIUN-n)+*>L_{Iw{#2^V!5n{dU z#oN}3WZY_0<2x6rkdnnQ^G;_ul5vs`hNq(&C4fQ<<<~wrUB=>{HQM42T`#+n$~1B| z2Vck5DbMKPeVws;nIM@Pn$oyV00+`L9HVq+LES6?&=_{)ADy5O!nH!^?Y!8#B&f!G z*8p1`Ixm1Aq&uH2tIqPy?A60PO8XkSekQK?)}NfXe+7=g{;vZ&8GQ~V%okgt>^_l5 z+>ctG=rzK#&oWtEeAU#P#dm@p@wr9NyYm!s?dic)&7HlKv53J;M*I}EUuHTY1z-~NwhcG zr-79V%UP~L$_sUxR$1<$z&m}TZQRj=WG@0;={E0_+<=iBFyBFMr?16j^hJlwbyw}+ z!IE3xi<(h2v9b~G){roCNElI_%6|ITMZx-p}hY^5PsFtL4{`=(IkfP&4DV zz;IxF!-#w=8Tb>Y#YbYELeAxY6+W#$dzQ2iwzIsyp8u|ae*w=T0{Iks{lps0oT&W$ zI)I0tYSN19i?UHnzIAq~@eu%AP`_hA(|_{je@IX<28P!aY^X30uo(bCUYCe^y61e% z1$J>3vtxK5sn#i^7wW(H$C?7j;aL_`m+(UqkxW z0f7wzeHNfc&R?3wz!6Y1ZM9Nq%cjI(o6w*CN&jtUZ@OB%jJ6b4R#mV}5h=|+g`B2% zR2(*eO}`H%H^%3md{zD+g|X{J@BNh~Qi}{nzvlZwjYsLUWXDwD@J%0*NQ+q!jIl%| z=(cYutCpRJt};h;C~@YPe~(YA*aE}bAWaV*U%+x(s#8~ViPbX9mbk!E>&aNC97a^ABBz+kg7kX8&edBC%NM0D7M8EKki&J#`6D^e&~J zkxObHJTCOaFubnPkT86Nj&r-+uF(w5r1|S^iaUlEj-eQi`D;$`gGUcT(0w-0U581a z@u)JC=pgP%lUjX9r0RYgF`zBzlW`B@qgP`# zY4VHsaL=huqd9n%n~*-)g6h|i(Gpn&Fghk>dMdhGe{(0#4i4z^jLmsC2|l~|wv^Tg zuogzOfl@<~+hmXC*Y1T;c(xNABy#eiFWSz}LhOc!SzS8?I-3?=UB4Zw2y2`_e&f5i z$kgG6v*5EV#d0Zgb9EG%c5E9Mu>A6-*K~#bpjtydxiPCWohQx^e2Ufl4+&6E)Jxhn z%xcM-`IsTkJmUZStm2B{a{)>En`WKfx{8c9i%}&U((Uvbt$e;9xG%O)KdpgHw$k1U z-`T7O5iP|`bmdz84}3v5UZ(ePiFBW|!1r0at|%PuUwJQFA8q!S&f7U?)gJRApYsIn zcQ-h75F?)|;^Yxb)A>RYWBf;#PCmoP!GmEeJ15MbjeciZn47x)_G%Bei=ysyZ%ZT- z4a<5TYN~AI49SL<@^GLEdqQBA|5y^b$`O8XD}KD?!%s>FlC+? z&phdy1mp50(v#~>v==q;AlE(WXK-e4l?}BSKSlWi% zd{#;tVYP&-t8$u??xZOOhY)`iE{LNz$@;*w{6;vW!VtruzxBpnn%2M>aG)}Qsx}pW z8}iKu=Y1mHqv`zSWswmDFv z$mG`8{_Mu5C;-jx19q=^7PD}xsnOaXudmTHAmzhITaZ#GqMWu9p{3MJuRiPHf@Mm} zw|L>~tXz|n9yFxZ_L`T*TVJ9(WX*w~?D{13_YmJVtzW#4CZ9^$^RQO>R3zT}EeSI< zKwxOGRy)CFc?JXR55ST2d;BFM&2K67PY;5a$Fosj7swWkUJBI!#d%x#S%2G4FwDT0T= zJovt>_oCuQyO04hWLwj@nl{w0f4EhVuZU$1&l0^U`W!0_wFm`!I@8BGD0pVFsK}gS z)}#i>hD;l3oiNIMEeE`itzFI0Ssjw;UX5BNH<{Lfh8gMlID}9~+=%r5czf%xs=9V> z6p-$2kWf0MyFsO;djS&C9n#V*qJnftcXy{E-M#1zB_+SPR37zt_x{e_=eo}M&z#IT z=9pvL_pb&CdUD&%$)b_Qn?^~UMdyik{+et%i~-(dgesB(qwj6nFbbO6D~TMtEH=a* zd{!GN+zDA=nvG@5H85Oj4)4VrA0625&_E#wFR#Gl~>>b`cp=*K~+UV=q12YZyBks6O|vafZ4WP?MM%Rc`uKjRGM z_1X`4Ln(2NR0E}~)O=kyR=FMA#Qi5hR{S+qlmhj2gL-}?&F9FJ88>DHr<7s}s9U^s z?tu#utHCU~zC#j|_O9=^)e=IFJx^8z(RhiH6epytaQ3`u=0tQHQO$fNOIQe|T%e5^ zp1=G@!|n?c{nYmwn5t`%z>sQ2xl3c%){a%j0Ucr!cnqdvR@eY}&xCsP3zF9YQTU74 zh4z9f?9$6l=a0z(0eYY0*`2H?#JgL92As=56TLXB)pn8%ekJLN{#{MY{gR#u9|G&= zgN(mvvW_)jhW9ahVsb8VRo1zX2D3h=+8blp9k4?2se5^$&pIV(EwftpoHUXbURK%m z^T#s8-JJ)-BVY&B2iO)T;zJ)rg(^Sm>dfC@hH~!G4w`SslZgJ^EU}z9p9o}(U4bf@F zxmBr050xw2D`%Y z9(Sp&Jt;HFkFON3)!IFSZWCFU<_pKfTn^YxBMZz1so5;bHBZGJs7c5cXV?` zjW!fqp)52-2PJ!HYL{;U{d`e!xh|Y)Q;$-;v@8EKfAO{58a7Kf1z8Ze>3Z&L`x73K zz(6ist*u@+HYj|?3Yp1)cAZSxbsH7#)X5^Cq1ovuisDLR3l7}XPewbblI?xQC8=kR zs^(Q`pGz;ee<0{1gjP)G@U+>&O*p|y)DW%*enPs^dMu&2hM1-9(sl(3I zP8#OS(?e*u?#^t^m$uK}n6NLoeNf$a<4v!7ku>`80(GK9B;d=bJ|XuI%6x+67Kdv8 z;kJOS;iltBdejA$W1Q5g?o7cT_i;X3-xIaf#&DkNV#F~Ji+V^fs9YBspRpmeKOvw- zdpy9{}kk9p*Gp&5&t9tFI?A1C14_U@HZQtEI@r%T zscPW@6dIQoQyfMxDfv4}g*5v1C#_M2ex)Abh1~cBfmiR!%Q<_w3qrdc)>M;2I&z97 zSiJKG_d76xpMgd{GQ*?q24-t**lWeA7~uC#bM5!*7H-c_GwwIu6o2}5*|l_OcVY1e zz0Y+l*yAjYeQK0gV=!xZH{Cj<5d@!8-L$l`hx{N1Y*D`{XePzA@|lRVDFbVTQvwK5 zN^;q+-@oi6`jitBpMn&x6{@BT{+5TUSAV7Owfd0IYq1-xwlj;#Vp=R+7~{LMMJ_89 z%~m#+H9q+53q6J-`i1xiLhiquy1*(`Bs%`)OQByr4SGaUzAp2O zI#;KsGMYkQW=)2C0<9b{%yjKbDpW_aUJ~U{YhZO%w2c^$A1S*IheOozKy{uKpcJqm zxT(#O)b2!v?bnPLoFIo06dcZu;@h71+`UxASSrlj1hzq4ovPdn5=I*U6*Vr67uwou z!ZtapP2{4*a9iSM_v?kC*`FLPJ4S+RlOwMS){QoLo;U_v&r>X(6;0zapbA;wsrthgfjUe5V!H|jL zM;2Aa8Eoc5ZZ5{J_d07XS@CaGj;FdfJJ^2!^NeT;O{SL?hwb4ZGc4*J+Sv^9RPCvl zQYqm^0Pr)&?mJl;q2(qG=~Qw_MI7B*RM>pP21V)DwDcgMc=iSlHl!TdD+y$%g;T|h ztJM2VA~+n-`B=BxHO_4N9e~$wP~SZL@&Xq|aF0?NRVo=B5pg#YPzFTZyohwMRS@5# z^~)$C6=08|KREWAjU{t(w9R|-ka@@fOn2`3#>+%vc$2U3dx{;|FkF-=z8$XE;e0AX zc|UufhGP7h>#L>jblaI{n>B|P)O-$Uz14WwBw-gI9QKsR$*6S+;UnXHHoy=_T^>Ww~=?If8Xrw}v(;JWB@*isKq=4V@2 zI$ktV$VBz)(eUNCgXe(iLka>by6(@3Wv}D&5Fc_VwGWDN8G8@rH(ptG*(;?PA={1J zUF$8iA2Jb?M&2{H@q$GAOlXmN53f+KE)Vt`jt8+IJ&&L_Owz9gB+U~^mD!+!-J5ef zUtOvye=c7wOROrNGgrOJ4c_&p$@t30iRovBpN&-d6Ec4*;D<~C6({fn{jP2Y+9(PQ zUjfJse9i;}5#QX@d1-#PR(@ejtz|&g_A)gRCDgK|IB11Nmj66gw4^?}WLK5{i zyqjwdLuy-x4>9+Z?Bq>3F%TQvko!%0_;lyzdQI7%K6rEcgmn^=^mq6ea1W4a^;Rye zagya76t5I)O$FfGliTDJtO8-fm)v6eiTDJ+XtC;UNSti-*rVd@CkozYTnB9}^?tKy z|N0*gvN4@f2GsdgWC3B533I|AM!OT%Za0mAr|;%X2Ox7`U%zc0hwE_ko)6mjKy*d` z{=2pNflow0L|uT5?dwEzLFsaD$&jg8BB!7`-*Ha^&r(P7;>u~uZGvizINmzwjyL%A z7)oh#hyXyDKmUit)nkMvN z=bj<&JBK{gbu4lMe^&JCXAIV5Sjcawq0xT|RPH`MIx|QS29a9aH_m+OOx)JMW4JLL zn>9lN5r>dxL0$fdV~c)=fEwzSTqX_#8v?HTq6<>>S6}TUA#Xp7vX$X(8bbPzXF$>H z))BfGlVD#VN8>=p4&6fgkZ|5bE|-9mn191&z*-=4F!v$v>q4&j$Sb(25S+{=e0pz(q?-ud+4M@{j!hr95pFm z?-LDAtcn;?F!?Cf=_q!DgWB51ufL=*ph_r|J7`@g3ZhYuwau6HoZQGD)w|cLxYmlWvud=hriM($gyw#(7+q>VoEc- zldNKCvP4KXu~HQ1=-9aACWfhAY&<`26vPfUK4ENqp)3oYtb!v=s0>ydvWnPZm?193 zwoiCqMK}zPJ{Z-#{5ocn^3u%(Q#kVRE9ukzUpK3?1!3}G1iwveoY$ce%~jQupuVC#tA-%_kp3~ruc4% z2l(4orGGpDFM`0cq$L4%sFZCxtEd(p!mK%Y!|FVm?aib50aBy_(DnM& z^?lw{=5ka)l!G48z{V#GI=!_d7F=WW!&8+U&`-|{5mR^(C9Z-;*KUf>%;AqNpDl|Df``mVUtPf)j&;Tys;-&txV|kWWHS|% zK3D9XBCi&G3WHshVeiFa@XuE#nuQ5|0QsFNEab*H8^OKA=wsO7ZsW05dj5&d>=K39bz!T0m9f{1<=vnmFB zD}%t)^rvY$!%XahN9QJ2D8J!3(TCVzRX~u0OrD zJy6*}>(f*)S=r7fO_2{^`_v{pRX<7eYh5qzzEZg@3dTjtq?G39v( zqq<*Wv~cl|aEWHpdT+@23rxVVvQbZP2m9(g++(N1?htbHQu`z6*FI)+u5!T#txJUL zM--&+!2DuyIde4~g);^)WW{m%!tZvZ>IFKj9ZNS{#ZDX~MR=gTnY(>D_Gt)Qi>nHfZwu%ll80|{^wZ;+m_gPkou6`fJHv>J3 zHBz<-{?O#^uQbb$1u33Wqo=%EQ!tbb;n8*St3kFA<|y?XrR?09!E|rax8u}k?{dw2 zED-R4%mfoHSGNhXT$tE?L+S(jzET(-=v^b{%pCOfdorn!n1`EYtO$AHJ(y(~o-Wic z`4~)K{$_gt@RUvlf~`4xp`LMmZyRXUHJGoRg&ZVu?fI32E<7PYlgXz^fWKJ~`-uBs zOb>=Ey%?Qm5PvAR$7Gm(_D*eihr#-lV**>tsl^0w(GK7HF5bhR6u?$8tR2NdQx_(HTSqK|#dHu2xww~FuiW83*Ts5TJ4ZGmdutFzP{7o6rR+=zW=eX_YLSS>z`;@2LnF~cQd+BA0?17y#JJlP;bLf}9p%skt6ur<C9L27!_CEn zXaf!gm~#7MHE(AG%+)5p+PqOdnAe*t?XxA-ceb=tb;tiPz5enaiu#370ac8q!nZ8b zqB5UVnJK=jdlB1*Fr?NNpH@8+0b{HjF7(m;%8YJ$K`}yJ^19nIUH^l?I6)`~NsxgGqWB4ca03Jpx)j(F& zh19W&Kq`^lk0SXJRW(mLYx!oU>xW49$FTd!P__y)R*D9q`X0>H`Rmk_*A4ro`T?ok z`RYaL>9^1;caH>_bmYaNQU^sqr4Hy$`~uybe32xulNB_LQ^F(M^yjsmB4`udzzI|O~*zKrCZS%0pi8J9Ey6}}u##UqKX1NoM@VUFtX^MbKnE2D& zxnQHCP`3xk(K26bQP@O@qS7#UsHFX6P9KNQhYWGaq%q-sf=Qyin_&PQ z5r|7?59LXJ4KF-Xhc%EI*^_o+usmywVE^qi+(*;7F+W|^#nu>%vSz#@ zJ8-va2r*1ENZ;Ma&?Q-XeZWsU?T9ot`Un6j`71Wq>aL_t6P3aPq{)p(yH6^$d5IpS z$xjm3tkT~zB4Vrg?ul#^%lf`J@fnhdLikVmzb22QjIZ|ISYnRxb`<=%ofj`v4UmOa z*wOhR=CL%g%a{p8W7ZMLC&dy#rRm{{TPVhyJ7__WTgbS*4) zdzijMbsb<+6+5r>A8jcxp5U~*k1DqnLg2RmGLP#?W@%G(^|EZ%drLrXu`)SQ&!kPQ z{(RC(GD**@cKQ>WCek}c*JEc_-#SDGV=9HV?opEcQ>ZUw;OQE)w@+kZ-jIgh`}!0h z@{x&3W*R&);|iYQGVFeU(ev;^NY6re?Qb%nyHteFNO%3Vo~Ak8V#ikbsTvhX^1jfVnFf8@|PLZPhgp!J8zEQ+|E^ z0rld$UJsnYk4G|PjwjEL9oS2eEsFz^5xuLZ5Tyqs7^yY02!$z@?y!J+suL4b;BhMX zg8olfV)pyqCv&p-&L61s1pT(hDUB?#U>A!rr5tpbmK~Z^3K=1D=hp^!NQh+jqkket zfGEt|dCZJk4lJ2oqvjKkO_^AIHF{g&pJ;~iQOZiv=@FE?AQgA&tC1!&QdLOC>q_go zX0M0(LmAu7Q_ZqQ#<0+b#E>2ynq?{7WS!@_zEuyw=yJ2C5 zOBph+-nRb1A-1kiwrT-)6dejATHCqlq5?vd_n*cH$mSwfeMb5|T2JR1FMc|&-GKAt zFXiADoWIxhB$<7p5nc0CuJ#;sxQXI9)A%O2UGZ5Hy(-_<0K@E@9 zU0R3gRRigH0MO@o5{IsSd_XPB3fpHrIDrlB(&!Yw6jsG{XdRlq0U-b3vvLN4=NFAYaHFClUF#0k!WI^h?V%=mG>DVhcx4Q>}xeyk!Ad zjXtR$QM9IVjio}iSI?&@s*&aB-L3%>DkXjVs5RjMwqWxf*M}K{DeZ0YjO?R655#$j z06kNd`LoK67TA~!{gLk~Q$%z3PS<_wW*-NHR-W&146)~UDaZdUHcg{Kw)+zO4j@kt zi)N=^Gq#%WT^?4jn@kJN{@FnP04Q)^$!juz!pTQZF|g6nQ5dRXhVW|9y_Tr)f!QS~ zJM|S?bqq3hq<(kIoKuX*U7Hu~+20$m5{I)!@*j0rnrI#hz+M~F(c8kRDcx!fecYFx z{BV%bmSHkwF#&Jf;2dDdC7PJ*$1LI%Fw=I>{$d+yk+H(Bduj;~~Z6ls*HGW6|RKMyR0F6SOi0`%=Lchg1(D2z}M_JlKV);5Vd* zv{u`S4n;59Mlim|&;J`w5e>uxI$8CE4|tEB{9?Oh?T-A*5G(opyRZ;2Js1a(56uif z{gF(fM+6EV@vjnF)=k62FAOI#aY2j!x;q9)j({B*W1{%F`x*9TOer${?5%K*>NUpt zLV+U(d^pApT$ZfjIS-(|_5qbdskOOd;I2`^k!M3){#}JL)IzRQP>=pF$%0!UX)fx( zc1-$Kf$Zzsed4XlM5|{F=(RAOElAOK0d#joE3jRALV88w_4jV|VwqPQ1I=Moi}JpE z8Em8zbE(NL`?P`r#!UJGFY&ZLICJ11u)f4VFK_{O-ZL?AaV6mS-VJH@MZaT%&Hvdw zfKmblxF(2iwj5n4)pkzR#dyqx-Uu$ch%b^e^C=0-TbF%K1%v)aNm?<_I0jaN0}Tvy z-mp79=~>F+0N)Pl%6)iP4c3bP*2SH~lr6EH?u^L(UGO;-`za?25yxDZ6FILsvS2n$ z(r+ZrO&4-BfWes?`gj4J3rFE{MQ>lKM{OmO_dS|PIhp13MZ05d=$cIKx+WRTwsJ~+ zvTOX80)+1O*aNw=PsyTQ;J~mmyKx59>$YQvbW)ud!hIy7=e?S>*JU}^H;=}Q)q_-E zwTiw7cdt-T8d6_JaaUnZ<(Yku^Km(Vrn^!Y?RH8Glr@raNYEWmg0R$%>f9A??A=y) z5V06j#M&IoK#njhJ^SrJ5@w}7*ELh`oLI5@>K!mqOuYh!Sn-X+%*|#-n9mKzHR8FA zi#4|dLnMhFnCi(Kn@%sl$l;i1>^FpOCs{T( znnk}X6(F+H@}07-x^*&qEwn9_pR~n)7HumQLfSyMIFpO(b;)P^dIb+ha$9(*;-=E> zljL$UTL5Cq3r7u5e{&0FgCz=;B56mE+N`8^Gc@~8YyIa)D1m%D+nEJTSXFe4%FV+Ku4m0E%T2IkhK@DW)t#rV^XddNNn(H zv01K$66o+|%(qSs^|viA=zftrfB!!qt#1M0!nglt*7`x&%tbaWX-sHgp2i89aL;%MA4zvppa>NztKm_ z%{<$J9>lp7iAIN;%7~jf7@;kpU;9kIy{+}(!HFBx6A#K4E@bX}-8DxBXexA4xMB#) zP>@(kKn{nna-QSpGBtY=?c=gjWZvWo{xZfhn)fR?Mnj0^7mXG&3mJP#*D!}HkIm)W za}p1)aBizl0%l_Y7^#wn=?H&a_-Eak_D{)uI0dn6G3KjvB>358nk2(#z51GJTEtcn zr7oYB8jiMWMj>I_Gmv)?uNX1oJIEveE2WvTL(^joAZTHO_UPr1;@Bb%GJF6ob z;Iu=^v}SnxRhUsIE@`j_^>D=wkjq{%t5<1U2L&+3*=fP%vmFDD_j!DU`tS5ih3UOd zWxD;LOEvw3>s{~{h=r7`QYnsAg&Hp`7^f1Tkx@K2-(QEl3?+c-k#?!47%z{)**4W{ zuOo@G!#m}_Pggip8%U-a)EyK1-kl5U1p_UwHXT%7&?a{&kGA&4^KW-9Mj%9`)eo|L zxRC;9#F#x}$l`NBy@X0mv3i!YUFRHBD-v#t5G6Uzpyn|8v@%oJ|6~V)vZ9F~2<=$B zhDQBj{Xgh!iu6`IRWH0iM0jTpuprm}+x2(tl zl_a~-^z}D1pf+0DbXn_b^XD#YiOut#-@_8jIox2HwRu&nI559OAwG4SQHlQ$3KB6u zi^VCYNIo7%PKpyw{OtfrYecdW1d3H-1_)Z9Jr%D5x-1p#?TcG0qt~(Sqh&i2x$Ppj z)|aWu?;;m(W~unWYUdfWsnRYV=Iy+0i@VqrzOti(0=Md$CpeSGaq*3t_$BGB zEPUz8FBU3a9@w!Ze%N9_+KiD?LbFopDdSb^>OGb6V!`hYG*M^(!5YC z>VRCF9X_CjraTGLTk8NXQ)1r9+pnZkNSKq{fKHTX_8?W-=p_xv6p$;luBp)1JAF@H zwq9btXpSVY`}&l=@Z?&5h{RM^wpC3^h%?_Xc&0Y0r8u-q%>QF8ZmtQ&GAlg35xxe8 z2{@5?-J8rIW+tPW+G-rhs7o97b8U#;P7TGyg|$5eevN$fq}s|Kn%JM}C&6YIHCcWx z_oMul?*6t_f{XU?X7BWV=YRVjbeysg%M}6)_aorqX;`IF8qhCCNirQ{ovdz+pv5Q1 zCFV~RdZT6nMQrF}@XKHT;nGS)RWKBWVr8CjRDD&sz1tpgVYEDJeS_dbD{Uu*1Zkux|Y zd9*V&sYjD+_SmG9@#17RD0`#YxUIe9*!W-;Nr5WOYq)|Oo7+3^b)aF$K>4dc-95a5 zG3!p`OOvwCWaWo4qyyZ+joU=d+L?;99J~4t3r$O;<(PRm3v5wnrC=slQ6!rCD*8Y) zR)0~K(kP{1V`imgD9!CDA;Um0CTD{3Lhd9_WTYZzh}!O2NiciRD#80@Ep ztL~+K7rp22ux(ES7qR`8AIK%*rnPYL+F{mn=4V~Hr=}r6Bfi|gbNWge@S?3_c(jNt zNIpJbkS4Mn;h+(HQ31`6zPtw;lU3f4g+oFUn-!Yj4k^G^e@ z4bP_A^rPr8S0^2-7bMlZ02ud8rqvHvuUd=TlBBsf`3*NT&avBRAy0q78 z!RQ3naG;rhCATUy`4Fs7=ygHn1AsM0?r#J@qEY?0lH5^fXg{xG$4~Ugnwj%MQ0Fxz z9}*p0H}>ar@{j4mX8MP8P6aNQuct^t%fjDV&z=rPK72gY_;6!s2c9HGzodw;fendR z2(cq5N_?o*lxiL(YESB7Q#l@65E^j zQ&^J8N~uNs{d&BGuT*s8+rvmgiIo9Oh;L*YN_vWf;2zo2c*13etL+VPcc$6I*_gk_ zi4eANT>E#(te}-&exPgbTrfkLMUbDLk%?*{gU{gr#j#5j{!TuIGwi>OIIYGI3b$0H zPw=09_7{-qW(9=Cig4$YAJewu-vJRnS_=RcRRE0k_j4*f)xFJzn*F;ZC+C0=*|(Pm zWOG+C9g*-?sBaG;1|3##Dksffc-~i>+}F@{5NBKWbdWm~G8%~wJ&6=ydQYRZ?TE4xn~+{Wq?3G&JO9$3FH+Ey%J2;NCT z%rv-Ia|76I`yA<`hhwjv5H{Y(!rokwWnY&Q9oXpKJE#f=bDELXm&E&L1yeI3G3Wf2 z>;g)IFp5_8XKCLM0XdAY&kBRItycK@kw2RrY(v>60+e?GcEaSwHwQX;Y=Rq;*0m`g zl+E2u|BaNZKy&No{TC#601a#KjoTTGXYG*(B>p{iyPWMe(D#d}j}kSp%mV0YxD#h5 z9buBo8(|%)lD|1_Ilmrx@UsiFD{LR+aP-{?YlmBhmoqEe@&mzt$}2F`zTH04_EEypB6A+phirm*?1Wp>w#GUq3AAH+#Sy!o(fZ)%ZwI6nlwkNr^o1&n5} zJe4Ks-g{S*{RG(A{^0dTd$Z*kU4AnA@k&pS-aQSi?GJwamUc)!98Ko!1TlkPKrjs) zmSuD2=>sgNL;+B1xgFD)y&ED~Mff93I`!Ct+eLm)ZxGM>V-K>#pos1Q8*Am!Wu(f& zQK$nKPb^xP>`XO1gvR5OTRZ-f@-|_c`GM}&rCGJkm(^{a5~1b0B#W##&gEz(Kw{!u zZwdL**(iXp&a3Ot4)_GRrjN`yQ6I+QN80eB=ymKx;nV69EIRjjk$D9qI=ky}L*7jb zQBn_QTBGpR?4qWY?O?{zR2(x_H;ZKsZ%hvmR;(n*eVNC0jgUz7UMbZp-p__O%XnTo z?M1NYHL=q$;lEI#Hr;#vW!3@L@P1V1SW)mn5d|$IkRU^M=<)gS4$F4PyS1Um5Berz zF^s;_u{vRTof5JUr+#+n*~i`cBxi&71gue(-vGL0@tj(F*mxa5J5ojLS?Nf>>?bxeJ`M1wDf=`|3hv%ea14`0#084*m=F9Zb@MFmDyw%(zT^gmnZ^ z*-K3En!T>5tOccDh3*8p)}mYUV`p3FRK+;(ip|OMwIum3+GNqZ<4R1kxKU)`lF5)1 z1Ua#>GmcXJE|fkg9YdUXtcIgpLMa8VW;HrLR-b55uN~s^kHIxIbMxw7+6aPA!FW6v zkbKY^QY*C4CgrOvT14wpJlCbT%#tMJCO!+uRhGlJ%O%3j3pCnTH4C^%v>VR7vRpp)86L8FYOj`T&Zal4bOk! z5*&^w`bDo1JS!F+;{SxtdR4v2M~1$8eJda3w0|!NoTf@Gl^oSc$V}?t5?pc4=o=Z# zBYk3zomtz$5X}(ed$nM*(Jk-ne@(fL=&g8xGJN0_!D`@r50DWXUmZ2E^Igg4d28h0 z*PnG{MA*;{_O%^-XT8=)pjWz=cW%>vSx;iA_I=Sc1UJ=Q@Q_XQ%#TX&qi75a)33cB zn1T%95JVJBUb>AYp^rC358+xbY3M0zEgF~4QY{T*P9sr5y^~}?Vo&M%pa^ElJfT0y zI6d1YV`XX*{y`JH;l@53&xyWdCk>@V5B-#fOHhdY0UlOCdx3xBaC#O8DX~jy$N^58 z{OI=4yQfpD`iy8_A*mWpg`pGe3kt9;RB+l@wKU1?34Z$-%~l;`5)&!nM!L_^8Ec-h zOLz=7TfrC^5WLB;Ojoz0Y-wOJdJ&Tq@LB(%Lz@g7E}+!P1cU+gF{zCX(2*8n-LXca z4@2Srajo;>hq#t4OJf^o_gJPn)^dl`Zx~WNEUAM+1M}<^l4y=3Zq3K+#fk}$LSCt2 z92IkObICve&FfE#!viN^^uMH1s#K5}#Z~mWVbr3cWOlM zxueXn?I@ul_e(g0h5O;6!S!xHKb69H=(E12If9H*Y2t^#Bj9@MNWfU0SYzbaNcM7} zX;)iA0lt!>R9gVm(5I7n>q?AihgDs-YrB9&U@;uK*guZKDuaysi|#AB?|Oz=$S&yC z;9Hf-T-MnkTZD}hU$5p1sRybW;_xlnSg<<;N0OpRh0+SV=)w%|MadZPmWye2;IHRm zS$JJn7zXqd$I1HQgpe*|6H835s4<**3iH)ZZf;ku%RU0{ z!E4DI!;RxUhYmvFmtnpB>ds__>A5Vfb&z(SE(ogN7bC(D*LraQju?p(mstpT&mC{7sxy zIGCTAhO~gX%Pk@o@FOXD1N>XF>u-hueYZU55{mnJHNNDm4CyX@k?V-&K0l%__u z=iW@f8smF$`3W(!RvmP-zCLjHffxH!zzp;iIxX|o3RSr{z~Eqib9k17@+-QXqdGJ< z`k~!~QU8wq;pM6ADuyEYu6RV*t@-c-|9R68xsXUifCPd@$a`|8-W6nu!=N5VK%?GT`d(9qo>Vq5?eH(G`>_A`LzN9?8{>-87*Ks6tn!bFHL1V}SmY`Bpg#tCNQa5e| z(j$bX|sbXY8GF z)D?6-(y~#U$YoxPnAsKD0BvlG)sv{Sb(7ti)yeWP%Qe2t55_ ztTLi`&nR;~HVYas2KYKMdw34z5d_)j7B^g0kXC9OO#t~5?3&|?7(l8Ba*no3ecCxe z%U8D|XAi9hi`==+2%JTF8#hk!RLu*5_nA#mW!^mJKnKIDL zmzI}a;{?R*q&wQd!>%8b`?{uVA>Z~F4BMWF>rs%B6BwsGZ+pU!1y|ij4jLK9sD7`0 zE`nv=R}WY7h4i1!2lUwY1#WI5M050p4qQhLB!_Uj$LY3IC-;5a`!<&A6&ypjl;G=r zuOYLqFV?=*nX~(JsDwW*$KHXd8ztbBS-}8ZZTvx+o!P@X!{3ARN%%3e%7-sJ7G{BQ!TPhnE0EG-Q0OH5;z!T5r zY2;P)1knLT_HNhxNLjI42~RLnp__B9O*l$Yo@}f;p1Td{Uum!8=}>?~iOkx<@Wi(U zICSaVGoqR+mX)B%E5@E}+q{snn9o!cwrO02*0jC-b??1QK5%dR)ha&10eJHF)LTFz zIBd(5;{JQ!u$&3)v&9&@(mbsUmoU8#c>ikemC{Ba`sMfhzyi1w+;)KPu~uy_ud{of z4{4CwDW>1v0~TiYFPsTz$xX27T%ed@UUI$&UCGQ@x!!o$s{Mi`Hf(NNYqFJAsPSte zqPO1O4uMw6k0dUu_kWfBxzAx9BKu~&Lp3*Z^1=Ih|4X;)trvp7wyS6#Kl;z`M>Q^r z0~>*FAByW2nT1Grai9lXc(!;HO#}c1tKSGNNaxQsBG(fVigNHqiy^;8OoG^&!O5kxKY-P}J$g+^&s%GX$A%bAkbc5V&bUeYwqdSisMAS1Iio z@>^{vUHTK=7m!)~chb!}H9+5EB6O4lXqSLo7C@nJhjS=}LX=F)v4eR({zTLSI2Y*G zV7~*<2fH=l+oWF+H9#|Om;K`rfU_3?u)}i$-Hwql@mEw}nnjhxyPNxBKY|c{OK+g? z9zm^#{=Whc40p6RFpbY-(HL4&zlQ@A5_2jI!`)y;tW5fMEaVN+ zyp;d?5%08sXwt(ypWCkPcVAJWx%SF4j{()~UYd7WQrJEpO3yn<*>@CJEhQ*m-JkL1Fdd#Sl2at90z_6!h zw2Y=1uAAiorp-$=!e3(uI2%fV6Bf1b^Rv)h*YC zl7#{3%twM3PFdiT>d81!W_DB$)FRZmixQQv1LN|zwt1D2EMFj96`5d9q2y|e-Is$c zrNXb}04{Wh2sg#`=OP^dIY0*peO&69qEl{K9ZVxzlqg0_oO@G8Cg`~i>iY!f;HE-L z>q^q8qm1Oi&jY&5V?03*0YUV{JVoDH;HMBJ2|-CwMt#RrZLVj}MxamhBfJKP(9r}y zWAB~J$LXm;T%;HCVuvU9-0(0AVRXuVhWUSuRha`0csNtYU+&{aJNX|U5R zL>_Eichd;C!Vj-Cph?es_SWMtPqlqufLN)@?=}MuQKv<{g&Em5SO^5jXwyB6u#RTW zGzH59c9%QKtop?c*M~90zd`bNMgIu>0e)8u$caiDx5g#Qj2QhV=J1~y$`p-*V}%ms z%>mSi8(N?_^5#CfP@p1Y#VJqdUV%mc5 z^0jW@c*XB#)Hh^^o+x5Q!$QRPtt+O3!GgT@0=@P=Nktu6YNWfr4%G0HW`z;n`;tg_ ze$=jIThp|-aE^A4p0LW_99l-pb5WS zxCLZY`j*%tOwHDIn0RqsrRmU&|%5SH9W0~qw4R;C9SF9!0`jVkVUIry-m zB2E`D)^xQnUa$&;eWaEh8q~)PL?&-WDLV>jPToX|9UfPv=Kw9*6PgyI%N$QPxUUQ= zYskd)|6U7~Y~Xm=e%wVbD-g%vDG}=ztMTSrjZzrB$6zvj*TfX=@3}Af5D#1l*@p;m zAqJJ$m(!qokA}O`sc$wev5aU~+5mJ3>vsR~xqxObQ`v{~tx;;Mqc)~a)V)xbb*eny zjjb`+*CNrXqTGKS8Z7EZ1*Zw=1s|tbh!J6A8`NpF4&KI(2WjNQEm?XHr51ljBro6w z$ub;1k4#FG`Y;)&(O1vw=!AftSR;K~?|N?q!2_z?X0iL^OT-nC+@D!nz&;F$fa+69 z!}NQD&^k@TOMXUh!|mD;+~mnN0e5}^jy^vgWUMD)pB?ie)>n`FdOd7>ep3!JSn~q; zQXJlCE&EC@QyiHM5}omDPydOvp(3-D*+H5ze)Vtb&w_~iIzvU?r|p@wo!fS@>&EyU z`p|-Ocr>s!^M{J%1q6*};SHKEk(w=6LeyEvzIvi@NT6+vY->($E`0x2G6`^fHnFtc zjE5M${bb-tA13eJ3Eap05sK8OMw4i83H-e6WiBe`+>a#AC-QR11hLgqz5EU;NIE97 zxR1}Y21JBOj$X0G3>YN*r@RY#<+oP?UfqT(u52wjv-a5}iilS!qQ;`ny?IU_(C6Zg z$F_4=gNX)WZX;^*YePd14Esz%)Rwr-EmC}ZON5UNZYGu{;>-E92UCQ)W9@i z(lAZ6eW32{$gtqiC;hF?fXzXTEy*lIZGi}9$Yp#IQOFg|?B<+wfMbL5*rB@F?NrU` z$_9q?{a!0lq%i$K0pNLX$EY(9hgSdF?*UX79*}mR{Bwv)@|w;EE42dEw!@rMp87pm zEJil#{uggo-)p_tPyh~!VamK@s)74QFlF!fss1IgNZ|!CWd%NHX0A)Klm>+zYjDf#^3Y`cv`A+!=>$(42;QL2NPZz(v#Bnd(;pxHnhPO@ zjzFuTnEGXfm11F9SUKaFUVtvCyg&nn0kdwz3l39Ca~nc*gHP@A=G{hxA}K6E z^xGu{Ox+pxA^Ir0C)JGV4zA346eAl{tKqhXAIv@CG{^g&(Sl|fhKp^wT*zFK@vK*) zGW zQN50zcmbo>`;3LRPmHmqCZBMAY+K8H{9tT5Fztz7{OR9nC*K!1p8u`dN%}w4PRhM& zz5gk6axgQGG)=KiKevW^Wm?qwIdT@0D6tR)EcM(9q-#{@x?`#*xmwL(=Q(~~h30&v zUwyJniqwD`oak(j>Pi9zYV|`l*r`7xz`lkM`SJ9ZjAn_ad(SpCbcFzkUYu1mE;q+m zv9(_xmWbinOQN?6xMCmS-#aH{5JiFZW|*g37)TVz`#}YCwF0s`7v|bMFvuJzQ<}4F zMKO4|rM_8G_>Ufy+n?;ZKajTW(ygNsU`4%T)s@W9hR#txeLROB^e!cqvkF-#^ zA(WA6(57eP1dF$@INBv60^s4T)V8K>B?u?3F#X~!f#W~6KRQiu<|B}>bN`o_96SHF zOb!adD%`eP1Qn^*^EpCExIAS(5_H0|Zp1I(d*0>5CdS#(&_mB^Fn3mJrEAjpFEY~i z=D%BQU1qv?WPH{`*_$Np9)Iemv~-kfa` z(SiMHfrT3mn*215rq@X*Ot^$3HD{m1Z|bJ&zQd?0-?i-kRNv^Wm0%DPB5uJ<)zM`( zKJSOdqF-U2Pc?jt?m9T7c%UW*O{ZOHecbDSSz5e-b2lGRpZ$jP<$BQPt5nORa*;(@ z@avMnLMF8Gev6DUT|Tv+k|q22|A}Ntf?)O20YUIV$5z27UZzFm`}^|--5^CvU7@(J zir{L>Z?A}wW=?}jY8YQ9y(-wsa8#2RtAct|oYXeVNX)KK_L5mWVst=nQZFhg#*;X7 z>x1tHJW{~cu9&qsvgR6A6WFtP$!*hm7WcXKBg()e>F_MnhsP1JzS_sejj;g6Y8A%DYlTAjjRp;FDg2@UCB;! zc%(OIwx({nU;+w-g6j8@Uz1g#_{=?{Vm1Q4ex`0SOAKi86myjxQfEt(mBSf4&HIV^ zPOk1x|33}ufDUYjQJDAW*qdPq7|`?G>hqQ`O}?n_`*?)<>0A#G_pQvLann{)I4!}z z%AXT*bP@7`#mx^tnzC@Zlht@#UEG`x<>-J7~r2nGSE5tXh z-y=H`_v(8Y9xiOVH$Q~wdJ@k~FY3y-|F%_Zb0ilZ5HGX5s)vGYIzp~@LvODHFKup3 zpm)&7wVTCCFZsLb2ixWZl?K2ixFY~^zD%~Eaei09uo*J&{d435t*{5Ow5X&)qR7-{FGNa*GL(Hb`sWC{`CQ?!$^JGsm?Aq|4@nOV0cI#tCb6P9h zDxHm^CR@*P6`Oyy*0f#NsUNgMCTu>j+UEfA|1Q{w+nMh>RSvvRdzExSJ6&CQu(SXY6e)k|h{_@*{)-phm12x6T`u^W;3%_rapl@`9P>FKg$p1Xj zui{h*>xUlbXAZFZP2>yCC9~F#gTCU*O;k4xUF>)G8w4Rr(}V#~GT@*$k;4N1TcoHF7GzCX)ABX`1FmCJ@P#h5S77>iFv*&VZ{}|? zl{Cu#Al|oerEr)w=q{xDmn;c*XkXwU7a+{J5zry&mF@sSqAQ-F?l+5Pn=lJPNIpi_ zp^If8pIR0QZwD8O1w@@N<2Y=2BI>@oo>tWF3g)i;il}6(ZVrcvYOYm%s)@^I&`xM!I9aX$P(;^8mK|e<~o&~tXdFQ(aa`V?XMsHXX z77B8)ekag&Vf}TIG~2J#aa>tQ9>#Qm#^H9tpuMtJre^X&5w6!)+hd(3Ob3&58{)-3 z<|Z)J+F|1X3JF)EbrnjpXuQ>I-Uu)rGTHne+TJp%sxE9B1*8!WDQN@|0RicfP*OTK zol19i8YoDKbT@2THr*m1U7N0rAhqcZH=Kov`n=zJ&iMYEKMcosp1szbbIp0rJFfe> z(vC-k0E`U1zmBUR`8D6)^^UqjWJHH1OZyKa>XW$B!tS4(sLB6JV-3(Cl=1-!h$OCQ zON%;DtdwRC3Arb=(U!<%LG;Wvw||_uzOg3!*?Kr%UQsTrXq&$yt}yaN(!^W6TYNJ* z<@&z;y|p}g(PSSB193x6@bR~KA$QrCf(-N*4vpUjvfg~UnQNIDYqb7D&Ed!}=&fib zL8YJInF&h}%J^NW=VjgEvQQxPMLb3+-%3x_A)d%u|3AWJAQ=br6I3VON+-cE`pE_A zDtjC1W;R)~|yC{=w`Qax@={-5_6}9rN zjrjo?OVBbm$G4@FJ0m?f7tf_X=NY=YcvcAkVjjJg^k^8d5fW)7bow^FQmn0kFN*WC z?FaZQxX<2JNwMC1;;?EU@escyfAHo1M){9_6%hWS73Jl&pa>2>N!ylwn^1$w_fk~) zM?L+*wh^*-+>^Hn2UxM(nrnp#eC(HGs`TB}Wsg`|R3)h6qm zEaeC>Zj%>}tn4tP&y$<5i&KL5T`^F7D~N^s6Y(M@&@d-&ml~F9bkYxfx4lBE^o2G= zeeyy7oC%egc}%6|&fO9WZzHI+Wq4ymb-({T6s=P>pMcOlSS7QuJxjW^IhauJjJ3t& z67O|*W_H3=!#Hs8u6Gvj&kmqlxN0>4+Vdtf@qf*N;1Yj)V@(wA#eqQobcv{L`Ekxk z=}mi*G`X6C+xZPYIAdj`#9^BcuMnSTcUISVG6b8AYP#jg)^vTAA*s;4&U;TvR-UdQ z;k%npyfxtk=(IcQ>u~h_n+%E5 z?ydRlR~*`T1@%t-m^is`-(rxQEf$W zI{fLw7ea2nx)eg5uB-hF(rmlwltH|AH?f97r(0Y+soXtCF?`DNG9}hlkHhGjg$MY< zt(a`TbW2vq-tDy+byjHISB?8`8@LJDr?gj!vy6Jiw z&qn0!3jmZv5l+SAw&P%T6JTlpfJ$#IYU_&LNq<>ZQ?9EH+~Twv>TS$ZtuBmuQfU{# zeziyc_4}`GFr}7PNHqa1>ds`k7yKbCu8lh*7@-b_2);pueT>` z7Q_PGYw|`om4SHY$H~=mJ@*rz8e&;Z&3p;Whhtun-fj-g8qW`t3|9B{UPuT;e933S z8x&UWcTlryp9q`htQSUxwVdelr;`kQ(9qIhg4aSC&SRXVXYS-!eVR=3d%48oef3n`eiN zR$qKTRbi9Vsz5Dg(mTUrU%YBqR9W|A^7xNehW6wJ7rq@C<$*^yGPhX<^byNOeYuTq zg=r#AqP+=t?eBTbHWI}6lKM0th`vRK`;* zFMbs|AS)5dA=$s*{cOaw>v!%~3=2&w<+srZ{D28`lLBo_;sBPMVXg3M9U{$2^99)- zE~fMk*bY;_)*{?_Vx2xrG9t6{VJPlc>(b_uNBs>zJKjv zVDN*MlApDYkgL+dhT~6e)-m5x4eHqLqaZb$oA&2E1(5b%!dkzr>5d1M120gG_$jQ_ zqcK(XB+E!&wy=^F&fNkuY{gCHjOL)VyZubST^ry)Y1$SxCP3!uK9FVH2DG7#s^>kR znxm1y?Q=75(RZw+&6kmEzHCR2w4?sD*+AP4J%LeP4$D>R4qoeMlgQEg_<+21@)Ne= zhss}$LJBe5ZsNR}B^;Cd_~NgE+;5Ym{xncE&8Zw{0{sABb@(4S-S)flgRM@GC+^v6 zI}&WPhS{la)zzY(*OBd8RaF`X_j>i9G!1X*<;RG z&J(kT5*v^a;rpzUp+26bvX*E$KR&5FnOac*y=uF+`5&dR>veq4Bvydl(oTbJt@>X= zub(?eneK}3;@fjm?8gt0kN$g^e7qu!dxoYBaA!R5M*gXf@#nj!-U5g}u;1DQ0jn;i zja_E@&*m5=09pce01$O#L2di*-T~#l-XfO^24fbY2SUhIf7wE06|U&@0Md>phwElD z7A*r}SfV!o<*s_@1&6Wt)w40n6t15wtCntM$$Ru@8Vssr25Wo=6*J@hV$Eyks@dO8 z2|i6@{n=Y=l!zqvJ9?1SzLM$4*=Sk*y&w$gWp}T00l@QXZ^Mj!{+ILvpvoWD#H9Pr zw*)i>t*@T*fDl+E)6d;f|GKKmddOEYbKSDQDrt9p(!U<%&!@GxAdjVskzs%ILi#a& zsBR-j90ovSTQ(X+pKCxxHu$}7cU@-3b#Ze$!ePfY6bG17@0K{%796eAf1}9H`4vK( z|8)Xep3}Fy`&fLn518m`QW%0}f$I1|0b2AU4@4a1k5eJ}b5pLl*#`7EYtmNBets82 z4h%Vb>DX`YAu@tBq^lga?Vc^XH9^&@DZa*pqqn>k=lCxszylS*)u+LvFIAYGrunr zXJKDHqZpD%i6mj)Y1qVM=NTr- z87Y(Us-8f5$uL8s0=ck#9^ly8zq7FCrSl_v?jRE4$t9VdND}%zkn7gA07a6dsP&KH z@`Xh-e%c_1*_y@5)zdYpjT1}snQ!C9j(YCBx-{Ws?-c1)XWve6(|QJDZjC?SSWj}0 z2oZ33pAwe7z3AFnzOZYtFy_yFR#zP`kSDj}h`|F2rVdKEl(m_J5EL@j(cA4Cd|3-x zMak>F`JXKh0BS&hrm&PBfFYcX9q4Fk9w5o{vP=2fPtvWxeB$(O9Zd8k+;eU8?jQ3jyyqj@vVDNzsy>%|$#44w6p1 zm#~7zA)Do&Xc*M{^^TX2@vslrGu?G@1ZfcTIy=GF~K zLqpAb(v(j_*aCPSQ|psBQOoA63Ski#Qvh8axXz^ZK9n1{qymnAcujtnBw@RRfY9V) zNAEj|IeN^*VLLTmIvN2X=NJKm%Omo2tYJ6NvX3-RLoNU)7dz zSGU-fx?ReE#7 z`wcxW6E%|wwK8)%m-}rK!`>&kE9&Eo3H2t>Ooltg?wDzK8#j+jUOsZ%7Z%b_kUsxB z_pj{(T+NT*fBSN%BmkMr3D2YKXSVmH?In`oE)vP_q=!8bBo%(QxCy)o{O48kcKet$ z@XCh3QLz*c!R_6TByRbz(~ef>zUCFEjuh!?v2Hrze)jg=vmg-APYzm z;&l55!$C!gp)g@j#`}0=VG4C=5~{6}qr^uh-?ekN)q^dGJielAg#YW_m|peQG%S4C zmg%d}MsGvHW4I4H&PH9F8p|&i7{g25!u#s|n#)FSVT~C@fq;?}t{H4Qvv6#Qq;5<< zv_HwaVA`N(M08+JC8<}#SpDFqQ(m+^O)D>aX86$d4YtFnGjW^0lfqWd0y=NF8Z9E>o3oBJ zY@s!$gx~oF4|p3P{n#4UNK=`T@+x@bBdt)4fI_>9XO^* zs`oHne&ZmqB^q*45nMJiu3^~Ugd;NNhrKKPnV`ypQwp`TEjE%m6LAi|aZJ;bB-c0N zfVsP;`W?Z-@kJpgDH+h<^J&mIsL;1LKc;&q12IkI>i0A;jOBFixTRv|rzGTw>RfHC zRbh!&w|+k-D0us+4Z)$i-F)cF>9{3`um5F@{bRRP9((o={XL$Q0lEXb@y#|2V>S~Z z08EvmVq2e3-`(fxjt7X-C;rlKJ>dG)IJACy{;)P*+eU;K*Hgen<$L_^)P%=lG=7$A z)7!1uvWj#v@%_uwPUFQv!s)rR*C)sGFVl(>nu>nwqbgG3T-K4$^`Q(LV@nOo02B+P z!T67V_r+kSdR~VKe;7D{q=)IlU-%Q1ugVVx!KT&|h8Jj|YSvl)`*pRVi&=#)=7<7k zQ>D~yJSk-p9gtfjC{?Q+l9$bQPspCiVtNKOZG}Num91XGpZ{aLIIn&!2o? z3B8_--*4|h3ID<8&OYmTnmZI0DoH7(v@y?q#Wz14;fQjiyeqv_SjJUgG+wSs{D_u| z+hy(TmMi@8adR5&`gdN7iZ4g)Pw@rR77yA(++``!uDRjK;a{_A~1rFIF z6jG8K)A2pq*z?7m?$@jT)3CtGNnlG4c5Yp9+WsQG6yU4w<`PS)vwbvy%ugNn3Stu# z@c-Ahjx4n0*=9ZflKjX1U7PUZl@}fXm8FYKY{V5%5g%Pdp;eUV`<<4CapF3)I zE>|fin{-fz7n4(B-ba^{s!VLoQm?Ez|1C@aezC_F6#Pc-E4G|Z1e=0A)tr-n*hp+DiUdh@9ClBE8_*6e~Hy!*++_X@(d;Ae_=8ncK1IWoOwjq^x<_;VV*M_ z1K{XC>>NSX_D7g-kRbR;Z63do`=np~q2788B}#p%DDa4T;=C!<9#gf?Wy&l<17|lJPd!jn zUC}sk$NTEu+L|b46oP0as!k>E(775Ue8a~y#!R@o%q%ae>o36PE~|rsh4P#=VTJ5X!+i@C8t0LbZUj-fbPfk6Y!4B4 z(chu>>l<)w{adX8TtP#CExJZFhS}q?mO4koy7r7>X-xxr^Zx{cyuxhy|K`5AI}ldg z`+q0c@_)&F^M5L~v_P=_Ulm*4-jY0hwWBX%!y)q?5E{5aL1(6mst8As7k}};0761@ z>F=&TeyCe1NfA+%@4b1)q-2&=SxldxERSdPO+ZWaBZ#En5l!7I2taRRYDiLt1&@q) zvpCu7J8@g%#L6j`e3k!%6}52^4O|iIpk6HX^i_OPDE@t=z0SeBPmTxb*d^q9#QvB_ zUru26`xmuNMB`q^S1f|zPI$kGyl4oAX_s?iDqfOZ z>0OHhT9;Oijk9U;0XJvyMUvSav$Qi4O8nINPFQ!qjB`KLe=M8W6-KiM`(GMXBW||c z^-{_dGOO{bmhW%V%A6W+%G!s}s@~`kR2aG`UFNn)Vj~oT%NSu+rtx*}4d4F-9;LjS z-`3#HkzvhSiAog3;*F>pR{0`%>Q>VjmMH4@95}s$mOjm<1lem-g$b|( z?deZI%3T!Yuo8XO517N`qc6kmB<1*W@sEB&`eaYHJ0nSOP;!f9!bHP+)Ly2>2`f%A z5>FhT%Nc6Bke!tF`7@RwDeZMGfIOt=;GBJzxUegjT`OGyM@VbTF2v1s*zpAFy809r zw9agmsZFS2@ZdpFUtq6%e(8NWjnSK78YX4fEZlZ4!U~^_;~+$eB~#2CidegGH055$ zJJ-ceqovzrC4MrUsM-XI%Wg7UOWq1Ns~s!(`{iY2Qnnb3&`NbN;|v-#FQ=d~!61$O zCF~u|3EH5wo|OYLWbfjC^}`?YqG*J2MW3=a zqLX2g=54)n1wD zn(s`*8eAEk&OJ{sJjun$myELMVwn4`c30U{&Z!xu0D8m+OLAu0E{t#H36+|Cq$8++ zDe;xDHJ*16D>gG6#q3*UdfsPPEla-3WOjwde|C3z5n2c_Qjc7$=FxGRwEj7fx6dA9 zOw+FCRaXkT8wEb7w|kg&y68T6PmG$Y$yRsn|B8;O+?KKu2$pP42-VQ{e$k#X?(Yur zYv%W%Q8ZNOFg^`GbJ)J%MEBNNT(4D67KY$Pk@mu%ReU~;`hiPCdJ3c|h2)tfXpxTJ zZ-yHT(aND(RJMmWCTf*`)a3gQrH^TOBGbrsce9`P7&fo6E@bJMf7a00jqFAh3ZXYNr=H){n%R(2{^Hxhoza$AVdmCcqc5(6mV zPHZ;swzLu2uE%HD728l6)T6+kk8Io5y`@hgnH|h)B`eaexiezs8`?(a051F_W&8I3 zSMDEly)TdUP0jU%!m_M{Ts&nBoRXu9od6-4mZ5x>!R32o`?>iee2&ok>z- zew$e=-!IHHPa)&w&vLbp$940VdL=Ckl|xPJQfFZvc0wXamA9O$EeDGvg`qr4)+)*r z$HT;~Qz0DMMwdFCBcGIu1T+X0kI*#Lp;nkl+4?6YZI&~&zUbD#XE==X_a*aDPM1fW z_y?8I(nbI<;64mKQLPx85Z$BSpu9kqa`iw42>)I>1XIoPXuPg~F!wz{yYDvkWW>f0orbnE!&)9UTH zV^^PB{Mc#|mlv}s5@O6jwOefjdtIpVSUxz&;@)kIRVchAzPE@Tb_f`MT%E=MNN^y{)Vti%_=+6$o-q0 zg`9&#Z9TsKsOz}4CS@vOprRfP4M-A=Q7L&)ZGNfwV%)FBf0OccdXvIn_q3RP(%Gu; z8EeCo%$>L7Rkj#+zmDVbejZQ>*I4qU<@Mhl%V!M5WV9r_XCvYeH zQcUSU3B#7cHG7NQxEh$3N$#<0r@~&k35|vGZJ7M@?vY>K z%T+^UMEsH`mc-y7o&(X!?v{XU!EN7A$7-Ve!)gArT-VQ=FP=yBeh3hz^6sD@E}eQ~MpK1??;>I@la=-}8su3jVU2UE&M$ zV;fkyo=#L|iQVsiRodcG4n|DAxdYuC#_e-?GX0!SL-CiWj0?)onDh$O05iU7m`ipa z{gcK}=SRl7>Qw=P|0Y1VCfL58`cH=GkBL|p#NKJ_CW|-6sKXln6vV7Ib?yR%w;49In{rT;&A3wd~GMcY- z#;CS4b^a~Nqh74DNpP{>R`U0q-w9#wX^O9)LjZL>3eNuebvdHCqJn0#S#0CFKnE;L zIw#iSy;_j(5;C61x(cQ| zuK1WzdA#ck1El}QvyOVr)hf05QG6c}39f60H*PuoJo*`75hFHb1+xa#mLl(+QE zTiwi-+IAU~TXHeVCKuLQ0llcQjMdb&^5^M+Hv`)w@mr!aMh#Ov@H(eAlOX91)Tw@2%CU5m%FuF-u`u5w+1GvR6s^;ilO+z|qdWmsH}Xh<(uGr^wua@4#B9)(@B331o-EPm)xD7dCJ?ZR z7XNo9VzoFQ`H}p~R&azN9=XGuTyor4EST=`$8{I|J(+S#osw5SNkPpgzJAjVdf;s| zm;#m~d4pAkpRj+Ey0b&P{-U4U{Ky4ir`#w$DgDoR!rDTsYSY2OD2H{zMf#fm3J?_kts=%KOv2PfC4>UVMjoFB`|^621@ z!O&?cHvK2`*NfaTjv9{->;jlAN;`H7&autD7GkVqFW9@uq0yhvk5S|(Qp&eMGyHAU z`k}Hk&)AdVv>&g|njlZQ`cqYjs{KNb_}2@zr@6Rm3igEY-K*+$ScO#z?>Cp!!U z7mS`!qcUZVSnE`oRG~}1j?KPN^5oAKE~`cwr4I-o#jc?Y@W1$;TtsE0)Ni~P$RAfP z`e}icb%tJ2xLk&Vg>wU{XU zB`*CCi3^ieYCTP7p4rqiZ9!4?LsX(yk{dOg_}rv3t6LT-ny;A#+) zo#Stl`F0t>!%^2fHEWWxUGUEAc;aS$HtusM{30-sY}=tqaZW_{WgRyMf?KsdSlZh3 z>6qinv!=U$w?0e_MIIe_o5(=`=@Jgb(oUPwgK-DZL-TslAe)n-aQOq9l#}haLzUq9 z{3m5Zpku#H&d#DmQo9@5<5bIf7fnqfZuc%To~D{x9G?k0q4KCk7$^|Ojxh7frg&GX z<>Og-+kolLn{riHOFzihBimnU58x(5!fPw(DN~(un7CyT20nT!PgFhXR}L1wq=v|7 zmk>mR4KYir;l`gNqDu%sHgYb=%OAoShI0!(-Y(qqQm=?@$ZE>iO*|Zi>%>(eijqaw zutIshb@4$AYs^aA!8LFqw-F;t>z6XB-$f-Wan zX53h#;~d-hqtlCr9vd7y7N;1E^SOaf`;f?1`mGK_;xu@?(wViZCs`mP8zqTqo=rso z2(`1x$DP>TAHMP12HDnp7Ej?gcv>;IW7W?=)rE&P8ln24rhxW=USDFBdh~tIH0kW3 zq2ARYulAYyZ0|Ld)w97#TS0W>1=@?sIW`6*@gKX_ANxQI)~9vZ8#(1u#FbdgMoDs( zpfp4|!RJ+KiyfN>4#zS~mZc`QopntgFu8?Q2^INutVT4Rv3T`6u_ld;Xs^8f?D_Y@ zUj6GNAb?M)^*fT!NnSMC0DVnv-B>XgDh}syv~Oq(jEzf?@a~D5x2|HdcMbi5#2q5D3L42*FSY+B()iez(Duiq@l+xsAMaMEP$;X+Zl$G14^^bFM=5&! zVB>j^U2im^6m>&D3BACv0u#-2W2D=(e3Hh)R581g#ca}y@JT73P!mDzI>!q333%?5 zbyFZY|DLm)-lXB-9>enB)WZD>!H*xJ-KZFQpL3-RVw#$3cSR9?=8`vZ6b12v&bF>z*Z86<`aP&Mh>3qIIm_0!7mhV^eQ?; zqNuMddvnu1ovP(i#1c&koAO}~4EMO)p7Jk&ZCR;fvz|yaWrfj@gAG(vfBayC)=K;1 z(r_p@`i(1O@m>}U#v(>b3#<`KTfBHs0oZjeVB(7#%ZVu{!i=u`}zXR++${#6H-&LnZ(`K{1D3#J>2u*jQXtz-uec6 zmWzxB44wU>G)?j!x|(0>xvwoGfyW}Pg<6)U)>F7WyE+pOl<7@3CNoWG?4d{5R#t~! z;5xi{&FdIRyzL0^EXenqTgBpsG&zrF*oV*FR%glMP<;xX^>TBfMgf^dKs5fqAd1vI zPCN@L-vb=%U7b=Aavt0FTP3OPFxXimk8i)T)pxC;Rwa>APbOA{`f>I8dpygVQ}8qu z+JDbGEsPa~SdVon5f*aRcj$^^mztU_3BM>BQ@i|lcvo|XeRWP@R~DF4K`_i$-;S!s7iWAnD^|#mgsRykojfUke5|N?POnz{o!R>n+a8`t8zIBhxjOC#dc|+d z^`mS0P-j33%NCLA$ND7_Y6D9k?fTYHU0i5`t9QlvOqJNnludcHCLis9+JDS7aN_`K z+yTXjp;XGnp&^d-@DB8L3k5aQTRV2hVDUfKlUPeVY6x)vjkQ}|NgZbkJy)5*#Z18k zEA?8EBJiQFoOQA5YZM8&^r|jgLgiL0RkgMe^{l~fyA2zAN51ArrZq%4qSiYGVqN96 zhf9)uT zLZp=6(+G&eCK5uzF!0PF5!r*-6*xBP=DHZRO>@`Eu~#2RGX8cxl;=?1NBn$8!12`& zbk(qMvB%CX2Ecu|eA-HKKJal!rHI#ybY?wA$#>_lr`OYIu||FH(`#udM3W^7C?<5?|Xf29g~yBsND@y|>rj^tX2%(20uKvXz@7 z`p*=vMp;T!W{PxiIKD5qpXOp`V9`=H0bk*Kw8LEbBlKP7Kl9jm6~KDK;Wf5q{_tq) z%%h#EH00^g=!DY>#tVZCrGK8zXJgFyh&#-W@<;FU{!jH7(_`rsR3AfuE~f!>k-{C^v?1EzkDsO3HiFK!DSt_5|$ zTS2rLuU?Rz*W_9rXn1;j4x5x1CVyjrkAEwK2xdNfkc*a zk=eL?#EN`U#kslHD;46DPL5IUm`_*Vt*p91QV>nXWtrt>DBmJ!(0;v}b_kGj1Ft@Z z5esrPDvXHGE-$|9d|G?a1^#|$y<$l}><9ePYGxi}`4#+ny>``h)O%6DHTm+*^2W@D zF>SCLJ0;l8y;`HbUI=V|Ku*;)NuU@$I@2mVx)&=WS-Ay%vTsu12G9msctoUvp9G@7i!}4x{B7jSzSo zmO;Mbpy_Hg3!0UB2-34s9J$F*Y9*9|b@sxz+aR;|aZBqm$e zN?(87Zl!>zt4~yq^^qBg=3*`TO^HEv(mZ9(xco$z(nZGCQ+qz4s4=g7}S*YtTT%D8~B7+Q_UAjFA_3dHljmprB$pdT5ce zFhlWt29B|A?Pd0Z)*;m_5v?p-76Y}46S73e6!=;9R~g_^d(a3m0akg9sq>RNvGYCg zt|apZUi%tm)VdJwb_+>{{;>mGG}Mimww*hF4q|!{>KzQgN<08Kvy&53bMr)e4fov< z`hcHFY`s3#lVOOk$uXGPDfc@)95>O|by+0ZG-y^bp7k#DwI#G_O9Xq*12^x6vaDCW zPQom#&-uJi`%rl_+^R5NIT$OK%;^FvM0q4O3R^9oE^D2MF7sWY$`5(hVo^@}r*T?hDPQ%{IFzW z*|GUJ9ng2j)6l9Rm%6RPdsV7F^4*=jTqEifHnCt6b%t5;k&4DenBxBX@pdSE{`z2wxp(y||_l>B54D@17v zME2#4dup`)QO_0D|UXMCV8pj7Pb?Af1CbiO34 zQ)e7J)(mB`epuK%(Wg@>U~SG}W=yUJ$PD^tnJkEt0uSicec!YrY ztoF;Ct)bwoPX;AaDtS2!~r3=y=87Sg~ktwfhwntE%r4>E;zG8n`h>0Qe-me~e9cO(!&bboo-EOrden>>*W+@l`u{q!n zpI<1P5?TPc{FonK8cwYeE!vz(ktb4mc?R=8*qF-kIo^fzOaoSNd$9pIt5vByirmbKGHZRibd?LB(t<- zPJt7iXMs9|obXKNg@~%H!j7~kJj_-+OpC-1leF2Z`$c@aW-X~WDmCoKWENUgy*`r^ zhbVBio_=^k`9M1G^3>!dxx7EXPlB)BGr8!xc_PYW(v2x>j$(6G+uH+*J?&xd#}BE0 zKF06sy7AR?S{HY0>ryJryrC$f88PhD{hzQSx63z1_+Ze&ZYC-oku%CTLp~ifylh0$RXcJZJ=J#BtbPnAc2X2pqKDAw*HLq(m zm-8Ob6pWPi4p-$F4oAB~Mxe#;7ru0LhaZ+Rv301Aa|}*xUUT{*a`MgMlFylh6?|ix zSgco0K15UginM;&Ux0rLa1i7C_VH8rllZ2Hr<^&IwAk+=q=#Eg3>eV(ybTIP(~8+f zo!M|TDw^yq59`9F%Irc-#`{3c9U05n0wNaWChoRFZ(@3Q*z20l6z21V;d81H_o=ui zDJf?7;gVg(oAZNja&A=kr#aMYr9z~7&?WRS>st@Kt>;!k52Xtmoi59#?4awbRaP8E z<-GFS*~y=tCgDPha;zbq(D!5z-@&-lp)3i2#j3hVC=U)mLw%gZ(WJH{3?V2ovz)9Ob4E8*62p9#Q3 z!g|Ns!UYFuRq$gq4Dj&D+S#K+P19td!ci6j(5QuTZ(Uegy5@&r><#AK&KKIv^ffKf zRl;{Nf>-RI4F-~pE8|ts`}LoTl|-BC=lc3Rm8R?FPQ8opMMX9Ix5lLFgCz>ZUwrKF zZ<_L)vodcqJZ|Y)>|YU>FG_EqXX03~9P`bQK{N#totF}JpWfP~u&YzL)ITpWV^1#W zvgCueyYUrDiGlW$x66Y=FRKERt>yfc@c;3FC*)ebBmGdrGAtn@`jcy@aC;kkFswhe z1-h%qK$RD{#s5BF8nypNGO+@Mdg0o}e$w-2R=VTapp}Xu`%O5HOOxn?mz!?i)YvRT zbCt#RP@7pb|Gc;RfKQ=HkLXy4ngDw!-XM<#n28@zX3`t(v@xWPW~e5oO)pgM>Q^zx zm$qCopS8IWWzU8)cG>;PA2B z(X9Cvf-`{Q>%dkqs3yQz)btdkwA-^=7{&B{i`exF1yG^s;kEGTsa47^ySMR-cE6xR zf0BL=#eLj5dsg(vwCCF37}Y@%91fm?t{mo#hQ2_E%x?x0y3GA39sj1iUhfiK!n*)* zO*!j75}0RB1S&ekQTvd{b7#R=Cv{OY=y0EgRlzflct1f$_0Bz8O1^@a${sx#MoK9p z-!!h)o^Txtc3*v>~tBE?7ITIX&2j5Pct@kd(8u?*pjasJ^3K!qfy3g%Q3c6f9 z&$~vLl&;Q41m+;quYn#B1Ig5RN&~r!C#{mb+zLsch9axWebQoq=+TFVgV;O~{TDlh zILGt{t8>xaU#&i@)DvUxHS2LJ-AJWX?ne=(xa>fkJ*(Shw_mUL;$hBn!*2wXl-(|`Q&PfvU&8_D-)T5CcFI0ZhVG00bUj5OnP zu?k__xhOf8;UbA&o_btq4a9goWBXvXt5;5GV)N@{+u2g_F@$%`$+-&}RUFD=*?luC zEPPB&TpB*PJHl<(?X9zSXc^HjcnCKo=P?N(=`g(MSWqv|inEBV-v%x1J3Ve@K4_4} zVaweA-iQION+ei+Sv_r2@wf6sftu5nk9%g5@P%)A-MY^R z>sjvx+xn863a2GZAZxHjuVK4m3eCf@?O-5R!e5vc$J? zi~wsgPoBSLC;3>AB}8JacR&^=e|dgSv=K{_I59KeYP66UB8v@-i0^0xtlB_>-$jx>u8?+k4HtH z&XN{pg!Rh~F|?Z-?jN>*8jr{1N8CcB6m)Y_I=Nyz_v&%RBgjR$tn)_e{JP}Tqp1FLlnF*2Xtx!jr1BlJJ(Mi+RRPH<5%6 zQr6suDV;YV35f?|KCF{54-4U;QfQ)r-VZ=HbDx%@r~7(G4)~a-x$+dP&D~~nX)MMd zMLOmWwF}X<T@_1eH{2LZCZynrbB5N6p1+H&uGk0N)oujv;oT20J- zNx`=!lQoz7#+dw2hN`_dY{=cb&jsg)Y}}&WjYBd)t+u-H;V5=_1c(o`O?2_@E-H6if3W#pz{7Ht*LUhWe7!K}U<=_tsPx=?y->6fEb6|Q zrRg`0_-uReQYk)NSX){e5s_-|U8X0x5_T2QTS$6s~4M9%jESi*L{iHLX$9bt9*SOh(+btmQbEoA30cupiQI5 zCxsk}sbVDd_A_&Eq2sJ9w)$JY9E=YwekeyOdunUDO-ZMV{Xh z$&6jBG7Yt&#z1Egvt0JqwoRVzvvZ%>A{uQd&H!uN)Lb-+=@Z4Kgue6PK{L>m#w|KU z2dPTTb_<>(fMkZNOqdr~fAoM#b+VU9zrZpO>|sl6djE^X!e^z!u5cm&O3VJLy+F}1 zQFL5O*NDvR+RM+y{`52N=uwoiSmao7fXl2Q_&S+StrX>>moyM z$jR{yVgeAu!0v5P$Bpq13_>5rG7HZ{hc&3|z#m67EX0LRdeyvs^uw1X5Cyq?VMDD? z-IXiYux@^>#EVw4ELrbW|%-ntbS6*G7?GcMKl)62wHltXm_ z+m@~4dFthM6u9JP`E^jGk?O)_>yEu``GC1>Gzv9njW_$D?;xa^w?#mzNq+V5@(+?b zB|>Lf^%ZU0ZU;Dt91Bd9Tp5F&C135e5hRoM&R-Z=+^DdAJiLK~KT@-5(RXLkKm(sx z&-{p?cIzpT#nXCM+Vg&|)do}Q`BGa9Rfzi#kuZ)W4!2@hPc?RN4u!ZPQ~!n-5F8$Jmd?f$ zK_Efv5S<#Y9&T6v9Eene)i+3&*EXwj(oxVEl;`ePk;2y3B+oO9&C8))%{4){RS2_z zA=5bx42sLU^BV>Z0=lI(`Y#SKUN z=O>@VybRJ`&jx+B=FU)4@-_Z!uYL&EtILH+t+?zZnI`gzz+Wa6;fqhXok>+Tu0h5c zxx}MMd~*_6*VksY6wdZmXmdJx$Mbi&cwC3@3beg>3XGpZD_0=SLq4qUMD>BD4Jw|_ z&*ZMk)$n8MsKY=_HIZROLNDt#Adp7FbG2NH83>wAqO#oajJD<|OWH`)ah~PI$ArYn zqxXp}HTt;geB)FhqeW|6+$>Uci&aLOhV>eZ@TzgD1dc@Gss4A*G_}6ps;D5(^rG6< zSft_WIvXq)U-rv@kdF%hO%k$>1DY^btMSHKPCozaXKJ~YQ&U75q4}9$SHMz8y(448 z^FGRV^Yc~Yo6}YzHE^QVl2q{1l`ELv81FJ}K!|p-5 zH1a6)%f=b?>5_1mH(U+T1Jye3qzd86I5nZC{1gBA|ue7uZCT5ILV$ z{Jv@ZbLk^ceckwa?3U6CUo9vXe!l+s962fCl^|b}IKuEwaX}UaeA1j+ZJyUvUFELT zgmb)^{GB4{OlBeL$|f|BZNU?I8s)Z-W$kt^uNSL?R+{w6oUO%&(Jx2&n7^FXSRQvC zYNeyunVv9=uA6Q&-kHWkc zu!KF5CK24<^-t<{tRtWHC8Jwwov(8evy@56;ZjVpTCt)9Pt~Afd4CxwQP)GNU0BdQPoUPeBpub|qx*|EuhtA0u?uA{)L z9?vSC@V>9ta#aH5Yr5-F)>RM?hBiYIU~2;t&I#s)($g2GSXFTSkhh<;c2g`4)7WMj z7WB6>$YEOTq z0k7Vl?t-Eh7GRHkE5O$o&};Y|pZuI3K(zq(0Re@!3b~#yKVNbEC-582$sY0lQ`ncs zL$&|^pV3SWKK3o8(v>ZtOQ;BK_Og?uG49P0K1g9?JL+EUrRdjHXfq{|y|QJU7WFC7 zO|}-AQru`!Az8n#Gc#xAxc~hA@fh%X7@>+JRFu?PO3&Fyl%GiT9K)5 zp=b7xjHyp);uWRH((RrLBLeh!StL18*ggrfuB`)6SKI1ky7texz*hS~)%*)JN8s)vdc9~q`5I??_A&XJa#Pn+ImCi z)!Qp?*Q2gxqA;>;S=LQt5fN30P}5KFEaTjPPv=(Z--^Ee?1{GK-N()Q#=k^;2vGfa zWJGeb5}76ThoBO|@cW6)E9$Lt?|qIEy;C^8s`a^4lx@HvFfyE0(-N459B0eyB{7`0 zox5b__1=&&lHZw;XyQI?uN3W-d+0a7M5ZSyj%%Joj@yl`M5g{OR%lBvmGkV=eEmPG zp5Gq3-@IWv#j&f#??@J(`zGv7qz&}%ZW*)7R7Rd&F11gM5_@)$)TScmSb%zEJWI8W zF3swwiU^E8bl=2aLt}lx;^qayGp9~ka zwSt6N`KnR{g~j_vmn^S}X&DKUo3FEoKUa82#-!%)vT_Oix}fe?6M?gp)&4aTVx_4! zqI{38vAlFL$gK3{h+$Aas$7~|Wzn@n`|sqc8{^^kmc=g(J~HeVWVP$%rH|=WRjDFI z+AE&V4X$rHG}c{Goi(;Qz3>zG%rhQZ@x;>)TH~KreoI-65 zy?Yr$EWAT2TN?a{J%a5^4g_3}Hu#oy&bTZ_)UwcNEWEGz`{jSieb4^BGqO*N=tOsUndYw|psd>#b5j&-I*qMO^o}Y}vVE zgX4`q95mN$`+tM|zEP0}4NvYGDJehppO(a#V`u!D6>ffM)PMWWT;=8-iIYwP@P?|+ zqA<%YiPJ|$qTyMKV(QOZH;0*JUDXO+)1=}Mlr;0&NKNfZXM6{V8#heXuv|cJ2{P<0 zU42hU>26h|P+VMUrq%!1cW6TV%L!UtEBW^AGUEdicD@BVi6&dNr)RGDZ|=;A;eeD5 zx9x^&O*I`g)rPL9l|0d!wHvK!?|W|fEuzugNJGQQcks$Nv#l2&#su}9Z9UlT-C(31 z(Y$2i;L#yDIeiP2!4|D8cP?gLj`y6ja`ubg^c!YQ|-2PrrWOVsrdfb zj>mP4zHaq2gYr%z;aC6Mc`QHDZ1y89c~6bDb!y0o$R=h}!mRz2U+{yypRZ|!x(%(= zmiw^&pw+BLSG{;g@3Z-WQsZB%^oJ(0l8R1E+0SknG~PR(@_o#GWU||S{9c;G$b^Xa zie2S}FN~u*#Ps5qra7$%(QaAt?j!0W zjG*MQ&`!trI6u&=JI5V8psnu0+FrY8wczo?2btMTm zc2k>KLE_54hBx#?Hv8vJ4`md2H{V=apcdF;Hvj8N zqs%6w4ES}xE3|^e<44CnC;E@abc}g?KWQp+Xl&T4i)nLZ;%sn4(DU4c^PyE`vsqn< z+bQ|`#&Zt#$AsMzFG>ua=)EseuI1j`KEj}t;%m| z@C+$&v(v@kf{8cDB(p_x)B54TE{hVUj5BWX)j*xRW={G{n)p@hO1hx*!9KBX2$9({ zwyg&V$qCClxw6X0^xNyFlyPu9ZwF3}H`yFT+6a zg!(Z%C4$W48An#`kR38H)D`eaATfMGR|&hEm&YhH_g5%sMcTM)~Q$U;J|FWFBS)_w$=DiRZLgA9yuA%N~=sZq#l$CNay0 zl5NDLGN%OuUR4DQa%oQHBKq)12a3>OFq@CA*1>M!NTAyih(Mh0Gh^Cq&3h2|8{_(;_pg$4IQ;rC3*?4M%dKCjUyRMH^a zj4M>j{ypjpg!f@*Q81aC*0LISLMmH=GE{Ei_MNqoQ&J3A;X!Eng zf_ouJPJmb2mZ4b%XMPhP7qsT5LBiIg&rO=Fk_9GD5(RceWlhZSGbG&Wf&5!A$51yL zp3+RFBl>~^ALh!raIaNVdkl>}l5}xg@7P|2B&uuhnu=*2){lk!HYg1T6!BITUBdN~ ziee;^hih|RAb)$`Mm+jX$S{B5%9JVz+Df{kiA%`ZE!4@0VGfm`t-w|70)X(2+$6PP z!lwWT8}_18pfEXbGcB7+f)LP8DaX_!dqYgCr|#4zOo7XnQwkqM!juuWO2Sbl zj>GCC{o=YE(UDg;&hvTfTg)FntYycNddsx7_6hXC`Lt3F`ZhCbJ{Sk=SU?3yIs)LL z)Q5Lo5OO&V3Ky7Pb}h7_W+ux}dv~FSiFBA6B%yG}&F3H*Pg5Ouk6AervjAK^i~o2E z|DY?#g;IRPu)l(aTbDn=>LrK3tC`;RQk3x_yvj!OePqBuk zT2;q^Wkj`Tn2{_jn7;wf(zg!~V>DISVWrS9j|fn}gO9cU@(5-K#l~P6sn+FkP*@9s zNF27_vDGEc``7}lD$^B6(@W^}R?g}~Wh4Z!47!;`Bor?b;eP|Krkfh z1MU&0U&e{+8a^ZoJROj2AZ6)DEmz>~065Tuj^SBZ6=0R+l0L#Uj5+fIWhvL_NlsJ< z0!jDtafQR%AJ@xca+$ve*7Cm%1o0J~bX|%r3;!`YLE%m^c%jdkc|o3;2!%-s1m6{@ zWBd{Xye5(>CO-r*s(^TL)=lp$3};BipsD8GOPF0 z@tu@&SDT+$1{Y)?6t$neAaGL7P^vonNjI6E@wH<`zy0$lQ$<^J!%O_P5-&Dc~v`+qC1&VmSjD`v)Dcfx-bS zxSSSuXp{+Lj@BOHhhSMt6beBM=2+zJ&g$pio$M{c#C8SQ2SE4t|5U`IAlxXaRX7~d z-NS8wG5Lqb7P!mrqT!2^^rQhvQXB3pbQg+nc*zGql3zfZ6_+vocnsy?m?Ejx?xx~@ zh$aSSZ6G)3B3W7F0gn9q3D{;7Smkt9gg{fAPbOfMiDO!gr|r< z;9fHRi@E8Sxt^4cXMYc%-)eK_0FNtJvZZsyaY%-Mq<7a}1NTzp8iaeoq?%8JHFJ_1+q+ue>5Q9xAPc= z1y8Ng7e##aI%A7I<2qkC48+YUuD~HWsz4gV7RH(NFz_-96%qh0&IeWe2@Ya{i#Gbo z6>@OG^B^C2dMK6gC&1TM{)nDn3REmcl=I|D6JRLiP!azh`0DWGD zevhDcWrqvcjLv`%@{Bclli-Wcx&||7NHXALUXp%VFzFoxE}eM;Cbno{^-!``;f}?Y zgYZ1I;j~gJTDZ9At2Y0FY7g%qVh$3nY_#(d`}r zA>b&EJ*jAd2JZg7Oze~ZaG2{x$&3yk7G}{JO=S5;;<+vjdGYZQdbF#J8Sz#a@H4l8 zON*Ge>*R|O$ir1=G-mi;SHx9`3@$9c7~ny@!KaUGi?c@+i5#Ft5K z9wP^fj`F`jbYvNkvnhO1C|0jDhV3fE8vnVAI9^brL0vHM(C;EQ3QdbR;Euv5hI@Ir zC)aUtSCNMpa9teUE&xWMDcL}U&M{c5umeJbA3%a~I55HL)iE|EJ$03%T7SECmn zrn0I%by63^I92cuZuW7|B@J0dHZ&AVJds!M{>2nSiJ%h^v5SMl%qZ?)gImlP^qx zX^Jz)k_RGQ>tuFvu4R-XIQY=LkVB(9G>5rC9$UZ(bM?O*Z2RBJJ(>9S7x;z&N1qdr zk6#2g7V=L#j>?lbWlo5?Ia1ws(h=M9!O#hH^AAx6*aGi`@-5`PYo0&O4w(3%GkQ&` z;wtcaMbz>cJ^`Br@#I*L4;U)GC;cJOP*8o!SBx`25ZzTRL`}i0+O-Z-$cJI8N*q2o zDW(#H|NOf{5%?E4yud*|UfbR8srs+lxf<4kFZ{1+bWW2wxaOy^4{JcYr$LTd&>U>8 zP2jlK4#c}b$@7M6Ax{MT-6v-72TexwfEj*H@vJ7b^HoOd)bX|_Vfg@5ZpyXp?f{P$Yok9jM{Z~T;{vts* zhhquOA9RSa6p}~WR)|cZ#s5FIxquukx_s9vkGE5Q;e|CcIG%c9_~d^>{@) zZz8Kt$h2z}skh+tUk`W|$BD_RIm{s5d+e3|mGRA4X&^|8x6+w^8L$`joic(NwwXNlAKV8-z1g$I613-!A-7}s`Y1oqP2?Db@YY@aZtE>DDR?5$)>qT&_ zqv$Nqp77c3kBP?cMCbVE%7s0X>AQM*!W^QHh5y#)O`1#v>h5kQW4Q9c+F^?4DJkGM zrj^-t6kY=7JvfJbGga`;((!%VlmZ;1_2hrBHO=TIVED1Nri_4BjYeb(T%ap5V}>Be l{QRLYp#k`?7+ZxeLZa#)NcT2mpMY-=>z%uIlx?FQ`9B-~iz5I4 literal 0 HcmV?d00001 diff --git a/packages/shared-components/src/i18n/strings/en_EN.json b/packages/shared-components/src/i18n/strings/en_EN.json index a1d2276ba5..ae240b32d0 100644 --- a/packages/shared-components/src/i18n/strings/en_EN.json +++ b/packages/shared-components/src/i18n/strings/en_EN.json @@ -23,6 +23,12 @@ "left_panel": { "open_dial_pad": "Open dial pad" }, + "notifications": { + "all_messages": "All messages", + "default_settings": "Match default settings", + "mentions_keywords": "Mentions and keywords", + "mute_room": "Mute room" + }, "room": { "context_menu": { "title": "Room options" @@ -44,8 +50,63 @@ } }, "room_list": { + "a11y": { + "default": "Open room %(roomName)s", + "invitation": "Open room %(roomName)s invitation.", + "mention": { + "one": "Open room %(roomName)s with 1 unread mention.", + "other": "Open room %(roomName)s with %(count)s unread mentions." + }, + "unread": { + "one": "Open room %(roomName)s with 1 unread message.", + "other": "Open room %(roomName)s with %(count)s unread messages." + }, + "unsent_message": "Open room %(roomName)s with an unsent message." + }, "appearance": "Appearance", + "collapse_filters": "Collapse filter list", + "empty": { + "no_chats": "No chats yet", + "no_chats_description": "Get started by messaging someone or by creating a room", + "no_chats_description_no_room_rights": "Get started by messaging someone", + "no_favourites": "You don't have favourite chats yet", + "no_favourites_description": "You can add a chat to your favourites in the chat settings", + "no_invites": "You don't have any unread invites", + "no_lowpriority": "You don't have any low priority rooms", + "no_mentions": "You don't have any unread mentions", + "no_people": "You don’t have direct chats with anyone yet", + "no_people_description": "You can deselect filters in order to see your other chats", + "no_rooms": "You’re not in any room yet", + "no_rooms_description": "You can deselect filters in order to see your other chats", + "no_unread": "Congrats! You don’t have any unread messages", + "show_activity": "See all activity", + "show_chats": "Show all chats" + }, + "expand_filters": "Expand filter list", + "filters": { + "favourite": "Favourites", + "invites": "Invites", + "low_priority": "Low priority", + "mentions": "Mentions", + "people": "People", + "rooms": "Rooms", + "unread": "Unreads" + }, + "list_title": "Room list", + "more_options": { + "copy_link": "Copy room link", + "favourited": "Favourited", + "leave_room": "Leave room", + "low_priority": "Low priority", + "mark_read": "Mark as read", + "mark_unread": "Mark as unread" + }, + "notification_options": "Notification options", "open_space_menu": "Open space menu", + "primary_filters": "Room list filters", + "room": { + "more_options": "More Options" + }, "room_options": "Room Options", "show_message_previews": "Show message previews", "sort": "Sort", diff --git a/packages/shared-components/src/index.ts b/packages/shared-components/src/index.ts index 49f6b4389f..0c8db1d6cd 100644 --- a/packages/shared-components/src/index.ts +++ b/packages/shared-components/src/index.ts @@ -22,6 +22,10 @@ export * from "./rich-list/RichItem"; export * from "./rich-list/RichList"; export * from "./room-list/RoomListHeaderView"; export * from "./room-list/RoomListSearchView"; +export * from "./room-list/RoomListView"; +export * from "./room-list/RoomListItem"; +export * from "./room-list/RoomListPrimaryFilters"; +export * from "./room-list/RoomList"; export * from "./utils/Box"; export * from "./utils/Flex"; export * from "./utils/VirtualizedList"; diff --git a/res/css/views/rooms/RoomListPanel/_RoomList.pcss b/packages/shared-components/src/room-list/RoomList/RoomList.module.css similarity index 63% rename from res/css/views/rooms/RoomListPanel/_RoomList.pcss rename to packages/shared-components/src/room-list/RoomList/RoomList.module.css index 54798f1ea9..c444c8c1cd 100644 --- a/res/css/views/rooms/RoomListPanel/_RoomList.pcss +++ b/packages/shared-components/src/room-list/RoomList/RoomList.module.css @@ -1,10 +1,14 @@ /* - * Copyright 2025 New Vector Ltd. + * Copyright 2025 Element Creations Ltd. * * 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. */ -.mx_RoomList { +/** + * Room list container styles + */ +.roomList { height: 100%; + width: 100%; } diff --git a/packages/shared-components/src/room-list/RoomList/RoomList.stories.tsx b/packages/shared-components/src/room-list/RoomList/RoomList.stories.tsx new file mode 100644 index 0000000000..a76ffe7e34 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomList/RoomList.stories.tsx @@ -0,0 +1,87 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, { type JSX } from "react"; +import { fn } from "storybook/test"; + +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { RoomList, type RoomListViewState } from "./RoomList"; +import type { RoomListSnapshot, RoomListViewActions } from "../RoomListView"; +import { useMockedViewModel } from "../../viewmodel"; +import type { FilterId } from "../RoomListPrimaryFilters"; +import { renderAvatar, createGetRoomItemViewModel, mockRoomIds } from "../story-mocks"; + +type RoomListStoryProps = RoomListSnapshot & RoomListViewActions & { renderAvatar: (room: any) => React.ReactElement }; + +// Use first 10 room IDs for this story +const storyRoomIds = mockRoomIds.slice(0, 10); + +// Wrapper component that creates a mocked ViewModel +const RoomListWrapper = ({ + onToggleFilter, + createChatRoom, + createRoom, + getRoomItemViewModel, + updateVisibleRooms, + renderAvatar: renderAvatarProp, + ...rest +}: RoomListStoryProps): JSX.Element => { + const vm = useMockedViewModel(rest, { + onToggleFilter, + createChatRoom, + createRoom, + getRoomItemViewModel, + updateVisibleRooms, + }); + + return ( +

+ +
+ ); +}; + +const mockFilterIds: FilterId[] = ["unread", "people"]; + +const defaultRoomListState: RoomListViewState = { + activeRoomIndex: 0, + spaceId: "!space:server", + filterKeys: undefined, +}; + +const meta: Meta = { + title: "Room List/RoomList", + component: RoomListWrapper, + tags: ["autodocs"], + args: { + isLoadingRooms: false, + isRoomListEmpty: false, + filterIds: mockFilterIds, + activeFilterId: undefined, + roomIds: storyRoomIds, + roomListState: defaultRoomListState, + canCreateRoom: true, + onToggleFilter: fn(), + createChatRoom: fn(), + createRoom: fn(), + getRoomItemViewModel: createGetRoomItemViewModel(storyRoomIds), + updateVisibleRooms: fn(), + renderAvatar, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/packages/shared-components/src/room-list/RoomList/RoomList.test.tsx b/packages/shared-components/src/room-list/RoomList/RoomList.test.tsx new file mode 100644 index 0000000000..c5ee9b8b75 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomList/RoomList.test.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, screen, fireEvent } from "@test-utils"; +import { VirtuosoMockContext } from "react-virtuoso"; +import { composeStories } from "@storybook/react-vite"; +import { describe, it, expect } from "vitest"; + +import * as stories from "./RoomList.stories"; + +const { Default } = composeStories(stories); + +const renderWithMockContext = (component: React.ReactElement): ReturnType => { + return render(component, { + wrapper: ({ children }) => ( + + {children} + + ), + }); +}; + +describe("", () => { + it("renders Default story", () => { + const { container } = renderWithMockContext(); + expect(container).toMatchSnapshot(); + }); + + it("should render the room list listbox", () => { + renderWithMockContext(); + expect(screen.getByRole("listbox", { name: "Room list" })).toBeInTheDocument(); + }); + + it("should render room items", () => { + renderWithMockContext(); + const items = screen.getAllByRole("option"); + expect(items.length).toBeGreaterThan(0); + }); + + it("should mark selected room with aria-selected true", () => { + renderWithMockContext(); + const items = screen.getAllByRole("option"); + // The first item (index 0) should be selected based on Default story (activeRoomIndex: 0) + expect(items[0]).toHaveAttribute("aria-selected", "true"); + }); + + it("should handle focus state correctly", () => { + renderWithMockContext(); + + const listbox = screen.getByRole("listbox", { name: "Room list" }); + fireEvent.focus(listbox); + + const items = screen.getAllByRole("option"); + // First item should have tabIndex 0 (focusable) when list is focused + expect(items[0]).toHaveAttribute("tabIndex", "0"); + }); + + it("should call updateVisibleRooms on render", () => { + renderWithMockContext(); + expect(Default.args.updateVisibleRooms).toHaveBeenCalled(); + }); +}); diff --git a/packages/shared-components/src/room-list/RoomList/RoomList.tsx b/packages/shared-components/src/room-list/RoomList/RoomList.tsx new file mode 100644 index 0000000000..ee5f7b3961 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomList/RoomList.tsx @@ -0,0 +1,197 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, { useCallback, useMemo, useRef, type JSX, type ReactNode } from "react"; +import { type ScrollIntoViewLocation } from "react-virtuoso"; +import { isEqual } from "lodash"; + +import { useViewModel } from "../../viewmodel"; +import { _t } from "../../utils/i18n"; +import { VirtualizedList, type VirtualizedListContext } from "../../utils/VirtualizedList"; +import { RoomListItemView } from "../RoomListItem"; +import type { RoomListViewModel } from "../RoomListView"; + +/** + * Filter key type - opaque string type for filter identifiers + */ +export type FilterKey = string; + +/** + * State for the room list data (nested within RoomListSnapshot) + */ +export interface RoomListViewState { + /** Optional active room index for keyboard navigation */ + activeRoomIndex?: number; + /** Space ID for context tracking */ + spaceId?: string; + /** Active filter keys for context tracking */ + filterKeys?: FilterKey[]; +} + +/** + * Props for the RoomList component + */ +export interface RoomListProps { + /** + * The view model containing all room list data and callbacks + */ + vm: RoomListViewModel; + + /** + * Render function for room avatar + * @param room - The opaque Room object from the client + */ + renderAvatar: (room: any) => ReactNode; + + /** + * Optional callback for keyboard key down events + */ + onKeyDown?: (e: React.KeyboardEvent) => void; +} + +/** Height of a single room list item in pixels */ +const ROOM_LIST_ITEM_HEIGHT = 48; + +/** + * Type for context used in ListView + */ +type Context = { spaceId: string; filterKeys: FilterKey[] | undefined }; + +/** + * Amount to extend the top and bottom of the viewport by. + * From manual testing and user feedback 25 items is reported to be enough to avoid blank space + * when using the mouse wheel, and the trackpad scrolling at a slow to moderate speed where you + * can still see/read the content. Using the trackpad to sling through a large percentage of the + * list quickly will still show blank space. We would likely need to simplify the item content to + * improve this case. + */ +const EXTENDED_VIEWPORT_HEIGHT = 25 * ROOM_LIST_ITEM_HEIGHT; + +/** + * A virtualized list of rooms. + * This component provides efficient rendering of large room lists using virtualization, + * and renders RoomListItemView components for each room. + * + * @example + * ```tsx + * } /> + * ``` + */ +export function RoomList({ vm, renderAvatar, onKeyDown }: RoomListProps): JSX.Element { + const snapshot = useViewModel(vm); + const { roomListState, roomIds } = snapshot; + const activeRoomIndex = roomListState.activeRoomIndex; + const lastSpaceId = useRef(undefined); + const lastFilterKeys = useRef(undefined); + const roomCount = roomIds.length; + + /** + * Callback when the visible range changes + * Notifies the view model which rooms are visible + */ + const rangeChanged = useCallback( + (range: { startIndex: number; endIndex: number }) => { + vm.updateVisibleRooms(range.startIndex, range.endIndex); + }, + [vm], + ); + + /** + * Get the item component for a specific index + * Gets the room's view model and passes it to RoomListItemView + */ + const getItemComponent = useCallback( + ( + index: number, + roomId: string, + context: VirtualizedListContext, + onFocus: (item: string, e: React.FocusEvent) => void, + ): JSX.Element => { + const isSelected = activeRoomIndex === index; + const roomItemVM = vm.getRoomItemViewModel(roomId); + + // Item is focused when the list has focus AND this item's key matches tabIndexKey + // This matches the old RoomList implementation's roving tabindex pattern + const isFocused = context.focused && context.tabIndexKey === roomId; + + return ( + + ); + }, + [activeRoomIndex, roomCount, renderAvatar, vm], + ); + + /** + * Get the key for a room item + * Since we're using virtualization, items are always room ID strings + */ + const getItemKey = useCallback((item: string): string => { + return item; + }, []); + + const context = useMemo( + () => ({ spaceId: roomListState.spaceId || "", filterKeys: roomListState.filterKeys }), + [roomListState.spaceId, roomListState.filterKeys], + ); + + /** + * Determine if we should scroll the active index into view + * This happens when the space or filters change + */ + const scrollIntoViewOnChange = useCallback( + (params: { + context: VirtualizedListContext<{ spaceId: string; filterKeys: FilterKey[] | undefined }>; + }): ScrollIntoViewLocation | null | undefined | false => { + const { spaceId, filterKeys } = params.context.context; + const shouldScrollIndexIntoView = + lastSpaceId.current !== spaceId || !isEqual(lastFilterKeys.current, filterKeys); + lastFilterKeys.current = filterKeys; + lastSpaceId.current = spaceId; + + if (shouldScrollIndexIntoView) { + return { + align: "start", + index: activeRoomIndex || 0, + behavior: "auto", + }; + } + return false; + }, + [activeRoomIndex], + ); + + return ( + true} + rangeChanged={rangeChanged} + onKeyDown={onKeyDown} + increaseViewportBy={{ + bottom: EXTENDED_VIEWPORT_HEIGHT, + top: EXTENDED_VIEWPORT_HEIGHT, + }} + /> + ); +} diff --git a/packages/shared-components/src/room-list/RoomList/__snapshots__/RoomList.test.tsx.snap b/packages/shared-components/src/room-list/RoomList/__snapshots__/RoomList.test.tsx.snap new file mode 100644 index 0000000000..f14e886bb1 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomList/__snapshots__/RoomList.test.tsx.snap @@ -0,0 +1,1277 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` > renders Default story 1`] = ` +
+
+
+
+
+
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ +
+ + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + + + + + + + +`; diff --git a/res/css/views/rooms/RoomListPanel/_RoomListItemMenuView.pcss b/packages/shared-components/src/room-list/RoomList/index.ts similarity index 51% rename from res/css/views/rooms/RoomListPanel/_RoomListItemMenuView.pcss rename to packages/shared-components/src/room-list/RoomList/index.ts index cabd9b2d20..0b0498d139 100644 --- a/res/css/views/rooms/RoomListPanel/_RoomListItemMenuView.pcss +++ b/packages/shared-components/src/room-list/RoomList/index.ts @@ -1,12 +1,9 @@ /* - * Copyright 2025 New Vector Ltd. + * Copyright 2026 Element Creations Ltd. * * 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. */ -.mx_RoomListItemMenuView { - svg { - fill: var(--cpd-color-icon-primary); - } -} +export { RoomList } from "./RoomList"; +export type { RoomListProps, RoomListViewState, FilterKey } from "./RoomList"; diff --git a/packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx b/packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx new file mode 100644 index 0000000000..db7ddf1f0a --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.stories.tsx @@ -0,0 +1,120 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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 type { Meta, StoryObj } from "@storybook/react-vite"; +import { NotificationDecoration, type NotificationDecorationProps } from "./NotificationDecoration"; + +const defaultProps: NotificationDecorationProps = { + hasAnyNotificationOrActivity: false, + isUnsentMessage: false, + invited: false, + isMention: false, + isActivityNotification: false, + isNotification: false, + hasUnreadCount: false, + count: 0, + muted: false, +}; + +const meta = { + title: "Room List/NotificationDecoration", + component: NotificationDecoration, + tags: ["autodocs"], + decorators: [ + (Story) => ( +
+ +
+ ), + ], + args: defaultProps, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const NoNotification: Story = {}; + +export const UnsentMessage: Story = { + args: { + hasAnyNotificationOrActivity: true, + isUnsentMessage: true, + }, +}; + +export const VideoCall: Story = { + args: { + hasAnyNotificationOrActivity: true, + callType: "video", + }, +}; + +export const VoiceCall: Story = { + args: { + hasAnyNotificationOrActivity: true, + callType: "voice", + }, +}; + +export const Invited: Story = { + args: { + hasAnyNotificationOrActivity: true, + invited: true, + }, +}; + +export const Mention: Story = { + args: { + hasAnyNotificationOrActivity: true, + isMention: true, + }, +}; + +export const MentionWithCount: Story = { + args: { + hasAnyNotificationOrActivity: true, + isMention: true, + count: 5, + }, +}; + +export const NotificationWithCount: Story = { + args: { + hasAnyNotificationOrActivity: true, + isNotification: true, + count: 3, + }, +}; + +export const ActivityIndicator: Story = { + args: { + hasAnyNotificationOrActivity: true, + isActivityNotification: true, + }, +}; + +export const Muted: Story = { + args: { + muted: true, + }, +}; + +export const MutedWithoutActivity: Story = { + args: { + hasAnyNotificationOrActivity: false, + muted: true, + }, +}; + +export const VideoCallWithoutActivity: Story = { + args: { + hasAnyNotificationOrActivity: false, + callType: "video", + }, +}; diff --git a/packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.test.tsx b/packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.test.tsx new file mode 100644 index 0000000000..f79e092f4d --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.test.tsx @@ -0,0 +1,80 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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 } from "@test-utils"; +import { composeStories } from "@storybook/react-vite"; +import { describe, it, expect } from "vitest"; + +import * as stories from "./NotificationDecoration.stories"; + +const { + NoNotification, + UnsentMessage, + VideoCall, + VoiceCall, + Invited, + Mention, + MentionWithCount, + NotificationWithCount, + ActivityIndicator, + Muted, +} = composeStories(stories); + +describe("", () => { + describe("snapshots", () => { + it("renders NoNotification story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders UnsentMessage story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders VideoCall story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders VoiceCall story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders Invited story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders Mention story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders MentionWithCount story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders NotificationWithCount story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders ActivityIndicator story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders Muted story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + }); +}); diff --git a/packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.tsx b/packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.tsx new file mode 100644 index 0000000000..03be962fbf --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/NotificationDecoration.tsx @@ -0,0 +1,90 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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 { + MentionIcon, + ErrorSolidIcon, + NotificationsOffSolidIcon, + VideoCallSolidIcon, + EmailSolidIcon, + VoiceCallSolidIcon, +} from "@vector-im/compound-design-tokens/assets/web/icons"; +import { UnreadCounter, Unread } from "@vector-im/compound-web"; + +import { Flex } from "../../../utils/Flex"; + +/** + * Data representing the notification state for a room or item. + * Used in snapshots and passed to the NotificationDecoration component. + */ +export interface NotificationDecorationData { + /** Whether there is any notification or activity to display */ + hasAnyNotificationOrActivity: boolean; + /** Whether there's an unsent message */ + isUnsentMessage: boolean; + /** Whether the user is invited to the room */ + invited: boolean; + /** Whether the notification is a mention */ + isMention: boolean; + /** Whether there's activity (not a full notification) */ + isActivityNotification: boolean; + /** Whether there's a notification (not just activity) */ + isNotification: boolean; + /** Whether there are unread messages with a count */ + hasUnreadCount: boolean; + /** Notification count */ + count: number; + /** Whether notifications are muted */ + muted: boolean; + /** Optional call type indicator */ + callType?: "video" | "voice"; +} + +/** + * Props for the NotificationDecoration component. + */ +export interface NotificationDecorationProps extends NotificationDecorationData {} + +/** + * Renders notification badges and indicators for rooms/items + */ +export const NotificationDecoration: React.FC = ({ + hasAnyNotificationOrActivity, + muted, + callType, + isUnsentMessage, + invited, + isMention, + isNotification, + isActivityNotification, + count, +}) => { + // Don't render anything if there's nothing to show + if (!hasAnyNotificationOrActivity && !muted && !callType) { + return null; + } + + return ( + + {isUnsentMessage && ( + + )} + {callType === "video" && ( + + )} + {callType === "voice" && ( + + )} + {invited && } + {isMention && } + {(isMention || isNotification) && } + {isActivityNotification && } + {muted && } + + ); +}; diff --git a/packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/__snapshots__/NotificationDecoration.test.tsx.snap b/packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/__snapshots__/NotificationDecoration.test.tsx.snap new file mode 100644 index 0000000000..a7c7da94f4 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/__snapshots__/NotificationDecoration.test.tsx.snap @@ -0,0 +1,242 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` > snapshots > renders ActivityIndicator story 1`] = ` +
+
+
+
+
+
+
+
+
+`; + +exports[` > snapshots > renders Invited story 1`] = ` +
+
+
+ + + +
+
+
+`; + +exports[` > snapshots > renders Mention story 1`] = ` +
+
+
+ + + +
+
+
+
+`; + +exports[` > snapshots > renders MentionWithCount story 1`] = ` +
+
+
+ + + + + 5 + +
+
+
+`; + +exports[` > snapshots > renders Muted story 1`] = ` +
+
+
+ + + + +
+
+
+`; + +exports[` > snapshots > renders NoNotification story 1`] = ` +
+
+
+`; + +exports[` > snapshots > renders NotificationWithCount story 1`] = ` +
+
+
+ + 3 + +
+
+
+`; + +exports[` > snapshots > renders UnsentMessage story 1`] = ` +
+
+
+ + + +
+
+
+`; + +exports[` > snapshots > renders VideoCall story 1`] = ` +
+
+
+ + + +
+
+
+`; + +exports[` > snapshots > renders VoiceCall story 1`] = ` +
+
+
+ + + +
+
+
+`; diff --git a/packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/index.tsx b/packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/index.tsx new file mode 100644 index 0000000000..42c7a3451f --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/NotificationDecoration/index.tsx @@ -0,0 +1,9 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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. + */ + +export { NotificationDecoration } from "./NotificationDecoration"; +export type { NotificationDecorationProps, NotificationDecorationData } from "./NotificationDecoration"; diff --git a/packages/shared-components/src/room-list/RoomListItem/RoomListItem.module.css b/packages/shared-components/src/room-list/RoomListItem/RoomListItem.module.css new file mode 100644 index 0000000000..008a4462ac --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/RoomListItem.module.css @@ -0,0 +1,106 @@ +/* + * Copyright 2025 Element Creations Ltd. + * + * 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. + */ + +/** + * The RoomListItem has the following structure: + * button--------------------------------------------------| + * | <-12px-> container------------------------------------| + * | | room avatar <-8px-> content----------------| + * | | | room_name <- 20px ->| + * | | | --------------------| <-- border + * |-------------------------------------------------------| + */ +.roomListItem { + /* Remove button default style */ + background: unset; + border: none; + padding: 0; + text-align: unset; + + cursor: pointer; + height: 48px; + width: 100%; + + padding-left: var(--cpd-space-3x); + font: var(--cpd-font-body-md-regular); + color: var(--cpd-color-text-primary); + + /* Hide the menu by default */ + .hoverMenu { + display: none; + } +} + +/* Show hover menu and background on hover/focus/menu-open states */ +.roomListItem:hover, +.roomListItem:focus-visible, +/* When the context menu is opened */ +.roomListItem[data-state="open"], +/* When the options and notifications menu are opened */ +.roomListItem:has(.hoverMenu > button[data-state="open"]) { + background-color: var(--cpd-color-bg-action-secondary-hovered); + + .hoverMenu { + display: flex; + } + + /* When the menu is visible, hide the notification decoration to avoid clutter */ + .notificationDecoration { + display: none; + } + + /** + * The figma uses 16px padding (--cpd-space-4x) but due to https://github.com/element-hq/compound-web/issues/331 + * the icon size of the menu is 18px instead of 20px with a different internal padding + * We need to use 18px to align the icon with the others icons + * 18px is not available in compound spacing + */ + .content { + padding-right: 18px; + } +} + +.content { + height: 100%; + flex: 1; + /* The border is only under the room name and the future hover menu */ + border-bottom: var(--cpd-border-width-0-5) solid var(--cpd-color-bg-subtle-secondary); + box-sizing: border-box; + min-width: 0; + padding-right: var(--cpd-space-5x); +} + +.text { + min-width: 0; +} + +.roomName { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.messagePreview { + font: var(--cpd-font-body-sm-regular); + color: var(--cpd-color-text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.selected { + background-color: var(--cpd-color-bg-action-secondary-pressed); +} + +.bold .roomName { + font: var(--cpd-font-body-md-semibold); +} + +/* Set icon color for hover menu buttons */ +.hoverMenu svg { + fill: var(--cpd-color-icon-primary); +} diff --git a/packages/shared-components/src/room-list/RoomListItem/RoomListItem.stories.tsx b/packages/shared-components/src/room-list/RoomListItem/RoomListItem.stories.tsx new file mode 100644 index 0000000000..0da88a6739 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/RoomListItem.stories.tsx @@ -0,0 +1,206 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, { type JSX } from "react"; +import { fn } from "storybook/test"; + +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { RoomListItemView, type RoomListItemSnapshot, type RoomListItemActions } from "./RoomListItem"; +import { useMockedViewModel } from "../../viewmodel"; +import { defaultSnapshot } from "./default-snapshot"; +import { renderAvatar } from "../story-mocks"; + +type RoomListItemProps = RoomListItemSnapshot & + RoomListItemActions & { + isSelected: boolean; + isFocused: boolean; + onFocus: (room: any, e: React.FocusEvent) => void; + roomIndex: number; + roomCount: number; + renderAvatar: (room: any) => React.ReactElement; + }; + +// Wrapper component that creates a mocked ViewModel +const RoomListItemWrapper = ({ + onOpenRoom, + onMarkAsRead, + onMarkAsUnread, + onToggleFavorite, + onToggleLowPriority, + onInvite, + onCopyRoomLink, + onLeaveRoom, + onSetRoomNotifState, + isSelected, + isFocused, + onFocus, + roomIndex, + roomCount, + renderAvatar: renderAvatarProp, + ...rest +}: RoomListItemProps): JSX.Element => { + const vm = useMockedViewModel(rest, { + onOpenRoom, + onMarkAsRead, + onMarkAsUnread, + onToggleFavorite, + onToggleLowPriority, + onInvite, + onCopyRoomLink, + onLeaveRoom, + onSetRoomNotifState, + }); + return ( + + ); +}; + +const meta = { + title: "Room List/RoomListItem", + component: RoomListItemWrapper, + tags: ["autodocs"], + decorators: [ + (Story) => ( +
+
+ +
+
+ ), + ], + args: { + ...defaultSnapshot, + isSelected: false, + isFocused: false, + roomIndex: 0, + roomCount: 10, + onOpenRoom: fn(), + onMarkAsRead: fn(), + onMarkAsUnread: fn(), + onToggleFavorite: fn(), + onToggleLowPriority: fn(), + onInvite: fn(), + onCopyRoomLink: fn(), + onLeaveRoom: fn(), + onSetRoomNotifState: fn(), + onFocus: fn(), + renderAvatar, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const Selected: Story = { + args: { + isSelected: true, + }, +}; + +export const Bold: Story = { + args: { + isBold: true, + name: "Team Updates", + }, +}; + +export const WithNotification: Story = { + args: { + isBold: true, + notification: { + hasAnyNotificationOrActivity: true, + isUnsentMessage: false, + invited: false, + isMention: false, + isActivityNotification: false, + isNotification: true, + hasUnreadCount: true, + count: 3, + muted: false, + }, + }, +}; + +export const WithMention: Story = { + args: { + isBold: true, + notification: { + hasAnyNotificationOrActivity: true, + isUnsentMessage: false, + invited: false, + isMention: true, + isActivityNotification: false, + isNotification: true, + hasUnreadCount: true, + count: 1, + muted: false, + }, + }, +}; + +export const Invitation: Story = { + args: { + name: "Secret Project", + messagePreview: "Bob invited you", + notification: { + hasAnyNotificationOrActivity: true, + isUnsentMessage: false, + invited: true, + isMention: false, + isActivityNotification: false, + isNotification: false, + hasUnreadCount: false, + count: 0, + muted: false, + }, + }, +}; + +export const UnsentMessage: Story = { + args: { + messagePreview: "Failed to send message", + notification: { + hasAnyNotificationOrActivity: true, + isUnsentMessage: true, + invited: false, + isMention: false, + isActivityNotification: false, + isNotification: false, + hasUnreadCount: false, + count: 0, + muted: false, + }, + }, +}; + +export const NoMessagePreview: Story = { + args: { + messagePreview: undefined, + }, +}; + +export const WithHoverMenu: Story = { + args: { + showMoreOptionsMenu: true, + }, +}; + +export const WithoutHoverMenu: Story = { + args: { + showMoreOptionsMenu: false, + }, +}; diff --git a/packages/shared-components/src/room-list/RoomListItem/RoomListItem.test.tsx b/packages/shared-components/src/room-list/RoomListItem/RoomListItem.test.tsx new file mode 100644 index 0000000000..788c9f317f --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/RoomListItem.test.tsx @@ -0,0 +1,123 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, screen } from "@test-utils"; +import userEvent from "@testing-library/user-event"; +import { composeStories } from "@storybook/react-vite"; +import { describe, it, expect } from "vitest"; + +import * as stories from "./RoomListItem.stories"; + +const { + Default, + Selected, + Bold, + WithNotification, + WithMention, + Invitation, + UnsentMessage, + NoMessagePreview, + WithHoverMenu, + WithoutHoverMenu, +} = composeStories(stories); + +describe("", () => { + it("renders Default story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders Selected story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders Bold story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders WithNotification story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders WithMention story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders Invitation story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders UnsentMessage story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders NoMessagePreview story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders WithHoverMenu story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("should call onOpenRoom when clicked", async () => { + const user = userEvent.setup(); + render(); + + await user.click(screen.getByRole("option")); + expect(Default.args.onOpenRoom).toHaveBeenCalled(); + }); + + it("should have aria-selected true when selected", () => { + render(); + expect(screen.getByRole("option")).toHaveAttribute("aria-selected", "true"); + }); + + it("should have aria-selected false when not selected", () => { + render(); + expect(screen.getByRole("option")).toHaveAttribute("aria-selected", "false"); + }); + + it("should have tabIndex -1 when not focused", () => { + render(); + expect(screen.getByRole("option")).toHaveAttribute("tabIndex", "-1"); + }); + + it("should call onFocus when focused", () => { + render(); + screen.getByRole("option").focus(); + expect(Default.args.onFocus).toHaveBeenCalled(); + }); + + it("should display notification decoration when present", () => { + render(); + expect(screen.getByTestId("notification-decoration")).toBeInTheDocument(); + }); + + it("should hide notification decoration when not present", () => { + render(); + expect(screen.queryByTestId("notification-decoration")).toBeNull(); + }); + + it("should show hover menu when showMoreOptionsMenu is true", () => { + const { container } = render(); + expect(container.querySelector('[aria-label="More Options"]')).not.toBeNull(); + }); + + it("should hide hover menu when showMoreOptionsMenu is false", () => { + const { container } = render(); + expect(container.querySelector('[aria-label="More Options"]')).toBeNull(); + }); +}); diff --git a/packages/shared-components/src/room-list/RoomListItem/RoomListItem.tsx b/packages/shared-components/src/room-list/RoomListItem/RoomListItem.tsx new file mode 100644 index 0000000000..ddde7ffeee --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/RoomListItem.tsx @@ -0,0 +1,202 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, { type JSX, memo, useEffect, useRef, type ReactNode } from "react"; +import classNames from "classnames"; + +import { Flex } from "../../utils/Flex"; +import { NotificationDecoration, type NotificationDecorationData } from "./NotificationDecoration"; +import { RoomListItemHoverMenu } from "./RoomListItemHoverMenu"; +import { RoomListItemContextMenu } from "./RoomListItemContextMenu"; +import { type RoomNotifState } from "./RoomNotifs"; +import styles from "./RoomListItem.module.css"; +import { useViewModel, type ViewModel } from "../../viewmodel"; +import { _t } from "../../utils/i18n"; + +/** + * Generate an accessible label for a room based on its notification state. + */ +function getA11yLabel(roomName: string, notification: NotificationDecorationData): string { + if (notification.isUnsentMessage) { + return _t("room_list|a11y|unsent_message", { roomName }); + } else if (notification.invited) { + return _t("room_list|a11y|invitation", { roomName }); + } else if (notification.isMention && notification.count) { + return _t("room_list|a11y|mention", { roomName, count: notification.count }); + } else if (notification.hasUnreadCount && notification.count) { + return _t("room_list|a11y|unread", { roomName, count: notification.count }); + } else { + return _t("room_list|a11y|default", { roomName }); + } +} + +/** + * Snapshot for a room list item. + * Contains all the data needed to render a room in the list. + */ +export interface RoomListItemSnapshot { + /** Unique identifier for the room (used for list keying) */ + id: string; + /** The opaque Room object from the client (e.g., matrix-js-sdk Room) */ + room: any; + /** The name of the room */ + name: string; + /** Whether the room name should be bolded (has unread/activity) */ + isBold: boolean; + /** Optional message preview text */ + messagePreview?: string; + /** Notification decoration data */ + notification: NotificationDecorationData; + /** Whether the more options menu should be shown */ + showMoreOptionsMenu: boolean; + /** Whether the notification menu should be shown */ + showNotificationMenu: boolean; + /** Whether the room is a favourite room */ + isFavourite: boolean; + /** Whether the room is a low priority room */ + isLowPriority: boolean; + /** Can invite other users in the room */ + canInvite: boolean; + /** Can copy the room link */ + canCopyRoomLink: boolean; + /** Can mark the room as read */ + canMarkAsRead: boolean; + /** Can mark the room as unread */ + canMarkAsUnread: boolean; + /** The room's notification state */ + roomNotifState: RoomNotifState; +} + +/** + * Actions interface for room list item operations. + * Implemented by the room item view model. + */ +export interface RoomListItemActions { + /** Called when the room should be opened */ + onOpenRoom: () => void; + /** Called when the room should be marked as read */ + onMarkAsRead: () => void; + /** Called when the room should be marked as unread */ + onMarkAsUnread: () => void; + /** Called when the room's favorite status should be toggled */ + onToggleFavorite: () => void; + /** Called when the room's low priority status should be toggled */ + onToggleLowPriority: () => void; + /** Called when inviting users to the room */ + onInvite: () => void; + /** Called when copying the room link */ + onCopyRoomLink: () => void; + /** Called when leaving the room */ + onLeaveRoom: () => void; + /** Called when setting the room notification state */ + onSetRoomNotifState: (state: RoomNotifState) => void; +} + +/** + * The view model type for a room list item + */ +export type RoomItemViewModel = ViewModel & RoomListItemActions; + +/** + * Props for RoomListItemView component + */ +export interface RoomListItemViewProps extends Omit, "onFocus"> { + /** The room item view model */ + vm: RoomItemViewModel; + /** Whether the room is selected */ + isSelected: boolean; + /** Whether the room should be focused */ + isFocused: boolean; + /** Callback when item receives focus */ + onFocus: (roomId: string, e: React.FocusEvent) => void; + /** Index of this room in the list (for accessibility) */ + roomIndex: number; + /** Total number of rooms in the list (for accessibility) */ + roomCount: number; + /** Function to render the room avatar */ + renderAvatar: (room: any) => ReactNode; +} + +/** + * A presentational room list item component. + * Displays room name, avatar, message preview, and notifications. + */ +export const RoomListItemView = memo(function RoomListItemView({ + vm, + isSelected, + isFocused, + onFocus, + roomIndex, + roomCount, + renderAvatar, + ...props +}: RoomListItemViewProps): JSX.Element { + const ref = useRef(null); + const item = useViewModel(vm); + + useEffect(() => { + if (isFocused) { + ref.current?.focus({ preventScroll: true, focusVisible: true } as FocusOptions); + } + }, [isFocused]); + + // Generate a11y label from notification state and room name + const a11yLabel = getA11yLabel(item.name, item.notification); + + const content = ( + ) => onFocus(item.id, e)} + tabIndex={isFocused ? 0 : -1} + {...props} + > + {renderAvatar(item.room)} + + {/* We truncate the room name when too long. Title here is to show the full name on hover */} +
+
+ {item.name} +
+ {item.messagePreview && ( +
+ {item.messagePreview} +
+ )} +
+ {(item.showMoreOptionsMenu || item.showNotificationMenu) && ( + + )} + + {/* aria-hidden because we summarise the unread count/notification status in a11yLabel */} +
+ +
+
+
+ ); + + return {content}; +}); diff --git a/packages/shared-components/src/room-list/RoomListItem/RoomListItemContextMenu.tsx b/packages/shared-components/src/room-list/RoomListItem/RoomListItemContextMenu.tsx new file mode 100644 index 0000000000..0d202474f8 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/RoomListItemContextMenu.tsx @@ -0,0 +1,40 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, { type JSX, type PropsWithChildren } from "react"; +import { ContextMenu } from "@vector-im/compound-web"; + +import { _t } from "../../utils/i18n"; +import { MoreOptionContent, type RoomItemViewModel } from "./RoomListItemMoreOptionsMenu"; + +/** + * Props for RoomListItemContextMenu component + */ +export interface RoomListItemContextMenuProps { + /** The room item view model */ + vm: RoomItemViewModel; +} + +/** + * The context menu for room list items. + * Wraps the trigger element with a right-click context menu displaying room options. + */ +export const RoomListItemContextMenu: React.FC> = ({ + vm, + children, +}): JSX.Element => { + return ( + + + + ); +}; diff --git a/packages/shared-components/src/room-list/RoomListItem/RoomListItemHoverMenu.tsx b/packages/shared-components/src/room-list/RoomListItem/RoomListItemHoverMenu.tsx new file mode 100644 index 0000000000..9a453b2014 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/RoomListItemHoverMenu.tsx @@ -0,0 +1,42 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, { type JSX } from "react"; + +import { Flex } from "../../utils/Flex"; +import { RoomListItemMoreOptionsMenu, type RoomItemViewModel } from "./RoomListItemMoreOptionsMenu"; +import { RoomListItemNotificationMenu } from "./RoomListItemNotificationMenu"; +import styles from "./RoomListItem.module.css"; + +/** + * Props for RoomListItemHoverMenu component + */ +export interface RoomListItemHoverMenuProps { + /** Whether the more options menu should be shown */ + showMoreOptionsMenu: boolean; + /** Whether the notification menu should be shown */ + showNotificationMenu: boolean; + /** The room item view model */ + vm: RoomItemViewModel; +} + +/** + * The hover menu for room list items. + * Displays more options and notification settings menus. + */ +export const RoomListItemHoverMenu: React.FC = ({ + showMoreOptionsMenu, + showNotificationMenu, + vm, +}): JSX.Element => { + return ( + + {showMoreOptionsMenu && } + {showNotificationMenu && } + + ); +}; diff --git a/packages/shared-components/src/room-list/RoomListItem/RoomListItemMoreOptionsMenu.test.tsx b/packages/shared-components/src/room-list/RoomListItem/RoomListItemMoreOptionsMenu.test.tsx new file mode 100644 index 0000000000..40b9917c5b --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/RoomListItemMoreOptionsMenu.test.tsx @@ -0,0 +1,227 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, { type JSX } from "react"; +import { render, screen } from "@test-utils"; +import userEvent from "@testing-library/user-event"; +import { describe, it, expect, vi } from "vitest"; + +import { RoomListItemMoreOptionsMenu } from "./RoomListItemMoreOptionsMenu"; +import { useMockedViewModel } from "../../viewmodel"; +import type { RoomListItemSnapshot } from "./RoomListItem"; +import { defaultSnapshot } from "./default-snapshot"; + +describe("", () => { + const mockCallbacks = { + onOpenRoom: vi.fn(), + onMarkAsRead: vi.fn(), + onMarkAsUnread: vi.fn(), + onToggleFavorite: vi.fn(), + onToggleLowPriority: vi.fn(), + onInvite: vi.fn(), + onCopyRoomLink: vi.fn(), + onLeaveRoom: vi.fn(), + onSetRoomNotifState: vi.fn(), + }; + + const renderMenu = (overrides: Partial = {}): ReturnType => { + const TestComponent = (): JSX.Element => { + const vm = useMockedViewModel( + { + ...defaultSnapshot, + showMoreOptionsMenu: true, + showNotificationMenu: false, + ...overrides, + } as RoomListItemSnapshot, + mockCallbacks, + ); + return ; + }; + return render(); + }; + + it("should render the more options button", () => { + renderMenu(); + expect(screen.getByRole("button", { name: "More Options" })).toBeInTheDocument(); + }); + + it("should open menu when clicked", async () => { + const user = userEvent.setup(); + renderMenu(); + + const button = screen.getByRole("button", { name: "More Options" }); + await user.click(button); + + expect(screen.getByRole("menu")).toBeInTheDocument(); + }); + + it("should show mark as read option when canMarkAsRead is true", async () => { + const user = userEvent.setup(); + renderMenu({ canMarkAsRead: true }); + + const button = screen.getByRole("button", { name: "More Options" }); + await user.click(button); + + expect(screen.getByRole("menuitem", { name: "Mark as read" })).toBeInTheDocument(); + }); + + it("should not show mark as read option when canMarkAsRead is false", async () => { + const user = userEvent.setup(); + renderMenu({ canMarkAsRead: false }); + + const button = screen.getByRole("button", { name: "More Options" }); + await user.click(button); + + expect(screen.queryByRole("menuitem", { name: "Mark as read" })).not.toBeInTheDocument(); + }); + + it("should call onMarkAsRead when mark as read clicked", async () => { + const user = userEvent.setup(); + renderMenu({ canMarkAsRead: true }); + + const button = screen.getByRole("button", { name: "More Options" }); + await user.click(button); + + const markAsReadOption = screen.getByRole("menuitem", { name: "Mark as read" }); + await user.click(markAsReadOption); + + expect(mockCallbacks.onMarkAsRead).toHaveBeenCalled(); + }); + + it("should show mark as unread option when canMarkAsUnread is true", async () => { + const user = userEvent.setup(); + renderMenu({ canMarkAsUnread: true }); + + const button = screen.getByRole("button", { name: "More Options" }); + await user.click(button); + + expect(screen.getByRole("menuitem", { name: "Mark as unread" })).toBeInTheDocument(); + }); + + it("should call onMarkAsUnread when mark as unread clicked", async () => { + const user = userEvent.setup(); + renderMenu({ canMarkAsUnread: true }); + + const button = screen.getByRole("button", { name: "More Options" }); + await user.click(button); + + const markAsUnreadOption = screen.getByRole("menuitem", { name: "Mark as unread" }); + await user.click(markAsUnreadOption); + + expect(mockCallbacks.onMarkAsUnread).toHaveBeenCalled(); + }); + + it("should show favorite option and call onToggleFavorite", async () => { + const user = userEvent.setup(); + renderMenu({ isFavourite: false }); + + const button = screen.getByRole("button", { name: "More Options" }); + await user.click(button); + + const favoriteOption = screen.getByRole("menuitemcheckbox", { name: "Favourited" }); + expect(favoriteOption).toBeInTheDocument(); + expect(favoriteOption).toHaveAttribute("aria-checked", "false"); + + await user.click(favoriteOption); + expect(mockCallbacks.onToggleFavorite).toHaveBeenCalled(); + }); + + it("should show favorite as checked when isFavourite is true", async () => { + const user = userEvent.setup(); + renderMenu({ isFavourite: true }); + + const button = screen.getByRole("button", { name: "More Options" }); + await user.click(button); + + const favoriteOption = screen.getByRole("menuitemcheckbox", { name: "Favourited" }); + expect(favoriteOption).toHaveAttribute("aria-checked", "true"); + }); + + it("should show low priority option and call onToggleLowPriority", async () => { + const user = userEvent.setup(); + renderMenu({ isLowPriority: false }); + + const button = screen.getByRole("button", { name: "More Options" }); + await user.click(button); + + const lowPriorityOption = screen.getByRole("menuitemcheckbox", { name: "Low priority" }); + expect(lowPriorityOption).toBeInTheDocument(); + expect(lowPriorityOption).toHaveAttribute("aria-checked", "false"); + + await user.click(lowPriorityOption); + expect(mockCallbacks.onToggleLowPriority).toHaveBeenCalled(); + }); + + it("should show invite option when canInvite is true", async () => { + const user = userEvent.setup(); + renderMenu({ canInvite: true }); + + const button = screen.getByRole("button", { name: "More Options" }); + await user.click(button); + + expect(screen.getByRole("menuitem", { name: "Invite" })).toBeInTheDocument(); + }); + + it("should call onInvite when invite clicked", async () => { + const user = userEvent.setup(); + renderMenu({ canInvite: true }); + + const button = screen.getByRole("button", { name: "More Options" }); + await user.click(button); + + const inviteOption = screen.getByRole("menuitem", { name: "Invite" }); + await user.click(inviteOption); + + expect(mockCallbacks.onInvite).toHaveBeenCalled(); + }); + + it("should show copy link option when canCopyRoomLink is true", async () => { + const user = userEvent.setup(); + renderMenu({ canCopyRoomLink: true }); + + const button = screen.getByRole("button", { name: "More Options" }); + await user.click(button); + + expect(screen.getByRole("menuitem", { name: "Copy room link" })).toBeInTheDocument(); + }); + + it("should call onCopyRoomLink when copy link clicked", async () => { + const user = userEvent.setup(); + renderMenu({ canCopyRoomLink: true }); + + const button = screen.getByRole("button", { name: "More Options" }); + await user.click(button); + + const copyLinkOption = screen.getByRole("menuitem", { name: "Copy room link" }); + await user.click(copyLinkOption); + + expect(mockCallbacks.onCopyRoomLink).toHaveBeenCalled(); + }); + + it("should show leave room option", async () => { + const user = userEvent.setup(); + renderMenu(); + + const button = screen.getByRole("button", { name: "More Options" }); + await user.click(button); + + expect(screen.getByRole("menuitem", { name: "Leave room" })).toBeInTheDocument(); + }); + + it("should call onLeaveRoom when leave room clicked", async () => { + const user = userEvent.setup(); + renderMenu(); + + const button = screen.getByRole("button", { name: "More Options" }); + await user.click(button); + + const leaveRoomOption = screen.getByRole("menuitem", { name: "Leave room" }); + await user.click(leaveRoomOption); + + expect(mockCallbacks.onLeaveRoom).toHaveBeenCalled(); + }); +}); diff --git a/packages/shared-components/src/room-list/RoomListItem/RoomListItemMoreOptionsMenu.tsx b/packages/shared-components/src/room-list/RoomListItem/RoomListItemMoreOptionsMenu.tsx new file mode 100644 index 0000000000..d10b5c32ec --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/RoomListItemMoreOptionsMenu.tsx @@ -0,0 +1,137 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, { useState, type JSX } from "react"; +import { IconButton, Menu, MenuItem, Separator, ToggleMenuItem } from "@vector-im/compound-web"; +import { + MarkAsReadIcon, + MarkAsUnreadIcon, + FavouriteIcon, + ArrowDownIcon, + UserAddIcon, + LinkIcon, + LeaveIcon, + OverflowHorizontalIcon, +} from "@vector-im/compound-design-tokens/assets/web/icons"; + +import { _t } from "../../utils/i18n"; +import { useViewModel, type ViewModel } from "../../viewmodel"; +import type { RoomListItemSnapshot, RoomListItemActions } from "./RoomListItem"; + +/** + * View model type for room list item + */ +export type RoomItemViewModel = ViewModel & RoomListItemActions; + +/** + * Props for RoomListItemMoreOptionsMenu component + */ +export interface RoomListItemMoreOptionsMenuProps { + /** The room item view model */ + vm: RoomItemViewModel; +} + +/** + * The more options menu for room list items. + * Displays additional room actions like mark as read/unread, favorite, invite, etc. + */ +export function RoomListItemMoreOptionsMenu({ vm }: RoomListItemMoreOptionsMenuProps): JSX.Element { + const [open, setOpen] = useState(false); + + return ( + + + + } + > + + + ); +} + +interface MoreOptionContentProps { + vm: RoomItemViewModel; +} + +export function MoreOptionContent({ vm }: MoreOptionContentProps): JSX.Element { + const snapshot = useViewModel(vm); + return ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions +
e.stopPropagation()}> + {snapshot.canMarkAsRead && ( + evt.stopPropagation()} + hideChevron={true} + /> + )} + {snapshot.canMarkAsUnread && ( + evt.stopPropagation()} + hideChevron={true} + /> + )} + evt.stopPropagation()} + /> + evt.stopPropagation()} + /> + {snapshot.canInvite && ( + evt.stopPropagation()} + hideChevron={true} + /> + )} + {snapshot.canCopyRoomLink && ( + evt.stopPropagation()} + hideChevron={true} + /> + )} + + evt.stopPropagation()} + hideChevron={true} + /> +
+ ); +} diff --git a/packages/shared-components/src/room-list/RoomListItem/RoomListItemNotificationMenu.test.tsx b/packages/shared-components/src/room-list/RoomListItem/RoomListItemNotificationMenu.test.tsx new file mode 100644 index 0000000000..3f88e2f8a1 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/RoomListItemNotificationMenu.test.tsx @@ -0,0 +1,164 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, { type JSX } from "react"; +import { render, screen } from "@test-utils"; +import userEvent from "@testing-library/user-event"; +import { describe, it, expect, vi } from "vitest"; + +import { RoomListItemNotificationMenu } from "./RoomListItemNotificationMenu"; +import { RoomNotifState } from "./RoomNotifs"; +import { useMockedViewModel } from "../../viewmodel"; +import type { RoomListItemSnapshot } from "./RoomListItem"; +import { defaultSnapshot } from "./default-snapshot"; + +describe("", () => { + const mockCallbacks = { + onOpenRoom: vi.fn(), + onMarkAsRead: vi.fn(), + onMarkAsUnread: vi.fn(), + onToggleFavorite: vi.fn(), + onToggleLowPriority: vi.fn(), + onInvite: vi.fn(), + onCopyRoomLink: vi.fn(), + onLeaveRoom: vi.fn(), + onSetRoomNotifState: vi.fn(), + }; + + const renderMenu = (roomNotifState: RoomNotifState = RoomNotifState.AllMessages): ReturnType => { + const TestComponent = (): JSX.Element => { + const vm = useMockedViewModel( + { + ...defaultSnapshot, + showMoreOptionsMenu: false, + showNotificationMenu: true, + roomNotifState, + } as RoomListItemSnapshot, + mockCallbacks, + ); + return ; + }; + return render(); + }; + + it("should render the notification menu button", () => { + renderMenu(); + expect(screen.getByRole("button", { name: "Notification options" })).toBeInTheDocument(); + }); + + it("should show muted icon when notifications are muted", () => { + renderMenu(RoomNotifState.Mute); + const button = screen.getByRole("button", { name: "Notification options" }); + expect(button.querySelector("svg")).toBeInTheDocument(); + }); + + it("should open menu when clicked", async () => { + const user = userEvent.setup(); + renderMenu(); + + const button = screen.getByRole("button", { name: "Notification options" }); + await user.click(button); + + expect(screen.getByRole("menu")).toBeInTheDocument(); + }); + + it("should call onSetRoomNotifState with AllMessages when default settings selected", async () => { + const user = userEvent.setup(); + renderMenu(); + + const button = screen.getByRole("button", { name: "Notification options" }); + await user.click(button); + + const defaultOption = screen.getByRole("menuitem", { name: "Match default settings" }); + await user.click(defaultOption); + + expect(mockCallbacks.onSetRoomNotifState).toHaveBeenCalledWith(RoomNotifState.AllMessages); + }); + + it("should call onSetRoomNotifState with AllMessagesLoud when all messages selected", async () => { + const user = userEvent.setup(); + renderMenu(); + + const button = screen.getByRole("button", { name: "Notification options" }); + await user.click(button); + + const allMessagesOption = screen.getByRole("menuitem", { name: "All messages" }); + await user.click(allMessagesOption); + + expect(mockCallbacks.onSetRoomNotifState).toHaveBeenCalledWith(RoomNotifState.AllMessagesLoud); + }); + + it("should call onSetRoomNotifState with MentionsOnly when mentions and keywords selected", async () => { + const user = userEvent.setup(); + renderMenu(); + + const button = screen.getByRole("button", { name: "Notification options" }); + await user.click(button); + + const mentionsOption = screen.getByRole("menuitem", { name: "Mentions and keywords" }); + await user.click(mentionsOption); + + expect(mockCallbacks.onSetRoomNotifState).toHaveBeenCalledWith(RoomNotifState.MentionsOnly); + }); + + it("should call onSetRoomNotifState with Mute when mute selected", async () => { + const user = userEvent.setup(); + renderMenu(); + + const button = screen.getByRole("button", { name: "Notification options" }); + await user.click(button); + + const muteOption = screen.getByRole("menuitem", { name: "Mute room" }); + await user.click(muteOption); + + expect(mockCallbacks.onSetRoomNotifState).toHaveBeenCalledWith(RoomNotifState.Mute); + }); + + it("should show check mark next to selected option - AllMessage", async () => { + const user = userEvent.setup(); + renderMenu(RoomNotifState.AllMessages); + + const button = screen.getByRole("button", { name: "Notification options" }); + await user.click(button); + + const defaultOption = screen.getByRole("menuitem", { name: "Match default settings" }); + expect(defaultOption).toHaveAttribute("aria-selected", "true"); + }); + + it("should show check mark next to selected option - AllMessagesLoud", async () => { + const user = userEvent.setup(); + renderMenu(RoomNotifState.AllMessagesLoud); + + const button = screen.getByRole("button", { name: "Notification options" }); + await user.click(button); + + const allMessagesOption = screen.getByRole("menuitem", { name: "All messages" }); + expect(allMessagesOption).toHaveAttribute("aria-selected", "true"); + }); + + it("should show check mark next to selected option - MentionsOnly", async () => { + const user = userEvent.setup(); + renderMenu(RoomNotifState.MentionsOnly); + + const button = screen.getByRole("button", { name: "Notification options" }); + await user.click(button); + + const mentionsOption = screen.getByRole("menuitem", { name: "Mentions and keywords" }); + expect(mentionsOption).toHaveAttribute("aria-selected", "true"); + }); + + it("should show check mark next to selected option - Mute", async () => { + const user = userEvent.setup(); + renderMenu(RoomNotifState.Mute); + + const button = screen.getByRole("button", { name: "Notification options" }); + await user.click(button); + + const muteOption = screen.getByRole("menuitem", { name: "Mute room" }); + expect(muteOption).toHaveAttribute("aria-selected", "true"); + }); +}); diff --git a/packages/shared-components/src/room-list/RoomListItem/RoomListItemNotificationMenu.tsx b/packages/shared-components/src/room-list/RoomListItem/RoomListItemNotificationMenu.tsx new file mode 100644 index 0000000000..e4038fae6c --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/RoomListItemNotificationMenu.tsx @@ -0,0 +1,105 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, { useState, type JSX } from "react"; +import { IconButton, Menu, MenuItem } from "@vector-im/compound-web"; +import { + NotificationsSolidIcon, + NotificationsOffSolidIcon, + CheckIcon, +} from "@vector-im/compound-design-tokens/assets/web/icons"; + +import { _t } from "../../utils/i18n"; +import { RoomNotifState } from "./RoomNotifs"; +import { useViewModel, type ViewModel } from "../../viewmodel"; +import type { RoomListItemSnapshot, RoomListItemActions } from "./RoomListItem"; + +/** + * View model type for room list item + */ +export type RoomItemViewModel = ViewModel & RoomListItemActions; + +/** + * Props for RoomListItemNotificationMenu component + */ +export interface RoomListItemNotificationMenuProps { + /** The room item view model */ + vm: RoomItemViewModel; +} + +/** + * The notification settings menu for room list items. + * Displays options to change notification settings. + */ +export function RoomListItemNotificationMenu({ vm }: RoomListItemNotificationMenuProps): JSX.Element { + const snapshot = useViewModel(vm); + const [open, setOpen] = useState(false); + const isMuted = snapshot.roomNotifState === RoomNotifState.Mute; + const checkComponent = ; + + return ( + + {isMuted ? : } + + } + > + {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} +
e.stopPropagation()} + > + vm.onSetRoomNotifState(RoomNotifState.AllMessages)} + onClick={(evt) => evt.stopPropagation()} + > + {snapshot.roomNotifState === RoomNotifState.AllMessages && checkComponent} + + vm.onSetRoomNotifState(RoomNotifState.AllMessagesLoud)} + onClick={(evt) => evt.stopPropagation()} + > + {snapshot.roomNotifState === RoomNotifState.AllMessagesLoud && checkComponent} + + vm.onSetRoomNotifState(RoomNotifState.MentionsOnly)} + onClick={(evt) => evt.stopPropagation()} + > + {snapshot.roomNotifState === RoomNotifState.MentionsOnly && checkComponent} + + vm.onSetRoomNotifState(RoomNotifState.Mute)} + onClick={(evt) => evt.stopPropagation()} + > + {snapshot.roomNotifState === RoomNotifState.Mute && checkComponent} + +
+
+ ); +} diff --git a/packages/shared-components/src/room-list/RoomListItem/RoomNotifs.ts b/packages/shared-components/src/room-list/RoomListItem/RoomNotifs.ts new file mode 100644 index 0000000000..06fc0fc23d --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/RoomNotifs.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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. + */ + +/** + * Notification state for a room. + */ +export enum RoomNotifState { + /** All messages (default) */ + AllMessages = "all_messages", + /** All messages with sound */ + AllMessagesLoud = "all_messages_loud", + /** Only mentions and keywords */ + MentionsOnly = "mentions_only", + /** Muted */ + Mute = "mute", +} diff --git a/packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItem.test.tsx.snap b/packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItem.test.tsx.snap new file mode 100644 index 0000000000..ff1ccad613 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItem.test.tsx.snap @@ -0,0 +1,1236 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` > renders Bold story 1`] = ` +
+
+
+ + +
+ + +
+
+
+`; + +exports[` > renders Default story 1`] = ` +
+
+
+ + +
+ + +
+
+
+`; + +exports[` > renders Invitation story 1`] = ` +
+
+
+ + +
+ +
+ +
+
+ +`; + +exports[` > renders NoMessagePreview story 1`] = ` +
+
+
+ + +
+ + +
+
+ +`; + +exports[` > renders Selected story 1`] = ` +
+
+
+ + +
+ + +
+
+ +`; + +exports[` > renders UnsentMessage story 1`] = ` +
+
+
+ + +
+ +
+ +
+ + +`; + +exports[` > renders WithHoverMenu story 1`] = ` +
+
+
+ + +
+ + +
+
+ +`; + +exports[` > renders WithMention story 1`] = ` +
+
+
+ + +
+ +
+ +
+ + +`; + +exports[` > renders WithNotification story 1`] = ` +
+
+
+ + +
+ +
+ +
+ + +`; diff --git a/packages/shared-components/src/room-list/RoomListItem/default-snapshot.ts b/packages/shared-components/src/room-list/RoomListItem/default-snapshot.ts new file mode 100644 index 0000000000..b5e263567f --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/default-snapshot.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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 { type RoomListItemSnapshot } from "./RoomListItem"; +import { RoomNotifState } from "./RoomNotifs"; + +export const mockRoom = { name: "General" }; + +export const defaultSnapshot: RoomListItemSnapshot = { + id: "!room:server", + room: mockRoom, + name: "General", + isBold: false, + messagePreview: "Alice: Hey everyone!", + notification: { + hasAnyNotificationOrActivity: false, + isUnsentMessage: false, + invited: false, + isMention: false, + isActivityNotification: false, + isNotification: false, + hasUnreadCount: false, + count: 0, + muted: false, + }, + showMoreOptionsMenu: true, + showNotificationMenu: true, + isFavourite: false, + isLowPriority: false, + canInvite: true, + canCopyRoomLink: true, + canMarkAsRead: false, + canMarkAsUnread: true, + roomNotifState: RoomNotifState.AllMessages, +}; diff --git a/packages/shared-components/src/room-list/RoomListItem/index.ts b/packages/shared-components/src/room-list/RoomListItem/index.ts new file mode 100644 index 0000000000..df4a68bf64 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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. + */ + +export { RoomListItemView } from "./RoomListItem"; +export type { + RoomListItemSnapshot, + RoomItemViewModel, + RoomListItemActions, + RoomListItemViewProps, +} from "./RoomListItem"; +export { RoomListItemNotificationMenu } from "./RoomListItemNotificationMenu"; +export type { RoomListItemNotificationMenuProps } from "./RoomListItemNotificationMenu"; +export { RoomListItemMoreOptionsMenu, MoreOptionContent } from "./RoomListItemMoreOptionsMenu"; +export type { RoomListItemMoreOptionsMenuProps } from "./RoomListItemMoreOptionsMenu"; +export { RoomListItemHoverMenu } from "./RoomListItemHoverMenu"; +export type { RoomListItemHoverMenuProps } from "./RoomListItemHoverMenu"; +export { RoomListItemContextMenu } from "./RoomListItemContextMenu"; +export type { RoomListItemContextMenuProps } from "./RoomListItemContextMenu"; +export { NotificationDecoration } from "./NotificationDecoration"; +export type { NotificationDecorationProps, NotificationDecorationData } from "./NotificationDecoration"; +export { RoomNotifState } from "./RoomNotifs"; diff --git a/packages/shared-components/src/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.module.css b/packages/shared-components/src/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.module.css new file mode 100644 index 0000000000..29db6d1bd6 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.module.css @@ -0,0 +1,32 @@ +/* + * Copyright 2025 Element Creations Ltd. + * + * 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. + */ + +.roomListPrimaryFilters { + padding: var(--cpd-space-2x) var(--cpd-space-4x) var(--cpd-space-2x) var(--cpd-space-3x); +} + +/* Hide filters that are wrapping when collapsed */ +.roomListPrimaryFilters :global(.wrapping) { + display: none; +} + +.list { + /** + * The InteractionObserver needs the height to be set to work properly. + */ + height: 100%; + flex: 1; +} + +/* IconButton styles for chevron */ +.iconButton svg { + transition: transform 0.1s linear; +} + +.iconButton[aria-expanded="true"] svg { + transform: rotate(180deg); +} diff --git a/packages/shared-components/src/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.stories.tsx b/packages/shared-components/src/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.stories.tsx new file mode 100644 index 0000000000..65cf75e132 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.stories.tsx @@ -0,0 +1,85 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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 { fn } from "storybook/test"; + +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { RoomListPrimaryFilters } from "./RoomListPrimaryFilters"; +import type { FilterId } from "./useVisibleFilters"; + +const meta: Meta = { + title: "Room List/RoomListPrimaryFilters", + component: RoomListPrimaryFilters, + tags: ["autodocs"], + args: { + onToggleFilter: fn(), + }, +}; + +export default meta; +type Story = StoryObj; + +// All available filter IDs +const allFilterIds: FilterId[] = ["unread", "people", "rooms", "favourite", "mentions", "invites", "low_priority"]; + +// Subset of filters for narrow container tests +const fewFilterIds: FilterId[] = ["people", "rooms", "unread"]; + +export const Default: Story = { + args: { + filterIds: allFilterIds, + }, +}; + +export const PeopleSelected: Story = { + args: { + filterIds: allFilterIds, + activeFilterId: "people", + }, +}; + +export const NoFilters: Story = { + args: { + filterIds: [], + }, +}; + +/** + * Narrow container that causes filters to wrap. + * The chevron button should appear to expand/collapse the filter list. + */ +export const NarrowContainer: Story = { + args: { + filterIds: fewFilterIds, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +/** + * Narrow container with active filter that would wrap. + * When collapsed, the active filter should move to the front. + */ +export const NarrowWithActiveWrappingFilter: Story = { + args: { + filterIds: fewFilterIds, + activeFilterId: "unread", + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; diff --git a/packages/shared-components/src/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.test.tsx b/packages/shared-components/src/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.test.tsx new file mode 100644 index 0000000000..a86181da15 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.test.tsx @@ -0,0 +1,140 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, { act } from "react"; +import { render, screen } from "@test-utils"; +import userEvent from "@testing-library/user-event"; +import { composeStories } from "@storybook/react-vite"; +import { describe, it, expect, vi, beforeEach } from "vitest"; + +import * as stories from "./RoomListPrimaryFilters.stories"; + +const { Default, PeopleSelected, NoFilters, NarrowContainer, NarrowWithActiveWrappingFilter } = composeStories(stories); + +describe(" stories", () => { + describe("snapshots", () => { + it("renders Default story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders PeopleSelected story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders NoFilters story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders NarrowContainer story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders NarrowWithActiveWrappingFilter story", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + }); + + describe("behavior", () => { + it("should call onToggleFilter when a filter is clicked", async () => { + const user = userEvent.setup(); + render(); + + await user.click(screen.getByRole("option", { name: "People" })); + + expect(Default.args.onToggleFilter).toHaveBeenCalled(); + }); + }); + + describe("resize behavior", () => { + let resizeCallback: ResizeObserverCallback; + + beforeEach(() => { + globalThis.ResizeObserver = class MockResizeObserver { + public constructor(callback: ResizeObserverCallback) { + resizeCallback = callback; + } + public observe = vi.fn(); + public unobserve = vi.fn(); + public disconnect = vi.fn(); + } as unknown as typeof ResizeObserver; + }); + + function mockFiltersNotWrapping(): void { + vi.spyOn(screen.getByText("People"), "offsetLeft", "get").mockReturnValue(0); + vi.spyOn(screen.getByText("Rooms"), "offsetLeft", "get").mockReturnValue(30); + vi.spyOn(screen.getByText("Unreads"), "offsetLeft", "get").mockReturnValue(60); + + const listbox = screen.getByRole("listbox", { name: "Room list filters" }); + act(() => resizeCallback([{ target: listbox } as any], {} as ResizeObserver)); + } + + function mockUnreadWrapping(): void { + vi.spyOn(screen.getByText("People"), "offsetLeft", "get").mockReturnValue(0); + vi.spyOn(screen.getByText("Rooms"), "offsetLeft", "get").mockReturnValue(30); + vi.spyOn(screen.getByText("Unreads"), "offsetLeft", "get").mockReturnValue(0); + + const listbox = screen.getByRole("listbox", { name: "Room list filters" }); + act(() => resizeCallback([{ target: listbox } as any], {} as ResizeObserver)); + } + + it("should hide wrapping filters and show chevron", () => { + render(); + mockUnreadWrapping(); + + expect(screen.queryByRole("option", { name: "Unreads" })).toBeNull(); + expect(screen.getByRole("button", { name: "Expand filter list" })).toBeInTheDocument(); + }); + + it("should expand and collapse filter list with chevron button", async () => { + const user = userEvent.setup(); + render(); + mockUnreadWrapping(); + + expect(screen.queryByRole("option", { name: "Unreads" })).toBeNull(); + + await user.click(screen.getByRole("button", { name: "Expand filter list" })); + expect(screen.getByRole("option", { name: "Unreads" })).toBeVisible(); + + await user.click(screen.getByRole("button", { name: "Collapse filter list" })); + expect(screen.queryByRole("option", { name: "Unreads" })).toBeNull(); + }); + + it("should move active filter to front when collapsed and wrapping", () => { + render(); + mockUnreadWrapping(); + + const listbox = screen.getByRole("listbox", { name: "Room list filters" }); + expect(listbox.children[0]).toBe(screen.getByRole("option", { name: "Unreads" })); + }); + + it("should restore original filter order when expanded", async () => { + const user = userEvent.setup(); + render(); + mockUnreadWrapping(); + + await user.click(screen.getByRole("button", { name: "Expand filter list" })); + + const listbox = screen.getByRole("listbox", { name: "Room list filters" }); + expect(listbox.children[0]).toBe(screen.getByRole("option", { name: "People" })); + }); + + it("should handle resize from non-wrapping to wrapping", () => { + render(); + mockFiltersNotWrapping(); + + expect(screen.queryByRole("button", { name: "Expand filter list" })).toBeNull(); + + mockUnreadWrapping(); + expect(screen.getByRole("button", { name: "Expand filter list" })).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/shared-components/src/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.tsx b/packages/shared-components/src/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.tsx new file mode 100644 index 0000000000..561544a3a5 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListPrimaryFilters/RoomListPrimaryFilters.tsx @@ -0,0 +1,116 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, { type JSX, useId, useState } from "react"; +import { ChatFilter, IconButton } from "@vector-im/compound-web"; +import ChevronDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-down"; + +import { Flex } from "../../utils/Flex"; +import { _t } from "../../utils/i18n"; +import { useCollapseFilters } from "./useCollapseFilters"; +import { useVisibleFilters, type FilterId } from "./useVisibleFilters"; +import styles from "./RoomListPrimaryFilters.module.css"; + +/** + * Maps filter IDs to translated labels + */ +const filterIdToLabel = (filterId: FilterId): string => { + switch (filterId) { + case "unread": + return _t("room_list|filters|unread"); + case "people": + return _t("room_list|filters|people"); + case "rooms": + return _t("room_list|filters|rooms"); + case "favourite": + return _t("room_list|filters|favourite"); + case "mentions": + return _t("room_list|filters|mentions"); + case "invites": + return _t("room_list|filters|invites"); + case "low_priority": + return _t("room_list|filters|low_priority"); + } +}; + +/** + * Props for RoomListPrimaryFilters component + */ +export interface RoomListPrimaryFiltersProps { + /** Array of filter IDs to display */ + filterIds: FilterId[]; + /** Currently active filter ID (if any) */ + activeFilterId?: FilterId; + /** Callback when a filter is toggled */ + onToggleFilter: (filterId: FilterId) => void; +} + +/** + * The primary filters component for the room list. + * Displays a collapsible list of filters with expand/collapse functionality. + */ +export const RoomListPrimaryFilters: React.FC = ({ + filterIds, + activeFilterId, + onToggleFilter, +}): JSX.Element | null => { + const id = useId(); + const [isExpanded, setIsExpanded] = useState(false); + + const { + ref, + isWrapping: displayChevron, + wrappingIndex, + } = useCollapseFilters(isExpanded, "wrapping"); + const visibleFilterIds = useVisibleFilters(filterIds, activeFilterId, wrappingIndex); + + return ( + + {displayChevron && ( + setIsExpanded((expanded) => !expanded)} + > + + + )} + + {visibleFilterIds.map((filterId, index) => ( + onToggleFilter(filterId)} + > + {filterIdToLabel(filterId)} + + ))} + + + ); +}; diff --git a/packages/shared-components/src/room-list/RoomListPrimaryFilters/__snapshots__/RoomListPrimaryFilters.test.tsx.snap b/packages/shared-components/src/room-list/RoomListPrimaryFilters/__snapshots__/RoomListPrimaryFilters.test.tsx.snap new file mode 100644 index 0000000000..74c281bde5 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListPrimaryFilters/__snapshots__/RoomListPrimaryFilters.test.tsx.snap @@ -0,0 +1,388 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` stories > snapshots > renders Default story 1`] = ` +
+
+ +
+ + + + + + + +
+
+
+`; + +exports[` stories > snapshots > renders NarrowContainer story 1`] = ` +
+
+
+ +
+ + + +
+
+
+
+`; + +exports[` stories > snapshots > renders NarrowWithActiveWrappingFilter story 1`] = ` +
+
+
+ +
+ + + +
+
+
+
+`; + +exports[` stories > snapshots > renders NoFilters story 1`] = ` +
+
+
+
+
+`; + +exports[` stories > snapshots > renders PeopleSelected story 1`] = ` +
+
+ +
+ + + + + + + +
+
+
+`; diff --git a/packages/shared-components/src/room-list/RoomListPrimaryFilters/index.tsx b/packages/shared-components/src/room-list/RoomListPrimaryFilters/index.tsx new file mode 100644 index 0000000000..7697d4829c --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListPrimaryFilters/index.tsx @@ -0,0 +1,12 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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. + */ + +export { RoomListPrimaryFilters } from "./RoomListPrimaryFilters"; +export type { RoomListPrimaryFiltersProps } from "./RoomListPrimaryFilters"; +export { useCollapseFilters } from "./useCollapseFilters"; +export { useVisibleFilters } from "./useVisibleFilters"; +export type { FilterId } from "./useVisibleFilters"; diff --git a/packages/shared-components/src/room-list/RoomListPrimaryFilters/useCollapseFilters.ts b/packages/shared-components/src/room-list/RoomListPrimaryFilters/useCollapseFilters.ts new file mode 100644 index 0000000000..e3fbf74e54 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListPrimaryFilters/useCollapseFilters.ts @@ -0,0 +1,71 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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 { useEffect, useRef, useState, type RefObject } from "react"; + +/** + * A hook to manage the wrapping of filters in the room list. + * It observes the filter list and hides filters that are wrapping when the list is not expanded. + * @param isExpanded + * @param wrappingClassName - the CSS class to apply to wrapping filters + * @returns an object containing: + * - `ref`: a ref to put on the filter list element + * - `isWrapping`: a boolean indicating if the filters are wrapping + * - `wrappingIndex`: the index of the first filter that is wrapping + */ +export function useCollapseFilters( + isExpanded: boolean, + wrappingClassName: string, +): { + ref: RefObject; + isWrapping: boolean; + wrappingIndex: number; +} { + const ref = useRef(null); + const [isWrapping, setIsWrapping] = useState(false); + const [wrappingIndex, setWrappingIndex] = useState(-1); + + useEffect(() => { + if (!ref.current) return; + + const hideFilters = (list: Element): void => { + let isWrapping = false; + Array.from(list.children).forEach((node, i): void => { + const child = node as HTMLElement; + child.setAttribute("aria-hidden", "false"); + child.classList.remove(wrappingClassName); + + // If the filter list is expanded, all filters are visible + if (isExpanded) return; + + // If the previous element is on the left element of the current one, it means that the filter is wrapping + const previousSibling = child.previousElementSibling as HTMLElement | null; + if (previousSibling && child.offsetLeft <= previousSibling.offsetLeft) { + if (!isWrapping) setWrappingIndex(i); + isWrapping = true; + } + + // If the filter is wrapping, we hide it + child.classList.toggle(wrappingClassName, isWrapping); + child.setAttribute("aria-hidden", isWrapping.toString()); + }); + + if (!isWrapping) setWrappingIndex(-1); + setIsWrapping(isExpanded || isWrapping); + }; + + hideFilters(ref.current); + const observer = new ResizeObserver((entries) => entries.forEach((entry) => hideFilters(entry.target))); + + observer.observe(ref.current); + return () => { + observer.disconnect(); + }; + }, [isExpanded, wrappingClassName]); + + return { ref, isWrapping, wrappingIndex }; +} diff --git a/packages/shared-components/src/room-list/RoomListPrimaryFilters/useVisibleFilters.ts b/packages/shared-components/src/room-list/RoomListPrimaryFilters/useVisibleFilters.ts new file mode 100644 index 0000000000..73a580b4d9 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListPrimaryFilters/useVisibleFilters.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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 { useEffect, useState } from "react"; + +/** + * Standard filter identifiers that can be used across implementations. + * These are stable keys - the view layer maps them to translated labels. + */ +export type FilterId = "unread" | "people" | "rooms" | "favourite" | "mentions" | "invites" | "low_priority"; + +/** + * A hook to sort the filter IDs by active state. + * The list is sorted if the active filter index is greater than or equal to the wrapping index. + * If the wrapping index is -1, the filters are not sorted. + * + * @param filterIds - the list of filter IDs to sort. + * @param activeFilterId - the currently active filter ID (if any). + * @param wrappingIndex - the index of the first filter that is wrapping. + */ +export function useVisibleFilters( + filterIds: FilterId[], + activeFilterId: FilterId | undefined, + wrappingIndex: number, +): FilterId[] { + // By default, the filters are not sorted + const [sortedFilterIds, setSortedFilterIds] = useState(filterIds); + + useEffect(() => { + const activeIndex = activeFilterId ? filterIds.indexOf(activeFilterId) : -1; + const isActiveFilterWrapping = activeIndex >= wrappingIndex; + // If the active filter is not wrapping, we don't need to sort the filters + if (!isActiveFilterWrapping || wrappingIndex === -1) { + setSortedFilterIds(filterIds); + return; + } + + // Sort the filters with the active filter at first position + setSortedFilterIds( + filterIds.slice().sort((filterA, filterB) => { + // If the filter is active, it should be at the top of the list + if (filterA === activeFilterId && filterB !== activeFilterId) return -1; + if (filterA !== activeFilterId && filterB === activeFilterId) return 1; + // If both filters are active or not, keep their original order + return 0; + }), + ); + }, [filterIds, activeFilterId, wrappingIndex]); + + return sortedFilterIds; +} diff --git a/packages/shared-components/src/room-list/RoomListView/RoomListEmptyState.module.css b/packages/shared-components/src/room-list/RoomListView/RoomListEmptyState.module.css new file mode 100644 index 0000000000..204e7615a4 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListView/RoomListEmptyState.module.css @@ -0,0 +1,33 @@ +/* + * Copyright 2025 Element Creations Ltd. + * + * 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. + */ + +.genericPlaceholder { + align-self: center; + /** It should take 2/3 of the width **/ + width: 66%; + /** It should be positioned at 1/3 of the height **/ + padding-top: 33%; +} + +.title { + font: var(--cpd-font-body-lg-semibold); + text-align: center; +} + +.description { + font: var(--cpd-font-body-sm-regular); + color: var(--cpd-color-text-secondary); + text-align: center; +} + +.defaultPlaceholder { + margin-top: var(--cpd-space-4x); +} + +.genericPlaceholder button { + width: 100%; +} diff --git a/packages/shared-components/src/room-list/RoomListView/RoomListEmptyState.tsx b/packages/shared-components/src/room-list/RoomListView/RoomListEmptyState.tsx new file mode 100644 index 0000000000..98e51d6878 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListView/RoomListEmptyState.tsx @@ -0,0 +1,182 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, { type JSX, type PropsWithChildren, type ReactNode } from "react"; +import { Button } from "@vector-im/compound-web"; +import ChatIcon from "@vector-im/compound-design-tokens/assets/web/icons/chat"; +import RoomIcon from "@vector-im/compound-design-tokens/assets/web/icons/room"; + +import { Flex } from "../../utils/Flex"; +import { _t } from "../../utils/i18n"; +import { useViewModel } from "../../viewmodel"; +import type { RoomListViewModel } from "./RoomListView"; +import styles from "./RoomListEmptyState.module.css"; + +/** + * Props for RoomListEmptyState component + */ +export interface RoomListEmptyStateProps { + /** The view model containing all data and callbacks */ + vm: RoomListViewModel; +} + +/** + * Empty state component for the room list. + * Displays appropriate message and actions based on the active filter. + */ +export const RoomListEmptyState: React.FC = ({ vm }): JSX.Element => { + const snapshot = useViewModel(vm); + + // If there is no active filter, show the default empty state + if (!snapshot.activeFilterId) { + return ( + + + + {snapshot.canCreateRoom && ( + + )} + + + ); + } + + // Handle different filter cases based on filter ID + switch (snapshot.activeFilterId) { + case "favourite": + return ( + + ); + case "people": + return ( + + ); + case "rooms": + return ( + + ); + case "unread": + return ( + vm.onToggleFilter(snapshot.activeFilterId!)} + /> + ); + case "invites": + return ( + vm.onToggleFilter(snapshot.activeFilterId!)} + /> + ); + case "mentions": + return ( + vm.onToggleFilter(snapshot.activeFilterId!)} + /> + ); + case "low_priority": + return ( + vm.onToggleFilter(snapshot.activeFilterId!)} + /> + ); + default: + return ( + + ); + } +}; + +interface GenericPlaceholderProps { + /** The title of the placeholder */ + title: string; + /** The description of the placeholder */ + description?: string; + /** Optional children (e.g., action buttons) */ + children?: ReactNode; +} + +/** + * A generic placeholder for the room list + */ +function GenericPlaceholder({ title, description, children }: PropsWithChildren): JSX.Element { + return ( + + {title} + {description && {description}} + {children} + + ); +} + +interface ActionPlaceholderProps { + /** The title to display */ + title: string; + /** The action button text */ + action: string; + /** Callback when the action button is clicked */ + onAction?: () => void; +} + +/** + * A placeholder for the room list when a filter is active + * The user can take action to toggle the filter + */ +function ActionPlaceholder({ title, action, onAction }: ActionPlaceholderProps): JSX.Element { + return ( + + {onAction && ( + + )} + + ); +} diff --git a/packages/shared-components/src/room-list/RoomListView/RoomListLoadingSkeleton.module.css b/packages/shared-components/src/room-list/RoomListView/RoomListLoadingSkeleton.module.css new file mode 100644 index 0000000000..2f65f7969d --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListView/RoomListLoadingSkeleton.module.css @@ -0,0 +1,24 @@ +/* + * Copyright 2025 Element Creations Ltd. + * + * 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. + */ + +.skeleton { + position: relative; + margin-left: 4px; + height: 100%; + flex: 1; +} + +.skeleton::before { + background-color: var(--cpd-color-bg-subtle-secondary); + width: 100%; + height: 100%; + content: ""; + position: absolute; + mask-repeat: repeat-y; + mask-size: auto 96px; + mask-image: url("./assets/skeleton.svg"); +} diff --git a/packages/shared-components/src/room-list/RoomListView/RoomListLoadingSkeleton.tsx b/packages/shared-components/src/room-list/RoomListView/RoomListLoadingSkeleton.tsx new file mode 100644 index 0000000000..6ab8b80de3 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListView/RoomListLoadingSkeleton.tsx @@ -0,0 +1,18 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, { type JSX } from "react"; + +import styles from "./RoomListLoadingSkeleton.module.css"; + +/** + * Loading skeleton component for the room list. + * Displays a repeating skeleton pattern while rooms are being fetched. + */ +export const RoomListLoadingSkeleton: React.FC = (): JSX.Element => { + return
; +}; diff --git a/packages/shared-components/src/room-list/RoomListView/RoomListView.stories.tsx b/packages/shared-components/src/room-list/RoomListView/RoomListView.stories.tsx new file mode 100644 index 0000000000..af812d7347 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListView/RoomListView.stories.tsx @@ -0,0 +1,220 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, { type JSX } from "react"; +import { fn } from "storybook/test"; + +import type { Meta, StoryObj } from "@storybook/react-vite"; +import type { FilterId } from "../RoomListPrimaryFilters"; +import { RoomListView, type RoomListSnapshot, type RoomListViewActions } from "./RoomListView"; +import { useMockedViewModel } from "../../viewmodel"; +import { + renderAvatar, + createGetRoomItemViewModel, + mockRoomIds, + smallListRoomIds, + largeListRoomIds, +} from "../story-mocks"; + +type RoomListViewProps = RoomListSnapshot & RoomListViewActions & { renderAvatar: (room: any) => React.ReactElement }; + +const mockFilterIds: FilterId[] = ["unread", "people", "rooms", "favourite"]; + +// Wrapper component that creates a mocked ViewModel +const RoomListViewWrapper = ({ + onToggleFilter, + createChatRoom, + createRoom, + getRoomItemViewModel, + updateVisibleRooms, + renderAvatar: renderAvatarProp, + ...rest +}: RoomListViewProps): JSX.Element => { + const vm = useMockedViewModel(rest, { + onToggleFilter, + createChatRoom, + createRoom, + getRoomItemViewModel, + updateVisibleRooms, + }); + return ; +}; + +const meta = { + title: "Room List/RoomListView", + component: RoomListViewWrapper, + tags: ["autodocs"], + decorators: [ + (Story) => ( +
+ +
+ ), + ], + args: { + // Snapshot properties (state) + isLoadingRooms: false, + isRoomListEmpty: false, + filterIds: mockFilterIds, + activeFilterId: undefined, + roomListState: { + activeRoomIndex: undefined, + spaceId: "!space:server", + filterKeys: undefined, + }, + roomIds: mockRoomIds, + canCreateRoom: true, + // Action properties (callbacks) + onToggleFilter: fn(), + createChatRoom: fn(), + createRoom: fn(), + getRoomItemViewModel: createGetRoomItemViewModel(mockRoomIds), + updateVisibleRooms: fn(), + renderAvatar, + }, + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/vlmt46QDdE4dgXDiyBJXqp/ER-33-Left-Panel?node-id=2925-19126", + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const Loading: Story = { + args: { + isLoadingRooms: true, + }, +}; + +export const Empty: Story = { + args: { + isRoomListEmpty: true, + }, +}; + +export const EmptyWithoutCreatePermission: Story = { + args: { + isRoomListEmpty: true, + canCreateRoom: false, + }, +}; + +export const WithActiveFilter: Story = { + args: { + filterIds: ["unread", "people", "rooms", "favourite"], + activeFilterId: "favourite", + roomListState: { + activeRoomIndex: undefined, + spaceId: "!space:server", + filterKeys: ["favourites"], + }, + }, +}; + +export const WithSelection: Story = { + args: { + roomListState: { + activeRoomIndex: 0, + spaceId: "!space:server", + filterKeys: undefined, + }, + }, +}; + +export const EmptyFavouriteFilter: Story = { + args: { + isRoomListEmpty: true, + roomIds: [], + filterIds: ["favourite", "people"], + activeFilterId: "favourite", + }, +}; + +export const EmptyPeopleFilter: Story = { + args: { + isRoomListEmpty: true, + roomIds: [], + filterIds: ["people", "rooms"], + activeFilterId: "people", + }, +}; + +export const EmptyRoomsFilter: Story = { + args: { + isRoomListEmpty: true, + roomIds: [], + filterIds: ["rooms", "people"], + activeFilterId: "rooms", + }, +}; + +export const EmptyUnreadFilter: Story = { + args: { + isRoomListEmpty: true, + roomIds: [], + filterIds: ["unread", "people"], + activeFilterId: "unread", + }, +}; + +export const EmptyInvitesFilter: Story = { + args: { + isRoomListEmpty: true, + roomIds: [], + filterIds: ["invites", "people"], + activeFilterId: "invites", + }, +}; + +export const EmptyMentionsFilter: Story = { + args: { + isRoomListEmpty: true, + roomIds: [], + filterIds: ["mentions", "people"], + activeFilterId: "mentions", + }, +}; + +export const EmptyLowPriorityFilter: Story = { + args: { + isRoomListEmpty: true, + roomIds: [], + filterIds: ["low_priority", "people"], + activeFilterId: "low_priority", + }, +}; + +export const SmallList: Story = { + args: { + roomIds: smallListRoomIds, + getRoomItemViewModel: createGetRoomItemViewModel(smallListRoomIds), + }, +}; + +export const LargeList: Story = { + args: { + roomIds: largeListRoomIds, + getRoomItemViewModel: createGetRoomItemViewModel(largeListRoomIds), + }, +}; diff --git a/packages/shared-components/src/room-list/RoomListView/RoomListView.test.tsx b/packages/shared-components/src/room-list/RoomListView/RoomListView.test.tsx new file mode 100644 index 0000000000..15237eed7e --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListView/RoomListView.test.tsx @@ -0,0 +1,177 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, screen } from "@test-utils"; +import userEvent from "@testing-library/user-event"; +import { VirtuosoMockContext } from "react-virtuoso"; +import { composeStories } from "@storybook/react-vite"; +import { describe, it, expect } from "vitest"; + +import * as stories from "./RoomListView.stories"; + +const { + Default, + Loading, + Empty, + EmptyWithoutCreatePermission, + WithActiveFilter, + SmallList, + LargeList, + EmptyFavouriteFilter, + EmptyPeopleFilter, + EmptyRoomsFilter, + EmptyUnreadFilter, + EmptyInvitesFilter, + EmptyMentionsFilter, + EmptyLowPriorityFilter, +} = composeStories(stories); + +const renderWithMockContext = (component: React.ReactElement): ReturnType => { + return render(component, { + wrapper: ({ children }) => ( + + {children} + + ), + }); +}; + +describe("", () => { + it("renders Default story", () => { + const { container } = renderWithMockContext(); + expect(container).toMatchSnapshot(); + }); + + it("renders Loading story", () => { + const { container } = renderWithMockContext(); + expect(container).toMatchSnapshot(); + }); + + it("renders Empty story", () => { + const { container } = renderWithMockContext(); + expect(container).toMatchSnapshot(); + }); + + it("renders EmptyWithoutCreatePermission story", () => { + const { container } = renderWithMockContext(); + expect(container).toMatchSnapshot(); + }); + + it("renders WithActiveFilter story", () => { + const { container } = renderWithMockContext(); + expect(container).toMatchSnapshot(); + }); + + it("renders SmallList story", () => { + const { container } = renderWithMockContext(); + expect(container).toMatchSnapshot(); + }); + + it("renders LargeList story", () => { + const { container } = renderWithMockContext(); + expect(container).toMatchSnapshot(); + }); + + it("renders EmptyFavouriteFilter story", () => { + const { container } = renderWithMockContext(); + expect(container).toMatchSnapshot(); + }); + + it("renders EmptyPeopleFilter story", () => { + const { container } = renderWithMockContext(); + expect(container).toMatchSnapshot(); + }); + + it("renders EmptyRoomsFilter story", () => { + const { container } = renderWithMockContext(); + expect(container).toMatchSnapshot(); + }); + + it("renders EmptyUnreadFilter story", () => { + const { container } = renderWithMockContext(); + expect(container).toMatchSnapshot(); + }); + + it("renders EmptyInvitesFilter story", () => { + const { container } = renderWithMockContext(); + expect(container).toMatchSnapshot(); + }); + + it("renders EmptyMentionsFilter story", () => { + const { container } = renderWithMockContext(); + expect(container).toMatchSnapshot(); + }); + + it("renders EmptyLowPriorityFilter story", () => { + const { container } = renderWithMockContext(); + expect(container).toMatchSnapshot(); + }); + + it("should call onToggleFilter when filter is clicked", async () => { + const user = userEvent.setup(); + renderWithMockContext(); + + await user.click(screen.getByRole("option", { name: "People" })); + + expect(Default.args.onToggleFilter).toHaveBeenCalled(); + }); + + it("should call createRoom when New room button is clicked", async () => { + const user = userEvent.setup(); + renderWithMockContext(); + + await user.click(screen.getByRole("button", { name: "New room" })); + + expect(Empty.args.createRoom).toHaveBeenCalled(); + }); + + it("should call createChatRoom when Start chat button is clicked", async () => { + const user = userEvent.setup(); + renderWithMockContext(); + + await user.click(screen.getByRole("button", { name: "Start chat" })); + + expect(Empty.args.createChatRoom).toHaveBeenCalled(); + }); + + it("should call onToggleFilter when Show all chats is clicked in unread empty state", async () => { + const user = userEvent.setup(); + renderWithMockContext(); + + await user.click(screen.getByRole("button", { name: "Show all chats" })); + + expect(EmptyUnreadFilter.args.onToggleFilter).toHaveBeenCalled(); + }); + + it("should call onToggleFilter when See all activity is clicked in invites empty state", async () => { + const user = userEvent.setup(); + renderWithMockContext(); + + await user.click(screen.getByRole("button", { name: "See all activity" })); + + expect(EmptyInvitesFilter.args.onToggleFilter).toHaveBeenCalled(); + }); + + it("should call onToggleFilter when See all activity is clicked in mentions empty state", async () => { + const user = userEvent.setup(); + renderWithMockContext(); + + await user.click(screen.getByRole("button", { name: "See all activity" })); + + expect(EmptyMentionsFilter.args.onToggleFilter).toHaveBeenCalled(); + }); + + it("should call onToggleFilter when See all activity is clicked in low priority empty state", async () => { + const user = userEvent.setup(); + renderWithMockContext(); + + await user.click(screen.getByRole("button", { name: "See all activity" })); + + expect(EmptyLowPriorityFilter.args.onToggleFilter).toHaveBeenCalled(); + }); +}); diff --git a/packages/shared-components/src/room-list/RoomListView/RoomListView.tsx b/packages/shared-components/src/room-list/RoomListView/RoomListView.tsx new file mode 100644 index 0000000000..0abf9aa0e1 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListView/RoomListView.tsx @@ -0,0 +1,101 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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, { type JSX, type ReactNode } from "react"; + +import { useViewModel, type ViewModel } from "../../viewmodel"; +import { RoomListPrimaryFilters, type FilterId } from "../RoomListPrimaryFilters"; +import { RoomListLoadingSkeleton } from "./RoomListLoadingSkeleton"; +import { RoomListEmptyState } from "./RoomListEmptyState"; +import { RoomList, type RoomListViewState } from "../RoomList"; +import { type RoomListItemSnapshot } from "../RoomListItem"; + +/** + * Snapshot for the room list view + */ +export type RoomListSnapshot = { + /** Whether the rooms are currently loading */ + isLoadingRooms: boolean; + /** Whether the room list is empty */ + isRoomListEmpty: boolean; + /** Array of filter IDs */ + filterIds: FilterId[]; + /** Currently active filter ID (if any) */ + activeFilterId?: FilterId; + /** Room list state */ + roomListState: RoomListViewState; + /** Array of room IDs for virtualization */ + roomIds: string[]; + /** Optional description for the empty state */ + emptyStateDescription?: string; + /** Optional action element for the empty state */ + emptyStateAction?: ReactNode; + /** Whether the user can create rooms */ + canCreateRoom?: boolean; +}; + +/** + * Actions interface for room list operations + */ +export interface RoomListViewActions { + /** Called when a filter is toggled */ + onToggleFilter: (filterId: FilterId) => void; + /** Called to create a new chat room */ + createChatRoom: () => void; + /** Called to create a new room */ + createRoom: () => void; + /** Get view model for a specific room (virtualization API) */ + getRoomItemViewModel: (roomId: string) => any; + /** Called when the visible range changes (virtualization API) */ + updateVisibleRooms: (startIndex: number, endIndex: number) => void; +} + +/** + * The view model type for the room list view + */ +export type RoomListViewModel = ViewModel & RoomListViewActions; + +/** + * Props for RoomListView component + */ +export interface RoomListViewProps { + /** The view model containing all data and callbacks */ + vm: RoomListViewModel; + /** Render function for room avatar */ + renderAvatar: (roomItem: RoomListItemSnapshot) => ReactNode; + /** Optional callback for keyboard events on the room list */ + onKeyDown?: (e: React.KeyboardEvent) => void; +} + +/** + * Room list view component that manages filters, loading states, empty states, and the room list. + */ +export const RoomListView: React.FC = ({ vm, renderAvatar, onKeyDown }): JSX.Element => { + const snapshot = useViewModel(vm); + let listBody: ReactNode; + + if (snapshot.isLoadingRooms) { + listBody = ; + } else if (snapshot.isRoomListEmpty) { + listBody = ; + } else { + listBody = ; + } + + return ( + <> +
+ +
+ {listBody} + + ); +}; diff --git a/packages/shared-components/src/room-list/RoomListView/__snapshots__/RoomListView.test.tsx.snap b/packages/shared-components/src/room-list/RoomListView/__snapshots__/RoomListView.test.tsx.snap new file mode 100644 index 0000000000..c518632039 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListView/__snapshots__/RoomListView.test.tsx.snap @@ -0,0 +1,11387 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` > renders Default story 1`] = ` +
+
+
+
+
+ + + + +
+
+
+
+
+
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ +
+ +
+
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + + + + + + +`; + +exports[` > renders Empty story 1`] = ` +
+
+
+
+
+ + + + +
+
+
+
+ + No chats yet + + + Get started by messaging someone or by creating a room + +
+ + +
+
+
+
+`; + +exports[` > renders EmptyFavouriteFilter story 1`] = ` +
+
+
+
+
+ + +
+
+
+
+ + You don't have favourite chats yet + + + You can add a chat to your favourites in the chat settings + +
+
+
+`; + +exports[` > renders EmptyInvitesFilter story 1`] = ` +
+
+
+
+
+ + +
+
+
+
+ + You don't have any unread invites + + +
+
+
+`; + +exports[` > renders EmptyLowPriorityFilter story 1`] = ` +
+
+
+
+
+ + +
+
+
+
+ + You don't have any low priority rooms + + +
+
+
+`; + +exports[` > renders EmptyMentionsFilter story 1`] = ` +
+
+
+
+
+ + +
+
+
+
+ + You don't have any unread mentions + + +
+
+
+`; + +exports[` > renders EmptyPeopleFilter story 1`] = ` +
+
+
+
+
+ + +
+
+
+
+ + You don’t have direct chats with anyone yet + + + You can deselect filters in order to see your other chats + +
+
+
+`; + +exports[` > renders EmptyRoomsFilter story 1`] = ` +
+
+
+
+
+ + +
+
+
+
+ + You’re not in any room yet + + + You can deselect filters in order to see your other chats + +
+
+
+`; + +exports[` > renders EmptyUnreadFilter story 1`] = ` +
+
+
+
+
+ + +
+
+
+
+ + Congrats! You don’t have any unread messages + + +
+
+
+`; + +exports[` > renders EmptyWithoutCreatePermission story 1`] = ` +
+
+
+
+
+ + + + +
+
+
+
+ + No chats yet + + + Get started by messaging someone + +
+ +
+
+
+
+`; + +exports[` > renders LargeList story 1`] = ` +
+
+
+
+
+ + + + +
+
+
+
+
+
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + + +
+ + +
+ + + +
+ + +
+ + + + + + + + +`; + +exports[` > renders Loading story 1`] = ` +
+
+
+
+
+ + + + +
+
+
+
+
+
+`; + +exports[` > renders SmallList story 1`] = ` +
+
+
+
+
+ + + + +
+
+
+
+
+
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + + + +`; + +exports[` > renders WithActiveFilter story 1`] = ` +
+
+
+
+
+ + + + +
+
+
+
+
+
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + +
+
+ + +
+ + + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + + + + + + +`; diff --git a/packages/shared-components/src/room-list/RoomListView/assets/skeleton.svg b/packages/shared-components/src/room-list/RoomListView/assets/skeleton.svg new file mode 100644 index 0000000000..adf56e4ed8 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListView/assets/skeleton.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/shared-components/src/room-list/RoomListView/index.tsx b/packages/shared-components/src/room-list/RoomListView/index.tsx new file mode 100644 index 0000000000..ecfbb6d5a2 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListView/index.tsx @@ -0,0 +1,12 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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. + */ + +export { RoomListView } from "./RoomListView"; +export type { RoomListViewProps, RoomListViewModel, RoomListSnapshot, RoomListViewActions } from "./RoomListView"; +export { RoomListLoadingSkeleton } from "./RoomListLoadingSkeleton"; +export { RoomListEmptyState } from "./RoomListEmptyState"; +export type { RoomListEmptyStateProps } from "./RoomListEmptyState"; diff --git a/packages/shared-components/src/room-list/story-mocks.tsx b/packages/shared-components/src/room-list/story-mocks.tsx new file mode 100644 index 0000000000..03450469c6 --- /dev/null +++ b/packages/shared-components/src/room-list/story-mocks.tsx @@ -0,0 +1,136 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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 { fn } from "storybook/test"; + +import type { RoomListItemSnapshot } from "./RoomListItem"; +import { RoomNotifState } from "./RoomListItem/RoomNotifs"; + +/** + * Mock avatar component for stories + */ +export const mockAvatar = (name: string): React.ReactElement => ( +
+ {name.substring(0, 2).toUpperCase()} +
+); + +/** + * Render avatar function for stories + */ +export const renderAvatar = (room: any): React.ReactElement => { + return mockAvatar(room?.name || "Room"); +}; + +/** + * Room names used for mock data + */ +const roomNames = [ + "General", + "Random", + "Engineering", + "Design", + "Product", + "Marketing", + "Sales", + "Support", + "Announcements", + "Off-topic", + "Team Alpha", + "Team Beta", + "Project X", + "Project Y", + "Water Cooler", + "Feedback", + "Ideas", + "Bugs", + "Features", + "Releases", +]; + +/** + * Create a mock room item snapshot for stories + */ +export const createMockRoomSnapshot = (id: string, name: string, index: number): RoomListItemSnapshot => ({ + id, + room: { name }, + name, + isBold: index % 3 === 0, + messagePreview: index % 2 === 0 ? `Last message in ${name}` : undefined, + notification: { + hasAnyNotificationOrActivity: index % 5 === 0, + isUnsentMessage: false, + invited: false, + isMention: index % 5 === 0, + isActivityNotification: false, + isNotification: index % 5 === 0, + hasUnreadCount: index % 5 === 0, + count: index % 5 === 0 ? index : 0, + muted: false, + }, + showMoreOptionsMenu: true, + showNotificationMenu: true, + isFavourite: false, + isLowPriority: false, + canInvite: true, + canCopyRoomLink: true, + canMarkAsRead: false, + canMarkAsUnread: true, + roomNotifState: RoomNotifState.AllMessages, +}); + +/** + * Create a mock getRoomItemViewModel function for stories + */ +export const createGetRoomItemViewModel = (roomIds: string[]): ((roomId: string) => any) => { + const viewModels = new Map(); + roomIds.forEach((roomId, index) => { + const name = roomNames[index % roomNames.length]; + const snapshot = createMockRoomSnapshot(roomId, name, index); + + const mockViewModel = { + getSnapshot: () => snapshot, + subscribe: fn(), + unsubscribe: fn(), + onOpenRoom: fn(), + onMarkAsRead: fn(), + onMarkAsUnread: fn(), + onToggleFavorite: fn(), + onToggleLowPriority: fn(), + onInvite: fn(), + onCopyRoomLink: fn(), + onLeaveRoom: fn(), + onSetRoomNotifState: fn(), + }; + viewModels.set(roomId, mockViewModel); + }); + + return (roomId: string) => viewModels.get(roomId); +}; + +/** + * Mock room IDs for different list sizes + */ +export const mockRoomIds = Array.from({ length: 20 }, (_, i) => `!room${i}:server`); +export const smallListRoomIds = mockRoomIds.slice(0, 5); +export const largeListRoomIds = Array.from({ length: 100 }, (_, i) => `!room${i}:server`); diff --git a/packages/shared-components/src/utils/VirtualizedList/VirtualizedList.tsx b/packages/shared-components/src/utils/VirtualizedList/VirtualizedList.tsx index 20e191ba38..adea593d07 100644 --- a/packages/shared-components/src/utils/VirtualizedList/VirtualizedList.tsx +++ b/packages/shared-components/src/utils/VirtualizedList/VirtualizedList.tsx @@ -1,9 +1,9 @@ /* -Copyright 2025 New Vector Ltd. - -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. -*/ + * Copyright 2026 Element Creations Ltd. + * + * 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, { useRef, type JSX, useCallback, useEffect, useState, useMemo } from "react"; import { type VirtuosoHandle, type ListRange, Virtuoso, type VirtuosoProps } from "react-virtuoso"; @@ -95,6 +95,19 @@ export interface IVirtualizedListProps extends Omit< * @returns */ onKeyDown?: (e: React.KeyboardEvent) => void; + + /** + * Optional total count of items (for virtualization with partial data loading). + * If provided, this will be used instead of items.length for the total count. + */ + totalCount?: number; + + /** + * Optional callback when the visible range of items changes. + * Useful for loading data on-demand as the user scrolls. + * @param range - The new visible range with startIndex and endIndex + */ + rangeChanged?: (range: ListRange) => void; } /** @@ -113,7 +126,17 @@ export type ScrollIntoViewOnChange = NonNullable< */ export function VirtualizedList(props: IVirtualizedListProps): React.ReactElement { // Extract our custom props to avoid conflicts with Virtuoso props - const { items, getItemComponent, isItemFocusable, getItemKey, context, onKeyDown, ...virtuosoProps } = props; + const { + items, + getItemComponent, + isItemFocusable, + getItemKey, + context, + onKeyDown, + totalCount, + rangeChanged, + ...virtuosoProps + } = props; /** Reference to the Virtuoso component for programmatic scrolling */ const virtuosoHandleRef = useRef(null); /** Reference to the DOM element containing the virtualized list */ @@ -324,6 +347,15 @@ export function VirtualizedList(props: IVirtualizedListProp [tabIndexKey, isFocused, props.context], ); + // Combine internal range tracking with optional external callback + const handleRangeChanged = useCallback( + (range: ListRange) => { + setVisibleRange(range); + rangeChanged?.(range); + }, + [rangeChanged], + ); + return ( (props: IVirtualizedListProp scrollerRef={scrollerRef} onKeyDown={keyDownCallback} context={listContext} - rangeChanged={setVisibleRange} + rangeChanged={handleRangeChanged} // virtuoso errors internally if you pass undefined. overscan={props.overscan || 0} data={props.items} + totalCount={totalCount} onFocus={onFocus} onBlur={onBlur} itemContent={getItemComponentInternal} diff --git a/packages/shared-components/src/viewmodel/index.ts b/packages/shared-components/src/viewmodel/index.ts index 7936e535a2..25e5ec60b8 100644 --- a/packages/shared-components/src/viewmodel/index.ts +++ b/packages/shared-components/src/viewmodel/index.ts @@ -12,5 +12,5 @@ export * from "./ViewModelSubscriptions"; export type * from "./ViewModel"; export * from "./MockViewModel"; export * from "./useCreateAutoDisposedViewModel"; -export * from "./useViewModel"; export * from "./useMockedViewModel"; +export * from "./useViewModel"; diff --git a/playwright/e2e/sliding-sync/sliding-sync.spec.ts b/playwright/e2e/sliding-sync/sliding-sync.spec.ts index b31deadace..38aff5928d 100644 --- a/playwright/e2e/sliding-sync/sliding-sync.spec.ts +++ b/playwright/e2e/sliding-sync/sliding-sync.spec.ts @@ -38,7 +38,7 @@ const test = base.extend<{ test.describe("Sliding Sync", () => { const checkOrder = async (wantOrder: string[], page: Page) => { - await expect(page.getByTestId("room-list").locator(".mx_RoomListItemView_text")).toHaveText(wantOrder); + await expect(page.getByTestId("room-list").getByTestId("room-name")).toHaveText(wantOrder); }; const bumpRoom = async (roomId: string, app: ElementAppPage) => { diff --git a/res/css/_components.pcss b/res/css/_components.pcss index a93f040b6c..65a0d79a67 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -266,14 +266,7 @@ @import "./views/right_panel/_VerificationPanel.pcss"; @import "./views/right_panel/_WidgetCard.pcss"; @import "./views/room_settings/_AliasSettings.pcss"; -@import "./views/rooms/RoomListPanel/_EmptyRoomList.pcss"; -@import "./views/rooms/RoomListPanel/_RoomList.pcss"; -@import "./views/rooms/RoomListPanel/_RoomListItemMenuView.pcss"; -@import "./views/rooms/RoomListPanel/_RoomListItemView.pcss"; @import "./views/rooms/RoomListPanel/_RoomListPanel.pcss"; -@import "./views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss"; -@import "./views/rooms/RoomListPanel/_RoomListSecondaryFilters.pcss"; -@import "./views/rooms/RoomListPanel/_RoomListSkeleton.pcss"; @import "./views/rooms/_AppsDrawer.pcss"; @import "./views/rooms/_Autocomplete.pcss"; @import "./views/rooms/_AuxPanel.pcss"; diff --git a/res/css/views/rooms/RoomListPanel/_EmptyRoomList.pcss b/res/css/views/rooms/RoomListPanel/_EmptyRoomList.pcss deleted file mode 100644 index a0fbfdaea7..0000000000 --- a/res/css/views/rooms/RoomListPanel/_EmptyRoomList.pcss +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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. - */ - -.mx_EmptyRoomList_GenericPlaceholder { - align-self: center; - /** It should take 2/3 of the width **/ - width: 66%; - /** It should be positioned at 1/3 of the height **/ - padding-top: 33%; - - .mx_EmptyRoomList_GenericPlaceholder_title { - font: var(--cpd-font-body-lg-semibold); - text-align: center; - } - - .mx_EmptyRoomList_GenericPlaceholder_description { - font: var(--cpd-font-body-sm-regular); - color: var(--cpd-color-text-secondary); - text-align: center; - } - - .mx_EmptyRoomList_DefaultPlaceholder { - margin-top: var(--cpd-space-4x); - } - - button { - width: 100%; - } -} diff --git a/res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss b/res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss deleted file mode 100644 index 4a7eb23b18..0000000000 --- a/res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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. - */ - -/** - * The RoomListItemView has the following structure: - * button--------------------------------------------------| - * | <-12px-> container------------------------------------| - * | | room avatar <-8px-> content----------------| - * | | | room_name <- 20px ->| - * | | | --------------------| <-- border - * |-------------------------------------------------------| - */ -.mx_RoomListItemView { - /* Remove button default style */ - color: inherit; - background: unset; - border: none; - padding: 0; - text-align: unset; - - cursor: pointer; - height: 48px; - width: 100%; - - padding-left: var(--cpd-space-3x); - font: var(--cpd-font-body-md-regular); - - /* Hide the menu by default */ - .mx_RoomListItemView_menu { - display: none; - } - - &:hover, - &:focus-visible, - /* When the context menu is opened */ - &[data-state="open"], - /* When the options and notifications menu are opened */ - &:has(.mx_RoomListItemMenuView > button[data-state="open"]) { - background-color: var(--cpd-color-bg-action-secondary-hovered); - - .mx_RoomListItemView_menu { - display: flex; - } - - &.mx_RoomListItemView_has_menu { - /** - * The figma uses 16px padding (--cpd-space-4x) but due to https://github.com/element-hq/compound-web/issues/331 - * the icon size of the menu is 18px instead of 20px with a different internal padding - * We need to use 18px to align the icon with the others icons - * 18px is not available in compound spacing - */ - .mx_RoomListItemView_content { - padding-right: 18px; - } - - /* When the menu is visible, hide the notification decoration to avoid clutter */ - .mx_RoomListItemView_notificationDecoration { - display: none; - } - } - } - - .mx_RoomListItemView_content { - height: 100%; - flex: 1; - /* The border is only under the room name and the future hover menu */ - border-bottom: var(--cpd-border-width-0-5) solid var(--cpd-color-bg-subtle-secondary); - box-sizing: border-box; - min-width: 0; - padding-right: var(--cpd-space-5x); - - .mx_RoomListItemView_text { - min-width: 0; - } - - .mx_RoomListItemView_roomName { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .mx_RoomListItemView_messagePreview { - font: var(--cpd-font-body-sm-regular); - color: var(--cpd-color-text-secondary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - } -} - -.mx_RoomListItemView_selected { - background-color: var(--cpd-color-bg-action-secondary-pressed); -} - -.mx_RoomListItemView_bold .mx_RoomListItemView_roomName { - font: var(--cpd-font-body-md-semibold); -} diff --git a/res/css/views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss b/res/css/views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss deleted file mode 100644 index 378f2e75da..0000000000 --- a/res/css/views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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. - */ - -.mx_RoomListPrimaryFilters { - padding: var(--cpd-space-2x) var(--cpd-space-4x) var(--cpd-space-2x) var(--cpd-space-3x); - - .mx_RoomListPrimaryFilters_wrapping { - display: none; - } - - .mx_RoomListPrimaryFilters_list { - /** - * The InteractionObserver needs the height to be set to work properly. - */ - height: 100%; - flex: 1; - } - - .mx_RoomListPrimaryFilters_IconButton { - svg { - transition: transform 0.1s linear; - } - } - - .mx_RoomListPrimaryFilters_IconButton[aria-expanded="true"] { - svg { - transform: rotate(180deg); - } - } -} diff --git a/res/css/views/rooms/RoomListPanel/_RoomListSecondaryFilters.pcss b/res/css/views/rooms/RoomListPanel/_RoomListSecondaryFilters.pcss deleted file mode 100644 index 0fa8dc12ae..0000000000 --- a/res/css/views/rooms/RoomListPanel/_RoomListSecondaryFilters.pcss +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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. - */ - -.mx_RoomListSecondaryFilters { - font: var(--cpd-font-body-md-medium); - margin: var(--cpd-space-2x); - margin-left: var(--cpd-space-1x); -} diff --git a/res/css/views/rooms/RoomListPanel/_RoomListSkeleton.pcss b/res/css/views/rooms/RoomListPanel/_RoomListSkeleton.pcss deleted file mode 100644 index 2e644cbba1..0000000000 --- a/res/css/views/rooms/RoomListPanel/_RoomListSkeleton.pcss +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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. - */ - -.mx_RoomListSkeleton { - position: relative; - margin-left: 4px; - height: 100%; - - &::before { - background-color: var(--cpd-color-bg-subtle-secondary); - width: 100%; - height: 100%; - - content: ""; - position: absolute; - mask-repeat: repeat-y; - mask-size: auto 96px; - mask-image: url("$(res)/img/element-icons/roomlist/room-list-item-skeleton.svg"); - } -} diff --git a/src/components/viewmodels/roomlist/MessagePreviewViewModel.tsx b/src/components/viewmodels/roomlist/MessagePreviewViewModel.tsx deleted file mode 100644 index 9e141c1379..0000000000 --- a/src/components/viewmodels/roomlist/MessagePreviewViewModel.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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 { useCallback, useEffect, useState } from "react"; - -import type { Room } from "matrix-js-sdk/src/matrix"; -import { type MessagePreview, MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; -import { useEventEmitter } from "../../../hooks/useEventEmitter"; - -interface MessagePreviewViewState { - /** - * A string representation of the message preview if available. - */ - message?: string; -} - -/** - * View model for rendering a message preview for a given room list item. - * @param room The room for which we're rendering the message preview. - * @see {@link MessagePreviewViewState} for what this view model returns. - */ -export function useMessagePreviewViewModel(room: Room): MessagePreviewViewState { - const [messagePreview, setMessagePreview] = useState(null); - - const updatePreview = useCallback(async (): Promise => { - /** - * The second argument to getPreviewForRoom is a tag id which doesn't really make - * much sense within the context of the new room list. We can pass an empty string - * to match all tags for now but we should remember to actually change the implementation - * in the store once we remove the legacy room list. - */ - const newPreview = await MessagePreviewStore.instance.getPreviewForRoom(room, ""); - setMessagePreview(newPreview); - }, [room]); - - /** - * Update when the message preview has changed for this room. - */ - useEventEmitter(MessagePreviewStore.instance, MessagePreviewStore.getPreviewChangedEventName(room), () => { - updatePreview(); - }); - - /** - * Do an initial fetch of the message preview. - */ - useEffect(() => { - updatePreview(); - }, [updatePreview]); - - return { - message: messagePreview?.text, - }; -} diff --git a/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx b/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx deleted file mode 100644 index 738a05b8c3..0000000000 --- a/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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 { useCallback } from "react"; -import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix"; - -import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; -import { useEventEmitterState } from "../../../hooks/useEventEmitter"; -import { useUnreadNotifications } from "../../../hooks/useUnreadNotifications"; -import { hasAccessToNotificationMenu, hasAccessToOptionsMenu } from "./utils"; -import DMRoomMap from "../../../utils/DMRoomMap"; -import { DefaultTagID } from "../../../stores/room-list/models"; -import { NotificationLevel } from "../../../stores/notifications/NotificationLevel"; -import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; -import { UIComponent } from "../../../settings/UIFeature"; -import dispatcher from "../../../dispatcher/dispatcher"; -import { clearRoomNotification, setMarkedUnreadState } from "../../../utils/notifications"; -import PosthogTrackers from "../../../PosthogTrackers"; -import { tagRoom } from "../../../utils/room/tagRoom"; -import { RoomNotifState } from "../../../RoomNotifs"; -import { useNotificationState } from "../../../hooks/useRoomNotificationState"; - -export interface RoomListItemMenuViewState { - /** - * Whether the more options menu should be shown. - */ - showMoreOptionsMenu: boolean; - /** - * Whether the notification menu should be shown. - */ - showNotificationMenu: boolean; - /** - * Whether the room is a favourite room. - */ - isFavourite: boolean; - /** - * Whether the room is a low priority room. - */ - isLowPriority: boolean; - /** - * Can invite other user's in the room. - */ - canInvite: boolean; - /** - * Can copy the room link. - */ - canCopyRoomLink: boolean; - /** - * Can mark the room as read. - */ - canMarkAsRead: boolean; - /** - * Can mark the room as unread. - */ - canMarkAsUnread: boolean; - /** - * Whether the notification is set to all messages. - */ - isNotificationAllMessage: boolean; - /** - * Whether the notification is set to all messages loud. - */ - isNotificationAllMessageLoud: boolean; - /** - * Whether the notification is set to mentions and keywords only. - */ - isNotificationMentionOnly: boolean; - /** - * Whether the notification is muted. - */ - isNotificationMute: boolean; - /** - * Mark the room as read. - * @param evt - */ - markAsRead: (evt: Event) => void; - /** - * Mark the room as unread. - * @param evt - */ - markAsUnread: (evt: Event) => void; - /** - * Toggle the room as favourite. - * @param evt - */ - toggleFavorite: (evt: Event) => void; - /** - * Toggle the room as low priority. - */ - toggleLowPriority: () => void; - /** - * Invite other users in the room. - * @param evt - */ - invite: (evt: Event) => void; - /** - * Copy the room link in the clipboard. - * @param evt - */ - copyRoomLink: (evt: Event) => void; - /** - * Leave the room. - * @param evt - */ - leaveRoom: (evt: Event) => void; - /** - * Set the room notification state. - * @param state - */ - setRoomNotifState: (state: RoomNotifState) => void; -} - -export function useRoomListItemMenuViewModel(room: Room): RoomListItemMenuViewState { - const matrixClient = useMatrixClientContext(); - const roomTags = useEventEmitterState(room, RoomEvent.Tags, () => room.tags); - const { level: notificationLevel } = useUnreadNotifications(room); - - const isDm = Boolean(DMRoomMap.shared().getUserIdForRoomId(room.roomId)); - const isFavourite = Boolean(roomTags[DefaultTagID.Favourite]); - const isLowPriority = Boolean(roomTags[DefaultTagID.LowPriority]); - const isArchived = Boolean(roomTags[DefaultTagID.Archived]); - - const showMoreOptionsMenu = hasAccessToOptionsMenu(room); - const showNotificationMenu = hasAccessToNotificationMenu(room, matrixClient.isGuest(), isArchived); - - const canMarkAsRead = notificationLevel > NotificationLevel.None; - const canMarkAsUnread = !canMarkAsRead && !isArchived; - - const canInvite = - room.canInvite(matrixClient.getUserId()!) && !isDm && shouldShowComponent(UIComponent.InviteUsers); - const canCopyRoomLink = !isDm; - - const [roomNotifState, setRoomNotifState] = useNotificationState(room); - const isNotificationAllMessage = roomNotifState === RoomNotifState.AllMessages; - const isNotificationAllMessageLoud = roomNotifState === RoomNotifState.AllMessagesLoud; - const isNotificationMentionOnly = roomNotifState === RoomNotifState.MentionsOnly; - const isNotificationMute = roomNotifState === RoomNotifState.Mute; - - // Actions - - const markAsRead = useCallback( - async (evt: Event): Promise => { - await clearRoomNotification(room, matrixClient); - PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuMarkRead", evt); - }, - [room, matrixClient], - ); - - const markAsUnread = useCallback( - async (evt: Event): Promise => { - await setMarkedUnreadState(room, matrixClient, true); - PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuMarkUnread", evt); - }, - [room, matrixClient], - ); - - const toggleFavorite = useCallback( - (evt: Event): void => { - tagRoom(room, DefaultTagID.Favourite); - PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuFavouriteToggle", evt); - }, - [room], - ); - - const toggleLowPriority = useCallback((): void => tagRoom(room, DefaultTagID.LowPriority), [room]); - - const invite = useCallback( - (evt: Event): void => { - dispatcher.dispatch({ - action: "view_invite", - roomId: room.roomId, - }); - PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuInviteItem", evt); - }, - [room], - ); - - const copyRoomLink = useCallback( - (evt: Event): void => { - dispatcher.dispatch({ - action: "copy_room", - room_id: room.roomId, - }); - PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuFavouriteToggle", evt); - }, - [room], - ); - - const leaveRoom = useCallback( - (evt: Event): void => { - dispatcher.dispatch({ - action: isArchived ? "forget_room" : "leave_room", - room_id: room.roomId, - }); - PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuLeaveItem", evt); - }, - [room, isArchived], - ); - - return { - showMoreOptionsMenu, - showNotificationMenu, - isFavourite, - isLowPriority, - canInvite, - canCopyRoomLink, - canMarkAsRead, - canMarkAsUnread, - isNotificationAllMessage, - isNotificationAllMessageLoud, - isNotificationMentionOnly, - isNotificationMute, - markAsRead, - markAsUnread, - toggleFavorite, - toggleLowPriority, - invite, - copyRoomLink, - leaveRoom, - setRoomNotifState, - }; -} diff --git a/src/components/viewmodels/roomlist/RoomListItemViewModel.ts b/src/components/viewmodels/roomlist/RoomListItemViewModel.ts new file mode 100644 index 0000000000..d2ae3dbeb5 --- /dev/null +++ b/src/components/viewmodels/roomlist/RoomListItemViewModel.ts @@ -0,0 +1,327 @@ +/* +Copyright 2026 Element Creations Ltd. + +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 { + BaseViewModel, + RoomNotifState, + type RoomListItemSnapshot, + type RoomListItemActions, +} from "@element-hq/web-shared-components"; +import { RoomEvent } from "matrix-js-sdk/src/matrix"; +import { CallType } from "matrix-js-sdk/src/webrtc/call"; + +import type { Room, MatrixClient } from "matrix-js-sdk/src/matrix"; +import type { RoomNotificationState } from "../../../stores/notifications/RoomNotificationState"; +import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; +import { NotificationStateEvents } from "../../../stores/notifications/NotificationState"; +import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; +import { UPDATE_EVENT } from "../../../stores/AsyncStore"; +import { DefaultTagID } from "../../../stores/room-list/models"; +import DMRoomMap from "../../../utils/DMRoomMap"; +import SettingsStore from "../../../settings/SettingsStore"; +import { NotificationLevel } from "../../../stores/notifications/NotificationLevel"; +import { hasAccessToNotificationMenu, hasAccessToOptionsMenu } from "./utils"; +import { EchoChamber } from "../../../stores/local-echo/EchoChamber"; +import { RoomNotifState as ElementRoomNotifState } from "../../../RoomNotifs"; +import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; +import { UIComponent } from "../../../settings/UIFeature"; +import { CallStore, CallStoreEvent } from "../../../stores/CallStore"; +import { clearRoomNotification, setMarkedUnreadState } from "../../../utils/notifications"; +import { tagRoom } from "../../../utils/room/tagRoom"; +import dispatcher from "../../../dispatcher/dispatcher"; +import { Action } from "../../../dispatcher/actions"; +import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import PosthogTrackers from "../../../PosthogTrackers"; + +interface RoomItemProps { + room: Room; + client: MatrixClient; +} + +/** + * View model for an individual room list item. + * Manages per-room subscriptions and updates only when this specific room's data changes. + * Implements RoomListItemActions to provide interaction callbacks. + */ +export class RoomListItemViewModel + extends BaseViewModel + implements RoomListItemActions +{ + private notifState: RoomNotificationState; + + public constructor(props: RoomItemProps) { + // Get notification state first so we can generate a complete initial snapshot + const notifState = RoomNotificationStateStore.instance.getRoomState(props.room); + const initialItem = RoomListItemViewModel.generateItemSync(props.room, props.client, notifState); + super(props, initialItem); + + this.notifState = notifState; + + // Subscribe to notification state changes for this room + this.disposables.trackListener(this.notifState, NotificationStateEvents.Update, this.onNotificationChanged); + + // Subscribe to message preview changes (will filter to this room) + this.disposables.trackListener(MessagePreviewStore.instance, UPDATE_EVENT, this.onMessagePreviewChanged); + + // Subscribe to settings changes for message preview toggle + const settingsWatchRef = SettingsStore.watchSetting( + "RoomList.showMessagePreview", + null, + this.onMessagePreviewSettingChanged, + ); + this.disposables.track(() => { + SettingsStore.unwatchSetting(settingsWatchRef); + }); + + // Subscribe to call state changes + this.disposables.trackListener(CallStore.instance, CallStoreEvent.ConnectedCalls, this.onCallStateChanged); + + // Subscribe to room-specific events + this.disposables.trackListener(props.room, RoomEvent.Name, this.onRoomChanged); + this.disposables.trackListener(props.room, RoomEvent.Tags, this.onRoomChanged); + + // Load message preview asynchronously (sync data is already complete) + void this.loadAndSetMessagePreview(); + } + + private onNotificationChanged = (): void => { + this.updateItem(); + }; + + private onMessagePreviewChanged = (): void => { + void this.loadAndSetMessagePreview(); + }; + + private onMessagePreviewSettingChanged = (): void => { + void this.loadAndSetMessagePreview(); + }; + + private onCallStateChanged = (): void => { + // Only update if call state for this room actually changed + const call = CallStore.instance.getCall(this.props.room.roomId); + const currentCallType = this.snapshot.current.notification.callType; + const newCallType = + call && call.participants.size > 0 ? (call.callType === CallType.Voice ? "voice" : "video") : undefined; + + if (currentCallType !== newCallType) { + this.updateItem(); + } + }; + + private onRoomChanged = (): void => { + this.updateItem(); + }; + + /** + * Update the item snapshot with current sync data. + * Preserves the message preview which is managed separately. + */ + private updateItem(): void { + const newItem = RoomListItemViewModel.generateItemSync(this.props.room, this.props.client, this.notifState); + // Preserve message preview - it's managed separately by loadAndSetMessagePreview + this.snapshot.set({ ...newItem, messagePreview: this.snapshot.current.messagePreview }); + } + + private getMessagePreviewTag(): string { + const isDm = Boolean(DMRoomMap.shared().getUserIdForRoomId(this.props.room.roomId)); + return isDm ? DefaultTagID.DM : DefaultTagID.Untagged; + } + + /** + * Load the message preview for this room if enabled. + * Returns undefined if previews are disabled or couldn't be loaded. + */ + private async loadMessagePreview(): Promise { + const shouldShowMessagePreview = SettingsStore.getValue("RoomList.showMessagePreview"); + if (!shouldShowMessagePreview) { + return undefined; + } + + const messagePreviewTag = this.getMessagePreviewTag(); + const preview = await MessagePreviewStore.instance.getPreviewForRoom(this.props.room, messagePreviewTag); + return preview?.text; + } + + /** + * Load and set the message preview if it differs from current. + */ + private async loadAndSetMessagePreview(): Promise { + const messagePreview = await this.loadMessagePreview(); + if (messagePreview !== this.snapshot.current.messagePreview) { + this.snapshot.merge({ messagePreview }); + } + } + + /** + * Generate a complete RoomListItem with all synchronous data. + * Message preview is loaded separately to avoid blocking initial render. + */ + private static generateItemSync( + room: Room, + client: MatrixClient, + notifState: RoomNotificationState, + ): RoomListItemSnapshot { + // Get room tags for menu state + const roomTags = room.tags; + const isDm = Boolean(DMRoomMap.shared().getUserIdForRoomId(room.roomId)); + + // Message preview will be loaded asynchronously and updated separately + const messagePreview = undefined; + + const isFavourite = Boolean(roomTags[DefaultTagID.Favourite]); + const isLowPriority = Boolean(roomTags[DefaultTagID.LowPriority]); + const isArchived = Boolean(roomTags[DefaultTagID.Archived]); + + // More options menu state + const showMoreOptionsMenu = hasAccessToOptionsMenu(room); + const showNotificationMenu = hasAccessToNotificationMenu(room, client.isGuest(), isArchived); + + // Notification levels + const canMarkAsRead = notifState.level > NotificationLevel.None; + const canMarkAsUnread = !canMarkAsRead && !isArchived; + + const canInvite = room.canInvite(client.getUserId()!) && !isDm && shouldShowComponent(UIComponent.InviteUsers); + const canCopyRoomLink = !isDm; + + // Get the current room notification state from EchoChamber + const echoChamber = EchoChamber.forRoom(room); + const elementRoomNotifState = echoChamber.notificationVolume; + + // Convert element-web RoomNotifState to shared-components RoomNotifState + let roomNotifState: RoomNotifState; + switch (elementRoomNotifState) { + case ElementRoomNotifState.AllMessages: + roomNotifState = RoomNotifState.AllMessages; + break; + case ElementRoomNotifState.AllMessagesLoud: + roomNotifState = RoomNotifState.AllMessagesLoud; + break; + case ElementRoomNotifState.MentionsOnly: + roomNotifState = RoomNotifState.MentionsOnly; + break; + case ElementRoomNotifState.Mute: + roomNotifState = RoomNotifState.Mute; + break; + default: + roomNotifState = RoomNotifState.AllMessages; + } + + const isNotificationMute = elementRoomNotifState === ElementRoomNotifState.Mute; + + // Video room and call state tracking + const call = CallStore.instance.getCall(room.roomId); + const participantCount = call?.participants.size ?? 0; + const hasParticipantsInCall = participantCount > 0; + const callType = + call?.callType === CallType.Voice ? "voice" : call?.callType === CallType.Video ? "video" : undefined; + + return { + id: room.roomId, + room, + name: room.name, + isBold: notifState.hasAnyNotificationOrActivity, + messagePreview, + notification: { + hasAnyNotificationOrActivity: notifState.hasAnyNotificationOrActivity || hasParticipantsInCall, + isUnsentMessage: notifState.isUnsentMessage, + invited: notifState.invited, + isMention: notifState.isMention, + isActivityNotification: notifState.isActivityNotification, + isNotification: notifState.isNotification, + hasUnreadCount: notifState.hasUnreadCount, + count: notifState.count, + muted: isNotificationMute, + callType: hasParticipantsInCall ? callType : undefined, + }, + showMoreOptionsMenu, + showNotificationMenu, + isFavourite, + isLowPriority, + canInvite, + canCopyRoomLink, + canMarkAsRead, + canMarkAsUnread, + roomNotifState, + }; + } + + public onOpenRoom = (): void => { + dispatcher.dispatch({ + action: Action.ViewRoom, + room_id: this.props.room.roomId, + metricsTrigger: "RoomList", + }); + }; + + public onMarkAsRead = async (): Promise => { + await clearRoomNotification(this.props.room, this.props.client); + PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuMarkRead"); + }; + + public onMarkAsUnread = async (): Promise => { + await setMarkedUnreadState(this.props.room, this.props.client, true); + PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuMarkUnread"); + }; + + public onToggleFavorite = (): void => { + tagRoom(this.props.room, DefaultTagID.Favourite); + PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuFavouriteToggle"); + }; + + public onToggleLowPriority = (): void => { + tagRoom(this.props.room, DefaultTagID.LowPriority); + }; + + public onInvite = (): void => { + dispatcher.dispatch({ + action: "view_invite", + roomId: this.props.room.roomId, + }); + PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuInviteItem"); + }; + + public onCopyRoomLink = (): void => { + dispatcher.dispatch({ + action: "copy_room", + room_id: this.props.room.roomId, + }); + }; + + public onLeaveRoom = (): void => { + const isArchived = Boolean(this.props.room.tags[DefaultTagID.Archived]); + dispatcher.dispatch({ + action: isArchived ? "forget_room" : "leave_room", + room_id: this.props.room.roomId, + }); + PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuLeaveItem"); + }; + + public onSetRoomNotifState = (notifState: RoomNotifState): void => { + // Convert shared-components RoomNotifState to element-web RoomNotifState + let elementNotifState: ElementRoomNotifState; + switch (notifState) { + case "all_messages": + elementNotifState = ElementRoomNotifState.AllMessages; + break; + case "all_messages_loud": + elementNotifState = ElementRoomNotifState.AllMessagesLoud; + break; + case "mentions_only": + elementNotifState = ElementRoomNotifState.MentionsOnly; + break; + case "mute": + elementNotifState = ElementRoomNotifState.Mute; + break; + default: + elementNotifState = ElementRoomNotifState.AllMessages; + } + + // Set the notification state using EchoChamber + const echoChamber = EchoChamber.forRoom(this.props.room); + echoChamber.notificationVolume = elementNotifState; + }; +} diff --git a/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx deleted file mode 100644 index 30576e2dc2..0000000000 --- a/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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 { useCallback, useEffect, useMemo, useState } from "react"; -import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix"; -import { CallType } from "matrix-js-sdk/src/webrtc/call"; - -import dispatcher from "../../../dispatcher/dispatcher"; -import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; -import { Action } from "../../../dispatcher/actions"; -import { hasAccessToNotificationMenu, hasAccessToOptionsMenu } from "./utils"; -import { _t } from "../../../languageHandler"; -import { type RoomNotificationState } from "../../../stores/notifications/RoomNotificationState"; -import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; -import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; -import { useEventEmitter, useEventEmitterState, useTypedEventEmitter } from "../../../hooks/useEventEmitter"; -import { DefaultTagID } from "../../../stores/room-list/models"; -import { useCall, useConnectionState, useParticipantCount } from "../../../hooks/useCall"; -import { CallEvent, type ConnectionState } from "../../../models/Call"; -import { NotificationStateEvents } from "../../../stores/notifications/NotificationState"; -import DMRoomMap from "../../../utils/DMRoomMap"; -import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; -import { useMessagePreviewToggle } from "./useMessagePreviewToggle"; - -export interface RoomListItemViewState { - /** - * The name of the room. - */ - name: string; - /** - * Whether the context menu should be shown. - */ - showContextMenu: boolean; - /** - * Whether the hover menu should be shown. - */ - showHoverMenu: boolean; - /** - * Open the room having given roomId. - */ - openRoom: () => void; - /** - * The a11y label for the room list item. - */ - a11yLabel: string; - /** - * The notification state of the room. - */ - notificationState: RoomNotificationState; - /** - * Whether the room should be bolded. - */ - isBold: boolean; - /** - * Whether the room is a video room - */ - isVideoRoom: boolean; - /** - * The connection state of the call. - * `null` if there is no call in the room. - */ - callConnectionState: ConnectionState | null; - /** - * Whether there are participants in the call. - */ - hasParticipantInCall: boolean; - /** - * Whether the call is a voice or video call. - */ - callType: CallType | undefined; - /** - * Pre-rendered and translated preview for the latest message in the room, or undefined - * if no preview should be shown. - */ - messagePreview: string | undefined; - /** - * Whether the notification decoration should be shown. - */ - showNotificationDecoration: boolean; -} - -/** - * View model for the room list item - * @see {@link RoomListItemViewState} for more information about what this view model returns. - */ -export function useRoomListItemViewModel(room: Room): RoomListItemViewState { - const matrixClient = useMatrixClientContext(); - const roomTags = useEventEmitterState(room, RoomEvent.Tags, () => room.tags); - const isArchived = Boolean(roomTags[DefaultTagID.Archived]); - const name = useEventEmitterState(room, RoomEvent.Name, () => room.name); - - const notificationState = useMemo(() => RoomNotificationStateStore.instance.getRoomState(room), [room]); - - const [a11yLabel, setA11yLabel] = useState(getA11yLabel(name, notificationState)); - const [{ isBold, invited, hasVisibleNotification }, setNotificationValues] = useState( - getNotificationValues(notificationState), - ); - useEffect(() => { - setA11yLabel(getA11yLabel(name, notificationState)); - }, [name, notificationState]); - - // Listen to changes in the notification state and update the values - useTypedEventEmitter(notificationState, NotificationStateEvents.Update, () => { - setA11yLabel(getA11yLabel(name, notificationState)); - setNotificationValues(getNotificationValues(notificationState)); - }); - - // If the notification reference change due to room change, update the values - useEffect(() => { - setNotificationValues(getNotificationValues(notificationState)); - }, [notificationState]); - - // We don't want to show the menus if - // - there is an invitation for this room - // - the user doesn't have access to notification and more options menus - const showContextMenu = !invited && hasAccessToOptionsMenu(room); - const showHoverMenu = - !invited && (showContextMenu || hasAccessToNotificationMenu(room, matrixClient.isGuest(), isArchived)); - - const messagePreview = useRoomMessagePreview(room); - - // Video room - const isVideoRoom = room.isElementVideoRoom() || room.isCallRoom(); - // EC video call or video room - const call = useCall(room.roomId); - const connectionState = useConnectionState(call); - const participantCount = useParticipantCount(call); - const callConnectionState = call ? connectionState : null; - - const showNotificationDecoration = hasVisibleNotification || participantCount > 0; - - // Actions - - const openRoom = useCallback((): void => { - dispatcher.dispatch({ - action: Action.ViewRoom, - room_id: room.roomId, - metricsTrigger: "RoomList", - }); - }, [room]); - - const [callType, setCallType] = useState(CallType.Video); - useTypedEventEmitter(call ?? undefined, CallEvent.CallTypeChanged, setCallType); - - return { - name, - notificationState, - showContextMenu, - showHoverMenu, - openRoom, - a11yLabel, - isBold, - isVideoRoom, - callConnectionState, - hasParticipantInCall: participantCount > 0, - messagePreview, - showNotificationDecoration, - callType: call ? callType : undefined, - }; -} - -/** - * Calculate the values from the notification state - * @param notificationState - */ -function getNotificationValues(notificationState: RoomNotificationState): { - computeA11yLabel: (name: string) => string; - isBold: boolean; - invited: boolean; - hasVisibleNotification: boolean; -} { - const invited = notificationState.invited; - const computeA11yLabel = (name: string): string => getA11yLabel(name, notificationState); - const isBold = notificationState.hasAnyNotificationOrActivity; - - const hasVisibleNotification = notificationState.hasAnyNotificationOrActivity || notificationState.muted; - - return { - computeA11yLabel, - isBold, - invited, - hasVisibleNotification, - }; -} - -/** - * Get the a11y label for the room list item - * @param roomName - * @param notificationState - */ -function getA11yLabel(roomName: string, notificationState: RoomNotificationState): string { - if (notificationState.isUnsentMessage) { - return _t("a11y|room_messsage_not_sent", { - roomName, - }); - } else if (notificationState.invited) { - return _t("a11y|room_n_unread_invite", { - roomName, - }); - } else if (notificationState.isMention) { - return _t("a11y|room_n_unread_messages_mentions", { - roomName, - count: notificationState.count, - }); - } else if (notificationState.hasUnreadCount) { - return _t("a11y|room_n_unread_messages", { - roomName, - count: notificationState.count, - }); - } else { - return _t("room_list|room|open_room", { roomName }); - } -} - -function useRoomMessagePreview(room: Room): string | undefined { - const { shouldShowMessagePreview } = useMessagePreviewToggle(); - const [previewText, setPreviewText] = useState(undefined); - - const updatePreview = useCallback(async () => { - if (!shouldShowMessagePreview) { - setPreviewText(undefined); - return; - } - - const roomIsDM = Boolean(DMRoomMap.shared().getUserIdForRoomId(room.roomId)); - // For the tag, we only care about whether the room is a DM or not as we don't show - // display names in previewsd for DMs, so anything else we just say is 'untagged' - // (even though it could actually be have other tags: we don't care about them). - const messagePreview = await MessagePreviewStore.instance.getPreviewForRoom( - room, - roomIsDM ? DefaultTagID.DM : DefaultTagID.Untagged, - ); - setPreviewText(messagePreview?.text); - }, [room, shouldShowMessagePreview]); - - // MessagePreviewStore and the other AsyncStores need to be converted to TypedEventEmitter - useEventEmitter(MessagePreviewStore.instance, MessagePreviewStore.getPreviewChangedEventName(room), () => { - updatePreview(); - }); - - useEffect(() => { - updatePreview(); - }, [updatePreview]); - - return previewText; -} diff --git a/src/components/viewmodels/roomlist/RoomListViewModel.tsx b/src/components/viewmodels/roomlist/RoomListViewModel.tsx deleted file mode 100644 index a48d973b23..0000000000 --- a/src/components/viewmodels/roomlist/RoomListViewModel.tsx +++ /dev/null @@ -1,100 +0,0 @@ -/* -Copyright 2025 New Vector Ltd. - -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 { useCallback } from "react"; - -import type { Room } from "matrix-js-sdk/src/matrix"; -import { type PrimaryFilter, useFilteredRooms } from "./useFilteredRooms"; -import { createRoom as createRoomFunc, hasCreateRoomRights } from "./utils"; -import { useEventEmitterState } from "../../../hooks/useEventEmitter"; -import { UPDATE_SELECTED_SPACE } from "../../../stores/spaces"; -import SpaceStore from "../../../stores/spaces/SpaceStore"; -import dispatcher from "../../../dispatcher/dispatcher"; -import { Action } from "../../../dispatcher/actions"; -import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; -import { useStickyRoomList } from "./useStickyRoomList"; -import { useRoomListNavigation } from "./useRoomListNavigation"; -import { type RoomsResult } from "../../../stores/room-list-v3/RoomListStoreV3"; - -export interface RoomListViewState { - /** - * Whether the list of rooms is being loaded. - */ - isLoadingRooms: boolean; - - /** - * The room results to be displayed (along with the spaceId and filter keys at the time of query) - */ - roomsResult: RoomsResult; - - /** - * Create a chat room - * @param e - The click event - */ - createChatRoom: () => void; - - /** - * Whether the user can create a room in the current space - */ - canCreateRoom: boolean; - - /** - * Create a room - * @param e - The click event - */ - createRoom: () => void; - - /** - * A list of objects that provide the view enough information - * to render primary room filters. - */ - primaryFilters: PrimaryFilter[]; - - /** - * The currently active primary filter. - * If no primary filter is active, this will be undefined. - */ - activePrimaryFilter?: PrimaryFilter; - - /** - * The index of the active room in the room list. - */ - activeIndex: number | undefined; -} - -/** - * View model for the new room list - * @see {@link RoomListViewState} for more information about what this view model returns. - */ -export function useRoomListViewModel(): RoomListViewState { - const matrixClient = useMatrixClientContext(); - const { isLoadingRooms, primaryFilters, activePrimaryFilter, roomsResult: filteredRooms } = useFilteredRooms(); - const { activeIndex, roomsResult } = useStickyRoomList(filteredRooms); - - useRoomListNavigation(roomsResult.rooms); - - const currentSpace = useEventEmitterState( - SpaceStore.instance, - UPDATE_SELECTED_SPACE, - () => SpaceStore.instance.activeSpaceRoom, - ); - const canCreateRoom = hasCreateRoomRights(matrixClient, currentSpace); - - const createChatRoom = useCallback(() => dispatcher.fire(Action.CreateChat), []); - const createRoom = useCallback(() => createRoomFunc(currentSpace), [currentSpace]); - - return { - isLoadingRooms, - roomsResult, - canCreateRoom, - createRoom, - createChatRoom, - primaryFilters, - activePrimaryFilter, - activeIndex, - }; -} diff --git a/src/components/viewmodels/roomlist/RoomListViewViewModel.ts b/src/components/viewmodels/roomlist/RoomListViewViewModel.ts new file mode 100644 index 0000000000..25f3cde1dc --- /dev/null +++ b/src/components/viewmodels/roomlist/RoomListViewViewModel.ts @@ -0,0 +1,450 @@ +/* +Copyright 2026 Element Creations Ltd. + +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 { + BaseViewModel, + type RoomListSnapshot, + type FilterId, + type RoomListViewActions, + type RoomListViewState, +} from "@element-hq/web-shared-components"; +import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix"; + +import { Action } from "../../../dispatcher/actions"; +import dispatcher from "../../../dispatcher/dispatcher"; +import { type ViewRoomDeltaPayload } from "../../../dispatcher/payloads/ViewRoomDeltaPayload"; +import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; +import RoomListStoreV3, { RoomListStoreV3Event, type RoomsResult } from "../../../stores/room-list-v3/RoomListStoreV3"; +import { FilterKey } from "../../../stores/room-list-v3/skip-list/filters"; +import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; +import { hasCreateRoomRights } from "./utils"; +import { RoomListItemViewModel } from "./RoomListItemViewModel"; +import { SdkContextClass } from "../../../contexts/SDKContext"; + +interface RoomListViewViewModelProps { + client: MatrixClient; +} + +const filterKeyToIdMap: Map = new Map([ + [FilterKey.UnreadFilter, "unread"], + [FilterKey.PeopleFilter, "people"], + [FilterKey.RoomsFilter, "rooms"], + [FilterKey.FavouriteFilter, "favourite"], + [FilterKey.MentionsFilter, "mentions"], + [FilterKey.InvitesFilter, "invites"], + [FilterKey.LowPriorityFilter, "low_priority"], +]); + +export class RoomListViewViewModel + extends BaseViewModel + implements RoomListViewActions +{ + // State tracking + private activeFilter: FilterKey | undefined = undefined; + private roomsResult: RoomsResult; + private lastActiveRoomIndex: number | undefined = undefined; + + // Child view model management + private roomItemViewModels = new Map(); + private roomsMap = new Map(); + + public constructor(props: RoomListViewViewModelProps) { + const activeSpace = SpaceStore.instance.activeSpaceRoom; + + // Get initial rooms + const roomsResult = RoomListStoreV3.instance.getSortedRoomsInActiveSpace(undefined); + const canCreateRoom = hasCreateRoomRights(props.client, activeSpace); + const filterIds = [...filterKeyToIdMap.values()]; + + super(props, { + // Initial view state - start with empty, will populate in async init + isLoadingRooms: RoomListStoreV3.instance.isLoadingRooms, + isRoomListEmpty: roomsResult.rooms.length === 0, + filterIds, + activeFilterId: undefined, + roomListState: { + activeRoomIndex: undefined, + spaceId: roomsResult.spaceId, + filterKeys: undefined, + }, + roomIds: roomsResult.rooms.map((room) => room.roomId), + canCreateRoom, + }); + + this.roomsResult = roomsResult; + + // Build initial roomsMap from roomsResult + this.updateRoomsMap(roomsResult); + + // Subscribe to room list updates + this.disposables.trackListener( + RoomListStoreV3.instance, + RoomListStoreV3Event.ListsUpdate as any, + this.onListsUpdate, + ); + + // Subscribe to room list loaded + this.disposables.trackListener( + RoomListStoreV3.instance, + RoomListStoreV3Event.ListsLoaded as any, + this.onListsLoaded, + ); + + // Subscribe to active room changes to update selected room + const dispatcherRef = dispatcher.register(this.onDispatch); + this.disposables.track(() => { + dispatcher.unregister(dispatcherRef); + }); + + // Track cleanup of all child view models + this.disposables.track(() => { + for (const viewModel of this.roomItemViewModels.values()) { + viewModel.dispose(); + } + this.roomItemViewModels.clear(); + }); + } + + public onToggleFilter = (filterId: FilterId): void => { + // Find the FilterKey by matching the filter ID + let filterKey: FilterKey | undefined = undefined; + for (const [key, id] of filterKeyToIdMap.entries()) { + if (id === filterId) { + filterKey = key; + break; + } + } + + if (filterKey === undefined) return; + + // Toggle the filter - if it's already active, deactivate it + const newFilter = this.activeFilter === filterKey ? undefined : filterKey; + this.activeFilter = newFilter; + + // Update rooms result with new filter + const filterKeys = this.activeFilter !== undefined ? [this.activeFilter] : undefined; + this.roomsResult = RoomListStoreV3.instance.getSortedRoomsInActiveSpace(filterKeys); + + // Update roomsMap immediately before clearing VMs + this.updateRoomsMap(this.roomsResult); + + // Clear view models since room list changed + this.clearViewModels(); + + this.updateRoomListData(); + }; + + /** + * Rebuild roomsMap when roomsResult changes. + * This maintains a quick lookup for room objects. + */ + private updateRoomsMap(roomsResult: RoomsResult): void { + this.roomsMap.clear(); + for (const room of roomsResult.rooms) { + this.roomsMap.set(room.roomId, room); + } + } + + /** + * Clear all child view models. + * Called when the room list structure changes (space change, filter change, etc.) + */ + private clearViewModels(): void { + for (const viewModel of this.roomItemViewModels.values()) { + viewModel.dispose(); + } + this.roomItemViewModels.clear(); + } + + /** + * Get the ordered list of room IDs. + */ + public get roomIds(): string[] { + return this.roomsResult.rooms.map((room) => room.roomId); + } + + /** + * Get a RoomListItemViewModel for a specific room. + * Creates a RoomListItemViewModel if needed, which manages per-room subscriptions. + * The view should call this only for visible rooms from the roomIds list. + * @throws Error if room is not found in roomsMap (indicates a programming error) + */ + public getRoomItemViewModel(roomId: string): RoomListItemViewModel { + // Check if we have a view model for this room + let viewModel = this.roomItemViewModels.get(roomId); + + if (!viewModel) { + const room = this.roomsMap.get(roomId); + if (!room) { + throw new Error(`Room ${roomId} not found in roomsMap`); + } + + // Create new view model + viewModel = new RoomListItemViewModel({ + room, + client: this.props.client, + }); + + this.roomItemViewModels.set(roomId, viewModel); + } + + // Return the view model - the view will call useViewModel() on it + return viewModel; + } + + /** + * Update which rooms are currently visible. + * Called by the view when scroll position changes. + * Disposes of view models for rooms no longer visible. + */ + public updateVisibleRooms(startIndex: number, endIndex: number): void { + const allRoomIds = this.roomIds; + const newVisibleIds = allRoomIds.slice(startIndex, Math.min(endIndex, allRoomIds.length)); + + const newVisibleSet = new Set(newVisibleIds); + + // Dispose view models for rooms no longer visible + for (const [roomId, viewModel] of this.roomItemViewModels.entries()) { + if (!newVisibleSet.has(roomId)) { + viewModel.dispose(); + this.roomItemViewModels.delete(roomId); + } + } + } + + private onDispatch = (payload: any): void => { + if (payload.action === Action.ActiveRoomChanged) { + // When the active room changes, update the room list data to reflect the new selected room + // Pass isRoomChange=true so sticky logic doesn't prevent the index from updating + this.updateRoomListData(true); + } else if (payload.action === Action.ViewRoomDelta) { + // Handle keyboard navigation shortcuts (Alt+ArrowUp/Down) + // This was previously handled by useRoomListNavigation hook + this.handleViewRoomDelta(payload as ViewRoomDeltaPayload); + } + }; + + /** + * Handle keyboard navigation shortcuts (Alt+ArrowUp/Down) to move between rooms. + * Supports both regular navigation and unread-only navigation. + * Migrated from useRoomListNavigation hook. + */ + private handleViewRoomDelta(payload: ViewRoomDeltaPayload): void { + const currentRoomId = SdkContextClass.instance.roomViewStore.getRoomId(); + if (!currentRoomId) return; + + const { delta, unread } = payload; + const rooms = this.roomsResult.rooms; + + const filteredRooms = unread + ? // Filter the rooms to only include unread ones and the active room + rooms.filter((room) => { + const state = RoomNotificationStateStore.instance.getRoomState(room); + return room.roomId === currentRoomId || state.isUnread; + }) + : rooms; + + const currentIndex = filteredRooms.findIndex((room) => room.roomId === currentRoomId); + if (currentIndex === -1) return; + + // Get the next/previous new room according to the delta + // Use slice to loop on the list + // If delta is -1 at the start of the list, it will go to the end + // If delta is 1 at the end of the list, it will go to the start + const [newRoom] = filteredRooms.slice((currentIndex + delta) % filteredRooms.length); + if (!newRoom) return; + + dispatcher.dispatch({ + action: Action.ViewRoom, + room_id: newRoom.roomId, + show_room_tile: true, // to make sure the room gets scrolled into view + metricsTrigger: "WebKeyboardShortcut", + metricsViaKeyboard: true, + }); + } + + /** + * Handle room list updates from RoomListStoreV3. + * + * This event fires when: + * - Room order changes (new messages, manual reordering) + * - Active space changes + * - Filters are applied + * - Rooms are added/removed + * + * Space changes are detected by comparing old vs new spaceId. + * This matches the old hook pattern where space changes were handled + * indirectly through room list updates. + */ + private onListsUpdate = (): void => { + const filterKeys = this.activeFilter !== undefined ? [this.activeFilter] : undefined; + const oldSpaceId = this.roomsResult.spaceId; + + // Refresh room data from store + this.roomsResult = RoomListStoreV3.instance.getSortedRoomsInActiveSpace(filterKeys); + this.updateRoomsMap(this.roomsResult); + + const newSpaceId = this.roomsResult.spaceId; + + // Clear view models since room list structure changed + this.clearViewModels(); + + // Detect space change + if (oldSpaceId !== newSpaceId) { + // Space changed - get the last selected room for the new space to prevent flicker + const lastSelectedRoom = SpaceStore.instance.getLastSelectedRoomIdForSpace(newSpaceId); + + this.updateRoomListData(true, lastSelectedRoom); + return; + } + + // Normal room list update (not a space change) + this.updateRoomListData(); + }; + + private onListsLoaded = (): void => { + // Room lists have finished loading + this.snapshot.merge({ + isLoadingRooms: false, + }); + }; + + /** + * Calculate the active room index based on the currently viewed room. + * Returns undefined if no room is selected or if the selected room is not in the current list. + * + * @param roomId - The room ID to find the index for (can be null/undefined) + */ + private getActiveRoomIndex(roomId: string | null | undefined): number | undefined { + if (!roomId) { + return undefined; + } + + const index = this.roomsResult.rooms.findIndex((room) => room.roomId === roomId); + return index >= 0 ? index : undefined; + } + + /** + * Apply sticky room logic to keep the active room at the same index position. + * When the room list updates, this prevents the selected room from jumping around in the UI. + * + * @param isRoomChange - Whether this update is due to a room change (not a list update) + * @param roomId - The room ID to apply sticky logic for (can be null/undefined) + * @returns The modified rooms array with sticky positioning applied + */ + private applyStickyRoom(isRoomChange: boolean, roomId: string | null | undefined): Room[] { + const rooms = this.roomsResult.rooms; + + if (!roomId) { + return rooms; + } + + const newIndex = rooms.findIndex((room) => room.roomId === roomId); + const oldIndex = this.lastActiveRoomIndex; + + // When opening another room, the index should obviously change + if (isRoomChange) { + return rooms; + } + + // If oldIndex is undefined, then there was no active room before + // Similarly, if newIndex is -1, the active room is not in the current list + if (newIndex === -1 || oldIndex === undefined) { + return rooms; + } + + // If the index hasn't changed, we have nothing to do + if (newIndex === oldIndex) { + return rooms; + } + + // If the old index falls out of the bounds of the rooms array + // (usually because rooms were removed), we can no longer place + // the active room in the same old index + if (oldIndex > rooms.length - 1) { + return rooms; + } + + // Making the active room sticky is as simple as removing it from + // its new index and placing it in the old index + const newRooms = [...rooms]; + const [stickyRoom] = newRooms.splice(newIndex, 1); + newRooms.splice(oldIndex, 0, stickyRoom); + + return newRooms; + } + + private async updateRoomListData( + isRoomChange: boolean = false, + roomIdOverride: string | null = null, + ): Promise { + // Determine the room ID to use for calculations + // Use override if provided (e.g., during space changes), otherwise fall back to RoomViewStore + const roomId = roomIdOverride ?? SdkContextClass.instance.roomViewStore.getRoomId(); + + // Apply sticky room logic to keep selected room at same position + const stickyRooms = this.applyStickyRoom(isRoomChange, roomId); + + // Update roomsResult with sticky rooms + this.roomsResult = { + ...this.roomsResult, + rooms: stickyRooms, + }; + + // Rebuild roomsMap with the reordered rooms + this.updateRoomsMap(this.roomsResult); + + // Calculate the active room index after applying sticky logic + const activeRoomIndex = this.getActiveRoomIndex(roomId); + + // Track the current active room index for future sticky calculations + this.lastActiveRoomIndex = activeRoomIndex; + + // Build the complete state atomically to ensure consistency + // roomIds and roomListState must always be in sync + const roomIds = this.roomIds; + const roomListState: RoomListViewState = { + activeRoomIndex, + spaceId: this.roomsResult.spaceId, + filterKeys: this.roomsResult.filterKeys?.map((k) => String(k)), + }; + + const filterIds = [...filterKeyToIdMap.values()]; + const activeFilterId = this.activeFilter !== undefined ? filterKeyToIdMap.get(this.activeFilter) : undefined; + const isRoomListEmpty = roomIds.length === 0; + const isLoadingRooms = RoomListStoreV3.instance.isLoadingRooms; + + // Single atomic snapshot update + this.snapshot.merge({ + isLoadingRooms, + isRoomListEmpty, + filterIds, + activeFilterId, + roomListState, + roomIds, + }); + } + + public createChatRoom = (): void => { + dispatcher.fire(Action.CreateChat); + }; + + public createRoom = (): void => { + const activeSpace = SpaceStore.instance.activeSpaceRoom; + if (activeSpace) { + dispatcher.dispatch({ + action: Action.CreateRoom, + parent_space: activeSpace, + }); + } else { + dispatcher.dispatch({ + action: Action.CreateRoom, + }); + } + }; +} diff --git a/src/components/viewmodels/roomlist/useFilteredRooms.tsx b/src/components/viewmodels/roomlist/useFilteredRooms.tsx deleted file mode 100644 index a0e36dc668..0000000000 --- a/src/components/viewmodels/roomlist/useFilteredRooms.tsx +++ /dev/null @@ -1,131 +0,0 @@ -/* -Copyright 2025 New Vector Ltd. - -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 { useCallback, useEffect, useMemo, useState } from "react"; - -import { FilterKey } from "../../../stores/room-list-v3/skip-list/filters"; -import { _t, _td } from "../../../languageHandler"; -import RoomListStoreV3, { - LISTS_LOADED_EVENT, - LISTS_UPDATE_EVENT, - type RoomsResult, -} from "../../../stores/room-list-v3/RoomListStoreV3"; -import { useEventEmitter } from "../../../hooks/useEventEmitter"; - -/** - * Provides information about a primary filter. - * A primary filter is a commonly used filter that is given - * more precedence in the UI. For eg, primary filters may be - * rendered as pills above the room list. - */ -export interface PrimaryFilter { - // A function to toggle this filter on and off. - toggle: () => void; - // Whether this filter is currently applied - active: boolean; - // Text that can be used in the UI to represent this filter. - name: string; - // The key of the filter - key: FilterKey; -} - -interface FilteredRooms { - primaryFilters: PrimaryFilter[]; - isLoadingRooms: boolean; - roomsResult: RoomsResult; - /** - * The currently active primary filter. - * If no primary filter is active, this will be undefined. - */ - activePrimaryFilter?: PrimaryFilter; -} - -const filterKeyToNameMap: Map = new Map([ - [FilterKey.UnreadFilter, _td("room_list|filters|unread")], - [FilterKey.PeopleFilter, _td("room_list|filters|people")], - [FilterKey.RoomsFilter, _td("room_list|filters|rooms")], - [FilterKey.FavouriteFilter, _td("room_list|filters|favourite")], - [FilterKey.MentionsFilter, _td("room_list|filters|mentions")], - [FilterKey.InvitesFilter, _td("room_list|filters|invites")], - [FilterKey.LowPriorityFilter, _td("room_list|filters|low_priority")], -]); - -/** - * Track available filters and provide a filtered list of rooms. - */ -export function useFilteredRooms(): FilteredRooms { - /** - * Primary filter refers to the pill based filters - * rendered above the room list. - */ - const [primaryFilter, setPrimaryFilter] = useState(); - - const [roomsResult, setRoomsResult] = useState(() => RoomListStoreV3.instance.getSortedRoomsInActiveSpace()); - const [isLoadingRooms, setIsLoadingRooms] = useState(() => RoomListStoreV3.instance.isLoadingRooms); - - const updateRoomsFromStore = useCallback((filters: FilterKey[] = []): void => { - const newRooms = RoomListStoreV3.instance.getSortedRoomsInActiveSpace(filters); - setRoomsResult(newRooms); - }, []); - - const filterUndefined = (array: (FilterKey | undefined)[]): FilterKey[] => - array.filter((f) => f !== undefined) as FilterKey[]; - - const getAppliedFilters = useCallback((): FilterKey[] => { - return filterUndefined([primaryFilter]); - }, [primaryFilter]); - - useEffect(() => { - // Update the rooms state when the primary filter changes - const filters = getAppliedFilters(); - updateRoomsFromStore(filters); - }, [getAppliedFilters, updateRoomsFromStore]); - - useEventEmitter(RoomListStoreV3.instance, LISTS_UPDATE_EVENT, () => { - const filters = getAppliedFilters(); - updateRoomsFromStore(filters); - }); - - useEventEmitter(RoomListStoreV3.instance, LISTS_LOADED_EVENT, () => { - setIsLoadingRooms(false); - }); - - /** - * This tells the view which primary filters are available, how to toggle them - * and whether a given primary filter is active. @see {@link PrimaryFilter} - */ - const primaryFilters = useMemo(() => { - const createPrimaryFilter = (key: FilterKey, name: string): PrimaryFilter => { - return { - toggle: () => { - setPrimaryFilter((currentFilter) => { - const filter = currentFilter === key ? undefined : key; - updateRoomsFromStore(filterUndefined([filter])); - return filter; - }); - }, - active: primaryFilter === key, - name, - key, - }; - }; - const filters: PrimaryFilter[] = []; - for (const [key, name] of filterKeyToNameMap.entries()) { - filters.push(createPrimaryFilter(key, _t(name))); - } - return filters; - }, [primaryFilter, updateRoomsFromStore]); - - const activePrimaryFilter = useMemo(() => primaryFilters.find((filter) => filter.active), [primaryFilters]); - - return { - isLoadingRooms, - primaryFilters, - activePrimaryFilter, - roomsResult, - }; -} diff --git a/src/components/viewmodels/roomlist/useMessagePreviewToggle.tsx b/src/components/viewmodels/roomlist/useMessagePreviewToggle.tsx deleted file mode 100644 index efb58b3e04..0000000000 --- a/src/components/viewmodels/roomlist/useMessagePreviewToggle.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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 { useCallback } from "react"; - -import SettingsStore from "../../../settings/SettingsStore"; -import { SettingLevel } from "../../../settings/SettingLevel"; -import { useSettingValue } from "../../../hooks/useSettings"; - -interface MessagePreviewToggleState { - shouldShowMessagePreview: boolean; - toggleMessagePreview: () => void; -} - -/** - * This hook: - * - Provides a state that tracks whether message previews are turned on or off. - * - Provides a function to toggle message previews. - */ -export function useMessagePreviewToggle(): MessagePreviewToggleState { - const shouldShowMessagePreview = useSettingValue("RoomList.showMessagePreview"); - - const toggleMessagePreview = useCallback((): void => { - const toggled = !shouldShowMessagePreview; - SettingsStore.setValue("RoomList.showMessagePreview", null, SettingLevel.DEVICE, toggled); - }, [shouldShowMessagePreview]); - - return { toggleMessagePreview, shouldShowMessagePreview }; -} diff --git a/src/components/viewmodels/roomlist/useRoomListNavigation.ts b/src/components/viewmodels/roomlist/useRoomListNavigation.ts deleted file mode 100644 index 5ef979e79c..0000000000 --- a/src/components/viewmodels/roomlist/useRoomListNavigation.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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 { type Room } from "matrix-js-sdk/src/matrix"; - -import dispatcher from "../../../dispatcher/dispatcher"; -import { useDispatcher } from "../../../hooks/useDispatcher"; -import { Action } from "../../../dispatcher/actions"; -import { type ViewRoomDeltaPayload } from "../../../dispatcher/payloads/ViewRoomDeltaPayload"; -import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; -import { SdkContextClass } from "../../../contexts/SDKContext"; -import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; - -/** - * Hook to navigate the room list using keyboard shortcuts. - * It listens to the ViewRoomDelta action and updates the room list accordingly. - * @param rooms - */ -export function useRoomListNavigation(rooms: Room[]): void { - useDispatcher(dispatcher, (payload) => { - if (payload.action !== Action.ViewRoomDelta) return; - const roomId = SdkContextClass.instance.roomViewStore.getRoomId(); - if (!roomId) return; - - const { delta, unread } = payload as ViewRoomDeltaPayload; - const filteredRooms = unread - ? // Filter the rooms to only include unread ones and the active room - rooms.filter((room) => { - const state = RoomNotificationStateStore.instance.getRoomState(room); - return room.roomId === roomId || state.isUnread; - }) - : rooms; - - const currentIndex = filteredRooms.findIndex((room) => room.roomId === roomId); - if (currentIndex === -1) return; - - // Get the next/previous new room according to the delta - // Use slice to loop on the list - // If delta is -1 at the start of the list, it will go to the end - // If delta is 1 at the end of the list, it will go to the start - const [newRoom] = filteredRooms.slice((currentIndex + delta) % filteredRooms.length); - if (!newRoom) return; - - dispatcher.dispatch({ - action: Action.ViewRoom, - room_id: newRoom.roomId, - show_room_tile: true, // to make sure the room gets scrolled into view - metricsTrigger: "WebKeyboardShortcut", - metricsViaKeyboard: true, - }); - }); -} diff --git a/src/components/viewmodels/roomlist/useStickyRoomList.tsx b/src/components/viewmodels/roomlist/useStickyRoomList.tsx deleted file mode 100644 index 355e09a292..0000000000 --- a/src/components/viewmodels/roomlist/useStickyRoomList.tsx +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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 { useCallback, useEffect, useRef, useState } from "react"; - -import { SdkContextClass } from "../../../contexts/SDKContext"; -import { useDispatcher } from "../../../hooks/useDispatcher"; -import dispatcher from "../../../dispatcher/dispatcher"; -import { Action } from "../../../dispatcher/actions"; -import type { Room } from "matrix-js-sdk/src/matrix"; -import SpaceStore from "../../../stores/spaces/SpaceStore"; -import { type RoomsResult } from "../../../stores/room-list-v3/RoomListStoreV3"; - -function getIndexByRoomId(rooms: Room[], roomId: string): number | undefined { - const index = rooms.findIndex((room) => room.roomId === roomId); - return index === -1 ? undefined : index; -} - -function getRoomsWithStickyRoom( - rooms: Room[], - oldIndex: number | undefined, - newIndex: number | undefined, - isRoomChange: boolean, -): { newRooms: Room[]; newIndex: number | undefined } { - const updated = { newIndex, newRooms: rooms }; - if (isRoomChange) { - /* - * When opening another room, the index should obviously change. - */ - return updated; - } - if (newIndex === undefined || oldIndex === undefined) { - /* - * If oldIndex is undefined, then there was no active room before. - * So nothing to do in regards to sticky room. - * Similarly, if newIndex is undefined, there's no active room anymore. - */ - return updated; - } - if (newIndex === oldIndex) { - /* - * If the index hasn't changed, we have nothing to do. - */ - return updated; - } - if (oldIndex > rooms.length - 1) { - /* - * If the old index falls out of the bounds of the rooms array - * (usually because rooms were removed), we can no longer place - * the active room in the same old index. - */ - return updated; - } - - /* - * Making the active room sticky is as simple as removing it from - * its new index and placing it in the old index. - */ - const newRooms = [...rooms]; - const [newRoom] = newRooms.splice(newIndex, 1); - newRooms.splice(oldIndex, 0, newRoom); - - return { newIndex: oldIndex, newRooms }; -} - -export interface StickyRoomListResult { - /** - * The rooms result with the active sticky room applied - */ - roomsResult: RoomsResult; - /** - * Index of the active room in the room list. - */ - activeIndex: number | undefined; -} - -/** - * - Provides a list of rooms such that the active room is sticky i.e the active room is kept - * in the same index even when the order of rooms in the list changes. - * - Provides the index of the active room. - * @param rooms list of rooms - * @see {@link StickyRoomListResult} details what this hook returns.. - */ -export function useStickyRoomList(roomsResult: RoomsResult): StickyRoomListResult { - const [listState, setListState] = useState({ - activeIndex: getIndexByRoomId(roomsResult.rooms, SdkContextClass.instance.roomViewStore.getRoomId()!), - roomsResult: roomsResult, - }); - - const currentSpaceRef = useRef(SpaceStore.instance.activeSpace); - - const updateRoomsAndIndex = useCallback( - (newRoomId: string | null, isRoomChange: boolean = false) => { - setListState((current) => { - const activeRoomId = newRoomId ?? SdkContextClass.instance.roomViewStore.getRoomId(); - const newActiveIndex = getIndexByRoomId(roomsResult.rooms, activeRoomId!); - const oldIndex = current.activeIndex; - const { newIndex, newRooms } = getRoomsWithStickyRoom( - roomsResult.rooms, - oldIndex, - newActiveIndex, - isRoomChange, - ); - return { activeIndex: newIndex, roomsResult: { ...roomsResult, rooms: newRooms } }; - }); - }, - [roomsResult], - ); - - // Re-calculate the index when the active room has changed. - useDispatcher(dispatcher, (payload) => { - if (payload.action === Action.ActiveRoomChanged) updateRoomsAndIndex(payload.newRoomId, true); - }); - - // Re-calculate the index when the list of rooms has changed. - useEffect(() => { - let newRoomId: string | null = null; - let isRoomChange = false; - if (currentSpaceRef.current !== roomsResult.spaceId) { - /* - If the space has changed, we check if we can immediately set the active - index to the last opened room in that space. Otherwise, we might see a - flicker because of the delay between the space change event and - active room change dispatch. - */ - newRoomId = SpaceStore.instance.getLastSelectedRoomIdForSpace(roomsResult.spaceId); - isRoomChange = true; - currentSpaceRef.current = roomsResult.spaceId; - } - updateRoomsAndIndex(newRoomId, isRoomChange); - }, [roomsResult, updateRoomsAndIndex]); - - return listState; -} diff --git a/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx b/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx deleted file mode 100644 index 8c1d04b8c5..0000000000 --- a/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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, { type JSX, type PropsWithChildren } from "react"; -import { Button } from "@vector-im/compound-web"; -import ChatIcon from "@vector-im/compound-design-tokens/assets/web/icons/chat"; -import RoomIcon from "@vector-im/compound-design-tokens/assets/web/icons/room"; -import { Flex } from "@element-hq/web-shared-components"; - -import type { RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel"; -import { _t } from "../../../../languageHandler"; -import { FilterKey } from "../../../../stores/room-list-v3/skip-list/filters"; -import { type PrimaryFilter } from "../../../viewmodels/roomlist/useFilteredRooms"; - -interface EmptyRoomListProps { - /** - * The view model for the room list - */ - vm: RoomListViewState; -} - -/** - * The empty state for the room list - */ -export function EmptyRoomList({ vm }: EmptyRoomListProps): JSX.Element | undefined { - // If there is no active primary filter, show the default empty state - if (!vm.activePrimaryFilter) return ; - - switch (vm.activePrimaryFilter.key) { - case FilterKey.FavouriteFilter: - return ( - - ); - case FilterKey.PeopleFilter: - return ( - - ); - case FilterKey.RoomsFilter: - return ( - - ); - case FilterKey.UnreadFilter: - return ( - - ); - case FilterKey.InvitesFilter: - return ( - - ); - case FilterKey.MentionsFilter: - return ( - - ); - case FilterKey.LowPriorityFilter: - return ( - - ); - default: - return undefined; - } -} - -interface GenericPlaceholderProps { - /** - * The title of the placeholder - */ - title: string; - /** - * The description of the placeholder - */ - description?: string; -} - -/** - * A generic placeholder for the room list - */ -function GenericPlaceholder({ title, description, children }: PropsWithChildren): JSX.Element { - return ( - - {title} - {description && {description}} - {children} - - ); -} - -interface DefaultPlaceholderProps { - /** - * The view model for the room list - */ - vm: RoomListViewState; -} - -/** - * The default empty state for the room list when no primary filter is active - * The user can create chat or room (if they have the permission) - */ -function DefaultPlaceholder({ vm }: DefaultPlaceholderProps): JSX.Element { - return ( - - - - {vm.canCreateRoom && ( - - )} - - - ); -} - -interface ActionPlaceholderProps { - filter: PrimaryFilter; - title: string; - action: string; -} - -/** - * A placeholder for the room list when a filter is active - * The user can take action to toggle the filter - */ -function ActionPlaceholder({ filter, title, action }: ActionPlaceholderProps): JSX.Element { - return ( - - - - ); -} diff --git a/src/components/views/rooms/RoomListPanel/RoomList.tsx b/src/components/views/rooms/RoomListPanel/RoomList.tsx deleted file mode 100644 index c946695b39..0000000000 --- a/src/components/views/rooms/RoomListPanel/RoomList.tsx +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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, { useCallback, useRef, type JSX, useMemo } from "react"; -import { type Room } from "matrix-js-sdk/src/matrix"; -import { isEqual } from "lodash"; -import { - type VirtualizedListContext, - VirtualizedList, - type ScrollIntoViewOnChange, -} from "@element-hq/web-shared-components"; - -import { type RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel"; -import { _t } from "../../../../languageHandler"; -import { RoomListItemView } from "./RoomListItemView"; -import { type FilterKey } from "../../../../stores/room-list-v3/skip-list/filters"; -import { getKeyBindingsManager } from "../../../../KeyBindingsManager"; -import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts"; -import { Landmark, LandmarkNavigation } from "../../../../accessibility/LandmarkNavigation"; - -interface RoomListProps { - /** - * The view model state for the room list. - */ - vm: RoomListViewState; -} - -type Context = { - spaceId: string; - filterKeys: FilterKey[] | undefined; -}; - -/** - * Height of a single room list item - */ -const ROOM_LIST_ITEM_HEIGHT = 48; -/** - * Amount to extend the top and bottom of the viewport by. - * From manual testing and user feedback 25 items is reported to be enough to avoid blank space when using the mouse wheel, - * and the trackpad scrolling at a slow to moderate speed where you can still see/read the content. - * Using the trackpad to sling through a large percentage of the list quickly will still show blank space. - * We would likely need to simplify the item content to improve this case. - */ -const EXTENDED_VIEWPORT_HEIGHT = 25 * ROOM_LIST_ITEM_HEIGHT; -/** - * A virtualized list of rooms. - */ -export function RoomList({ vm: { roomsResult, activeIndex } }: RoomListProps): JSX.Element { - const lastSpaceId = useRef(undefined); - const lastFilterKeys = useRef(undefined); - const roomCount = roomsResult.rooms.length; - const getItemComponent = useCallback( - ( - index: number, - item: Room, - context: VirtualizedListContext, - onFocus: (item: Room, e: React.FocusEvent) => void, - ): JSX.Element => { - const itemKey = item.roomId; - const isRovingItem = itemKey === context.tabIndexKey; - const isFocused = isRovingItem && context.focused; - const isSelected = activeIndex === index; - return ( - - ); - }, - [activeIndex, roomCount], - ); - - const getItemKey = useCallback((item: Room): string => { - return item.roomId; - }, []); - - const scrollIntoViewOnChange = useCallback>( - (params) => { - const { spaceId, filterKeys } = params.context.context; - const shouldScrollIndexIntoView = - lastSpaceId.current !== spaceId || !isEqual(lastFilterKeys.current, filterKeys); - lastFilterKeys.current = filterKeys; - lastSpaceId.current = spaceId; - - if (shouldScrollIndexIntoView) { - return { - align: `start`, - index: activeIndex || 0, - behavior: "auto", - }; - } - return false; - }, - [activeIndex], - ); - - const keyDownCallback = useCallback((ev: React.KeyboardEvent) => { - const navAction = getKeyBindingsManager().getNavigationAction(ev); - if (navAction === KeyBindingAction.NextLandmark || navAction === KeyBindingAction.PreviousLandmark) { - LandmarkNavigation.findAndFocusNextLandmark( - Landmark.ROOM_LIST, - navAction === KeyBindingAction.PreviousLandmark, - ); - ev.stopPropagation(); - ev.preventDefault(); - return; - } - }, []); - const context = useMemo( - () => ({ spaceId: roomsResult.spaceId, filterKeys: roomsResult.filterKeys }), - [roomsResult.spaceId, roomsResult.filterKeys], - ); - - return ( - true} - onKeyDown={keyDownCallback} - increaseViewportBy={{ - bottom: EXTENDED_VIEWPORT_HEIGHT, - top: EXTENDED_VIEWPORT_HEIGHT, - }} - /> - ); -} diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemContextMenuView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemContextMenuView.tsx deleted file mode 100644 index f3ba4167e7..0000000000 --- a/src/components/views/rooms/RoomListPanel/RoomListItemContextMenuView.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -import { type Room } from "matrix-js-sdk/src/matrix"; -import { type JSX, type PropsWithChildren } from "react"; -import { ContextMenu } from "@vector-im/compound-web"; -import React from "react"; - -import { _t } from "../../../../languageHandler"; -import { MoreOptionContent } from "./RoomListItemMenuView"; -import { useRoomListItemMenuViewModel } from "../../../viewmodels/roomlist/RoomListItemMenuViewModel"; - -interface RoomListItemContextMenuViewProps { - /** - * The room to display the menu for. - */ - room: Room; -} - -/** - * A view for the room list item context menu. - */ -export function RoomListItemContextMenuView({ - room, - children, -}: PropsWithChildren): JSX.Element { - const vm = useRoomListItemMenuViewModel(room); - - return ( - - - - ); -} diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx deleted file mode 100644 index 7c5dd5ba1a..0000000000 --- a/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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, { type JSX, useState } from "react"; -import { IconButton, Menu, MenuItem, Separator, ToggleMenuItem } from "@vector-im/compound-web"; -import { - MarkAsReadIcon, - MarkAsUnreadIcon, - FavouriteIcon, - ArrowDownIcon, - UserAddIcon, - LinkIcon, - LeaveIcon, - OverflowHorizontalIcon, - NotificationsSolidIcon, - NotificationsOffSolidIcon, - CheckIcon, -} from "@vector-im/compound-design-tokens/assets/web/icons"; -import { type Room } from "matrix-js-sdk/src/matrix"; -import { Flex } from "@element-hq/web-shared-components"; -import classNames from "classnames"; - -import { _t } from "../../../../languageHandler"; -import { - type RoomListItemMenuViewState, - useRoomListItemMenuViewModel, -} from "../../../viewmodels/roomlist/RoomListItemMenuViewModel"; -import { RoomNotifState } from "../../../../RoomNotifs"; - -interface RoomListItemMenuViewProps { - /** - * Additional class name for the root element. - */ - className?: string; - - /** - * The room to display the menu for. - */ - room: Room; -} - -/** - * A view for the room list item menu. - */ -export function RoomListItemMenuView({ room, className }: RoomListItemMenuViewProps): JSX.Element { - const vm = useRoomListItemMenuViewModel(room); - - return ( - - {vm.showMoreOptionsMenu && } - {vm.showNotificationMenu && } - - ); -} - -interface MoreOptionsMenuProps { - /** - * The view model state for the menu. - */ - vm: RoomListItemMenuViewState; -} - -/** - * The more options menu for the room list item. - */ -function MoreOptionsMenu({ vm }: MoreOptionsMenuProps): JSX.Element { - const [open, setOpen] = useState(false); - - return ( - - - - } - > - - - ); -} - -interface MoreOptionContentProps { - /** - * The view model state for the menu. - */ - vm: RoomListItemMenuViewState; -} - -export function MoreOptionContent({ vm }: MoreOptionContentProps): JSX.Element { - return ( -
e.stopPropagation()} - > - {vm.canMarkAsRead && ( - evt.stopPropagation()} - hideChevron={true} - /> - )} - {vm.canMarkAsUnread && ( - evt.stopPropagation()} - hideChevron={true} - /> - )} - evt.stopPropagation()} - /> - evt.stopPropagation()} - /> - {vm.canInvite && ( - evt.stopPropagation()} - hideChevron={true} - /> - )} - {vm.canCopyRoomLink && ( - evt.stopPropagation()} - hideChevron={true} - /> - )} - - evt.stopPropagation()} - hideChevron={true} - /> -
- ); -} - -interface NotificationMenuProps { - /** - * The view model state for the menu. - */ - vm: RoomListItemMenuViewState; -} - -function NotificationMenu({ vm }: NotificationMenuProps): JSX.Element { - const [open, setOpen] = useState(false); - const checkComponent = ; - - return ( - - {vm.isNotificationMute ? : } - - } - > -
e.stopPropagation()} - > - vm.setRoomNotifState(RoomNotifState.AllMessages)} - onClick={(evt) => evt.stopPropagation()} - > - {vm.isNotificationAllMessage && checkComponent} - - vm.setRoomNotifState(RoomNotifState.AllMessagesLoud)} - onClick={(evt) => evt.stopPropagation()} - > - {vm.isNotificationAllMessageLoud && checkComponent} - - vm.setRoomNotifState(RoomNotifState.MentionsOnly)} - onClick={(evt) => evt.stopPropagation()} - > - {vm.isNotificationMentionOnly && checkComponent} - - vm.setRoomNotifState(RoomNotifState.Mute)} - onClick={(evt) => evt.stopPropagation()} - > - {vm.isNotificationMute && checkComponent} - -
-
- ); -} diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx deleted file mode 100644 index d87da9c034..0000000000 --- a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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, { type JSX, memo, useEffect, useRef } from "react"; -import { type Room } from "matrix-js-sdk/src/matrix"; -import classNames from "classnames"; -import { Flex } from "@element-hq/web-shared-components"; - -import { useRoomListItemViewModel } from "../../../viewmodels/roomlist/RoomListItemViewModel"; -import { RoomListItemMenuView } from "./RoomListItemMenuView"; -import { NotificationDecoration } from "../NotificationDecoration"; -import { RoomAvatarView } from "../../avatars/RoomAvatarView"; -import { RoomListItemContextMenuView } from "./RoomListItemContextMenuView"; - -interface RoomListItemViewProps extends Omit, "onFocus"> { - /** - * The room to display - */ - room: Room; - /** - * Whether the room is selected - */ - isSelected: boolean; - /** - * Whether the room is focused - */ - isFocused: boolean; - /** - * A callback that indicates the item has received focus - */ - onFocus: (room: Room, e: React.FocusEvent) => void; - /** - * The index of the room in the list - */ - roomIndex: number; - /** - * The total number of rooms in the list - */ - roomCount: number; -} - -/** - * An item in the room list - */ -export const RoomListItemView = memo(function RoomListItemView({ - room, - isSelected, - isFocused, - onFocus, - roomIndex: index, - roomCount: count, - ...props -}: RoomListItemViewProps): JSX.Element { - const ref = useRef(null); - const vm = useRoomListItemViewModel(room); - - useEffect(() => { - if (isFocused) { - ref.current?.focus({ preventScroll: true, focusVisible: true }); - } - }, [isFocused]); - - const content = ( - vm.openRoom()} - onFocus={(e: React.FocusEvent) => onFocus(room, e)} - tabIndex={isFocused ? 0 : -1} - {...props} - > - - - {/* We truncate the room name when too long. Title here is to show the full name on hover */} -
-
- {vm.name} -
- {vm.messagePreview && ( -
- {vm.messagePreview} -
- )} -
- {vm.showHoverMenu && } - - {/* aria-hidden because we summarise the unread count/notification status in a11yLabel variable */} - {vm.showNotificationDecoration && ( - - )} -
-
- ); - - if (!vm.showContextMenu) return content; - return {content}; -}); diff --git a/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx b/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx deleted file mode 100644 index 44f19a86da..0000000000 --- a/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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, { type JSX, useEffect, useId, useRef, useState, type RefObject } from "react"; -import { ChatFilter, IconButton } from "@vector-im/compound-web"; -import ChevronDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-down"; -import { Flex } from "@element-hq/web-shared-components"; - -import type { RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel"; -import { _t } from "../../../../languageHandler"; - -interface RoomListPrimaryFiltersProps { - /** - * The view model for the room list - */ - vm: RoomListViewState; -} - -/** - * The primary filters for the room list - */ -export function RoomListPrimaryFilters({ vm }: RoomListPrimaryFiltersProps): JSX.Element { - const id = useId(); - const [isExpanded, setIsExpanded] = useState(false); - - const { ref, isWrapping: displayChevron, wrappingIndex } = useCollapseFilters(isExpanded); - const filters = useVisibleFilters(vm.primaryFilters, wrappingIndex); - - return ( - - {displayChevron && ( - setIsExpanded((_expanded) => !_expanded)} - > - - - )} - - {filters.map((filter, i) => ( - filter.toggle()}> - {filter.name} - - ))} - - - ); -} - -/** - * A hook to manage the wrapping of filters in the room list. - * It observes the filter list and hides filters that are wrapping when the list is not expanded. - * @param isExpanded - * @returns an object containing: - * - `ref`: a ref to put on the filter list element - * - `isWrapping`: a boolean indicating if the filters are wrapping - * - `wrappingIndex`: the index of the first filter that is wrapping - */ -function useCollapseFilters( - isExpanded: boolean, -): { ref: RefObject; isWrapping: boolean; wrappingIndex: number } { - const ref = useRef(null); - const [isWrapping, setIsWrapping] = useState(false); - const [wrappingIndex, setWrappingIndex] = useState(-1); - - useEffect(() => { - if (!ref.current) return; - - const hideFilters = (list: Element): void => { - let isWrapping = false; - Array.from(list.children).forEach((node, i): void => { - const child = node as HTMLElement; - const wrappingClass = "mx_RoomListPrimaryFilters_wrapping"; - child.setAttribute("aria-hidden", "false"); - child.classList.remove(wrappingClass); - - // If the filter list is expanded, all filters are visible - if (isExpanded) return; - - // If the previous element is on the left element of the current one, it means that the filter is wrapping - const previousSibling = child.previousElementSibling as HTMLElement | null; - if (previousSibling && child.offsetLeft <= previousSibling.offsetLeft) { - if (!isWrapping) setWrappingIndex(i); - isWrapping = true; - } - - // If the filter is wrapping, we hide it - child.classList.toggle(wrappingClass, isWrapping); - child.setAttribute("aria-hidden", isWrapping.toString()); - }); - - if (!isWrapping) setWrappingIndex(-1); - setIsWrapping(isExpanded || isWrapping); - }; - - hideFilters(ref.current); - const observer = new ResizeObserver((entries) => entries.forEach((entry) => hideFilters(entry.target))); - - observer.observe(ref.current); - return () => { - observer.disconnect(); - }; - }, [isExpanded]); - - return { ref, isWrapping, wrappingIndex }; -} - -/** - * A hook to sort the filters by active state. - * The list is sorted if the current filter index is greater than or equal to the wrapping index. - * If the wrapping index is -1, the filters are not sorted. - * - * @param filters - the list of filters to sort. - * @param wrappingIndex - the index of the first filter that is wrapping. - */ -export function useVisibleFilters( - filters: RoomListViewState["primaryFilters"], - wrappingIndex: number, -): RoomListViewState["primaryFilters"] { - // By default, the filters are not sorted - const [sortedFilters, setSortedFilters] = useState(filters); - - useEffect(() => { - const isActiveFilterWrapping = filters.findIndex((f) => f.active) >= wrappingIndex; - // If the active filter is not wrapping, we don't need to sort the filters - if (!isActiveFilterWrapping || wrappingIndex === -1) { - setSortedFilters(filters); - return; - } - - // Sort the filters with the current filter at first position - setSortedFilters( - filters.slice().sort((filterA, filterB) => { - // If the filter is active, it should be at the top of the list - if (filterA.active && !filterB.active) return -1; - if (!filterA.active && filterB.active) return 1; - // If both filters are active or not, keep their original order - return 0; - }), - ); - }, [filters, wrappingIndex]); - - return sortedFilters; -} diff --git a/src/components/views/rooms/RoomListPanel/RoomListView.tsx b/src/components/views/rooms/RoomListPanel/RoomListView.tsx index b29affc0be..b6c6c20fb9 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListView.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListView.tsx @@ -5,33 +5,42 @@ * Please see LICENSE files in the repository root for full details. */ -import React, { type JSX } from "react"; +import React, { useCallback, type JSX, type ReactNode } from "react"; +import { RoomListView as SharedRoomListView, useCreateAutoDisposedViewModel } from "@element-hq/web-shared-components"; -import { useRoomListViewModel } from "../../../viewmodels/roomlist/RoomListViewModel"; -import { RoomList } from "./RoomList"; -import { EmptyRoomList } from "./EmptyRoomList"; -import { RoomListPrimaryFilters } from "./RoomListPrimaryFilters"; +import { RoomListViewViewModel } from "../../../viewmodels/roomlist/RoomListViewViewModel"; +import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext"; +import { RoomAvatarView } from "../../avatars/RoomAvatarView"; +import { getKeyBindingsManager } from "../../../../KeyBindingsManager"; +import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts"; +import { Landmark, LandmarkNavigation } from "../../../../accessibility/LandmarkNavigation"; /** - * Host the room list and the (future) room filters + * RoomListView component using shared components with proper MVVM pattern. */ export function RoomListView(): JSX.Element { - const vm = useRoomListViewModel(); - const isRoomListEmpty = vm.roomsResult.rooms.length === 0; - let listBody; - if (vm.isLoadingRooms) { - listBody =
; - } else if (isRoomListEmpty) { - listBody = ; - } else { - listBody = ; - } - return ( - <> -
- -
- {listBody} - - ); + const matrixClient = useMatrixClientContext(); + + // Create and auto-dispose ViewModel instance + const vm = useCreateAutoDisposedViewModel(() => new RoomListViewViewModel({ client: matrixClient })); + + // Render avatar for each room - memoized to prevent re-renders + const renderAvatar = useCallback((room: any): ReactNode => { + return ; + }, []); + + // Handle keyboard navigation for landmarks + const onKeyDown = useCallback((ev: React.KeyboardEvent) => { + const navAction = getKeyBindingsManager().getNavigationAction(ev); + if (navAction === KeyBindingAction.NextLandmark || navAction === KeyBindingAction.PreviousLandmark) { + LandmarkNavigation.findAndFocusNextLandmark( + Landmark.ROOM_LIST, + navAction === KeyBindingAction.PreviousLandmark, + ); + ev.stopPropagation(); + ev.preventDefault(); + } + }, []); + + return ; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 8299d762a6..2c99427036 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -12,16 +12,6 @@ "other": "%(count)s unread messages including mentions." }, "recent_rooms": "Recent rooms", - "room_messsage_not_sent": "Open room %(roomName)s with an unsent message.", - "room_n_unread_invite": "Open room %(roomName)s invitation.", - "room_n_unread_messages": { - "one": "Open room %(roomName)s with 1 unread message.", - "other": "Open room %(roomName)s with %(count)s unread messages." - }, - "room_n_unread_messages_mentions": { - "one": "Open room %(roomName)s with 1 unread mention.", - "other": "Open room %(roomName)s with %(count)s unread messages including mentions." - }, "room_name": "Room %(name)s", "room_status_bar": "Room status bar", "seek_bar_label": "Audio seek bar", @@ -1718,7 +1708,6 @@ "class_global": "Global", "class_other": "Other", "default": "Default", - "default_settings": "Match default settings", "email_pusher_app_display_name": "Email Notifications", "enable_prompt_toast_description": "Enable desktop notifications", "enable_prompt_toast_title": "Notifications", @@ -1737,8 +1726,7 @@ "mentions_and_keywords_description": "Get notified only with mentions and keywords as set up in your
settings", "mentions_keywords": "Mentions and keywords", "message_didnt_send": "Message didn't send. Click for info.", - "mute_description": "You won't get any notifications", - "mute_room": "Mute room" + "mute_description": "You won't get any notifications" }, "notifier": { "m.key.verification.request": "%(name)s is requesting verification" @@ -2153,37 +2141,9 @@ "add_space_label": "Add space", "breadcrumbs_empty": "No recently visited rooms", "breadcrumbs_label": "Recently visited rooms", - "collapse_filters": "Collapse filter list", - "empty": { - "no_chats": "No chats yet", - "no_chats_description": "Get started by messaging someone or by creating a room", - "no_chats_description_no_room_rights": "Get started by messaging someone", - "no_favourites": "You don't have favourite chats yet", - "no_favourites_description": "You can add a chat to your favourites in the chat settings", - "no_invites": "You don't have any unread invites", - "no_lowpriority": "You don't have any low priority rooms", - "no_mentions": "You don't have any unread mentions", - "no_people": "You don’t have direct chats with anyone yet", - "no_people_description": "You can deselect filters in order to see your other chats", - "no_rooms": "You’re not in any room yet", - "no_rooms_description": "You can deselect filters in order to see your other chats", - "no_unread": "Congrats! You don’t have any unread messages", - "show_activity": "See all activity", - "show_chats": "Show all chats" - }, - "expand_filters": "Expand filter list", "failed_add_tag": "Failed to add tag %(tagName)s to room", "failed_remove_tag": "Failed to remove tag %(tagName)s from room", "failed_set_dm_tag": "Failed to set direct message tag", - "filters": { - "favourite": "Favourites", - "invites": "Invites", - "low_priority": "Low priority", - "mentions": "Mentions", - "people": "People", - "rooms": "Rooms", - "unread": "Unreads" - }, "home_menu_label": "Home options", "join_public_room_label": "Join public room", "joining_rooms_status": { @@ -2192,23 +2152,13 @@ }, "list_title": "Room list", "more_options": { - "copy_link": "Copy room link", - "favourited": "Favourited", - "leave_room": "Leave room", - "low_priority": "Low priority", - "mark_read": "Mark as read", - "mark_unread": "Mark as unread" + "leave_room": "Leave room" }, "notification_options": "Notification options", - "primary_filters": "Room list filters", "redacting_messages_status": { "one": "Currently removing messages in %(count)s room", "other": "Currently removing messages in %(count)s rooms" }, - "room": { - "more_options": "More Options", - "open_room": "Open room %(roomName)s" - }, "show_less": "Show less", "show_n_more": { "one": "Show %(count)s more", diff --git a/test/unit-tests/components/viewmodels/roomlist/MessagePreviewViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/MessagePreviewViewModel-test.tsx deleted file mode 100644 index 6c5b121022..0000000000 --- a/test/unit-tests/components/viewmodels/roomlist/MessagePreviewViewModel-test.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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 { renderHook, waitFor } from "jest-matrix-react"; -import { type Room } from "matrix-js-sdk/src/matrix"; - -import { createTestClient, mkStubRoom } from "../../../../test-utils"; -import { type MessagePreview, MessagePreviewStore } from "../../../../../src/stores/room-list/MessagePreviewStore"; -import { useMessagePreviewViewModel } from "../../../../../src/components/viewmodels/roomlist/MessagePreviewViewModel"; - -describe("MessagePreviewViewModel", () => { - let room: Room; - - beforeEach(() => { - const matrixClient = createTestClient(); - room = mkStubRoom("roomId", "roomName", matrixClient); - }); - - it("should do an initial fetch of the message preview", async () => { - // Mock the store to return some text. - jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockImplementation(async (room) => { - return { text: "Hello world!" } as MessagePreview; - }); - - const { result: vm } = renderHook(() => useMessagePreviewViewModel(room)); - - // Eventually, vm.message should have the text from the store. - await waitFor(() => { - expect(vm.current.message).toEqual("Hello world!"); - }); - }); - - it("should fetch message preview again on update from store", async () => { - // Mock the store to return the text in variable message. - let message = "Hello World!"; - jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockImplementation(async (room) => { - return { text: message } as MessagePreview; - }); - jest.spyOn(MessagePreviewStore, "getPreviewChangedEventName").mockImplementation((room) => { - return "UPDATE"; - }); - - const { result: vm } = renderHook(() => useMessagePreviewViewModel(room)); - - // Let's assume the message changed. - message = "New message!"; - MessagePreviewStore.instance.emit("UPDATE"); - - /// vm.message should be the updated message. - await waitFor(() => { - expect(vm.current.message).toEqual(message); - }); - }); -}); diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListItemMenuViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListItemMenuViewModel-test.tsx deleted file mode 100644 index d017084db5..0000000000 --- a/test/unit-tests/components/viewmodels/roomlist/RoomListItemMenuViewModel-test.tsx +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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 { renderHook } from "jest-matrix-react"; -import { mocked } from "jest-mock"; -import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix"; - -import { mkStubRoom, stubClient, withClientContextRenderOptions } from "../../../../test-utils"; -import { useRoomListItemMenuViewModel } from "../../../../../src/components/viewmodels/roomlist/RoomListItemMenuViewModel"; -import { - hasAccessToNotificationMenu, - hasAccessToOptionsMenu, -} from "../../../../../src/components/viewmodels/roomlist/utils"; -import DMRoomMap from "../../../../../src/utils/DMRoomMap"; -import { DefaultTagID } from "../../../../../src/stores/room-list/models"; -import { useUnreadNotifications } from "../../../../../src/hooks/useUnreadNotifications"; -import { NotificationLevel } from "../../../../../src/stores/notifications/NotificationLevel"; -import { clearRoomNotification, setMarkedUnreadState } from "../../../../../src/utils/notifications"; -import { tagRoom } from "../../../../../src/utils/room/tagRoom"; -import dispatcher from "../../../../../src/dispatcher/dispatcher"; -import { useNotificationState } from "../../../../../src/hooks/useRoomNotificationState"; -import { RoomNotifState } from "../../../../../src/RoomNotifs"; - -jest.mock("../../../../../src/components/viewmodels/roomlist/utils", () => ({ - hasAccessToOptionsMenu: jest.fn().mockReturnValue(false), - hasAccessToNotificationMenu: jest.fn().mockReturnValue(false), -})); - -jest.mock("../../../../../src/hooks/useUnreadNotifications", () => ({ - useUnreadNotifications: jest.fn(), -})); - -jest.mock("../../../../../src/hooks/useRoomNotificationState", () => ({ - useNotificationState: jest.fn(), -})); - -jest.mock("../../../../../src/utils/notifications", () => ({ - clearRoomNotification: jest.fn(), - setMarkedUnreadState: jest.fn(), -})); - -jest.mock("../../../../../src/utils/room/tagRoom", () => ({ - tagRoom: jest.fn(), -})); - -describe("RoomListItemMenuViewModel", () => { - let matrixClient: MatrixClient; - let room: Room; - - beforeEach(() => { - matrixClient = stubClient(); - room = mkStubRoom("roomId", "roomName", matrixClient); - - DMRoomMap.makeShared(matrixClient); - jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(undefined); - - mocked(useUnreadNotifications).mockReturnValue({ symbol: null, count: 0, level: NotificationLevel.None }); - mocked(useNotificationState).mockReturnValue([RoomNotifState.AllMessages, jest.fn()]); - jest.spyOn(dispatcher, "dispatch"); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - function render() { - return renderHook(() => useRoomListItemMenuViewModel(room), withClientContextRenderOptions(matrixClient)); - } - - it("default", () => { - const { result } = render(); - expect(result.current.showMoreOptionsMenu).toBe(false); - expect(result.current.canInvite).toBe(false); - expect(result.current.isFavourite).toBe(false); - expect(result.current.canCopyRoomLink).toBe(true); - expect(result.current.canMarkAsRead).toBe(false); - expect(result.current.canMarkAsUnread).toBe(true); - }); - - it("should has showMoreOptionsMenu to be true", () => { - mocked(hasAccessToOptionsMenu).mockReturnValue(true); - const { result } = render(); - expect(result.current.showMoreOptionsMenu).toBe(true); - }); - - it("should has showNotificationMenu to be true", () => { - mocked(hasAccessToNotificationMenu).mockReturnValue(true); - const { result } = render(); - expect(result.current.showNotificationMenu).toBe(true); - }); - - it("should be able to invite", () => { - jest.spyOn(room, "canInvite").mockReturnValue(true); - const { result } = render(); - expect(result.current.canInvite).toBe(true); - }); - - it("should be a favourite", () => { - room.tags = { [DefaultTagID.Favourite]: { order: 0 } }; - const { result } = render(); - expect(result.current.isFavourite).toBe(true); - }); - - it("should not be able to copy the room link", () => { - jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue("userId"); - const { result } = render(); - expect(result.current.canCopyRoomLink).toBe(false); - }); - - it("should be able to mark as read", () => { - // Add a notification - mocked(useUnreadNotifications).mockReturnValue({ - symbol: null, - count: 1, - level: NotificationLevel.Notification, - }); - const { result } = render(); - expect(result.current.canMarkAsRead).toBe(true); - expect(result.current.canMarkAsUnread).toBe(false); - }); - - it("should has isNotificationAllMessage to be true", () => { - const { result } = render(); - expect(result.current.isNotificationAllMessage).toBe(true); - }); - - it("should has isNotificationAllMessageLoud to be true", () => { - mocked(useNotificationState).mockReturnValue([RoomNotifState.AllMessagesLoud, jest.fn()]); - const { result } = render(); - expect(result.current.isNotificationAllMessageLoud).toBe(true); - }); - - it("should has isNotificationMentionOnly to be true", () => { - mocked(useNotificationState).mockReturnValue([RoomNotifState.MentionsOnly, jest.fn()]); - const { result } = render(); - expect(result.current.isNotificationMentionOnly).toBe(true); - }); - - it("should has isNotificationMute to be true", () => { - mocked(useNotificationState).mockReturnValue([RoomNotifState.Mute, jest.fn()]); - const { result } = render(); - expect(result.current.isNotificationMute).toBe(true); - }); - - // Actions - - it("should mark as read", () => { - const { result } = render(); - result.current.markAsRead(new Event("click")); - expect(mocked(clearRoomNotification)).toHaveBeenCalledWith(room, matrixClient); - }); - - it("should mark as unread", () => { - const { result } = render(); - result.current.markAsUnread(new Event("click")); - expect(mocked(setMarkedUnreadState)).toHaveBeenCalledWith(room, matrixClient, true); - }); - - it("should tag a room as favourite", () => { - const { result } = render(); - result.current.toggleFavorite(new Event("click")); - expect(mocked(tagRoom)).toHaveBeenCalledWith(room, DefaultTagID.Favourite); - }); - - it("should tag a room as low priority", () => { - const { result } = render(); - result.current.toggleLowPriority(); - expect(mocked(tagRoom)).toHaveBeenCalledWith(room, DefaultTagID.LowPriority); - }); - - it("should dispatch invite action", () => { - const { result } = render(); - result.current.invite(new Event("click")); - expect(dispatcher.dispatch).toHaveBeenCalledWith({ - action: "view_invite", - roomId: room.roomId, - }); - }); - - it("should dispatch a copy room action", () => { - const { result } = render(); - result.current.copyRoomLink(new Event("click")); - expect(dispatcher.dispatch).toHaveBeenCalledWith({ - action: "copy_room", - room_id: room.roomId, - }); - }); - - it("should dispatch forget room action", () => { - // forget room is only available for archived rooms - room.tags = { [DefaultTagID.Archived]: { order: 0 } }; - - const { result } = render(); - result.current.leaveRoom(new Event("click")); - expect(dispatcher.dispatch).toHaveBeenCalledWith({ - action: "forget_room", - room_id: room.roomId, - }); - }); - - it("should dispatch leave room action", () => { - const { result } = render(); - result.current.leaveRoom(new Event("click")); - expect(dispatcher.dispatch).toHaveBeenCalledWith({ - action: "leave_room", - room_id: room.roomId, - }); - }); - - it("should call setRoomNotifState", () => { - const setRoomNotifState = jest.fn(); - mocked(useNotificationState).mockReturnValue([RoomNotifState.AllMessages, setRoomNotifState]); - const { result } = render(); - result.current.setRoomNotifState(RoomNotifState.Mute); - expect(setRoomNotifState).toHaveBeenCalledWith(RoomNotifState.Mute); - }); -}); diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx index 96bc53016e..095b987863 100644 --- a/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx +++ b/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx @@ -5,276 +5,435 @@ * Please see LICENSE files in the repository root for full details. */ -import { renderHook, waitFor } from "jest-matrix-react"; -import { type Room } from "matrix-js-sdk/src/matrix"; -import { mocked } from "jest-mock"; +import { type MatrixClient, type MatrixEvent, Room, RoomEvent, PendingEventOrdering } from "matrix-js-sdk/src/matrix"; +import { CallType } from "matrix-js-sdk/src/webrtc/call"; -import dispatcher from "../../../../../src/dispatcher/dispatcher"; -import { Action } from "../../../../../src/dispatcher/actions"; -import { useRoomListItemViewModel } from "../../../../../src/components/viewmodels/roomlist/RoomListItemViewModel"; -import { createTestClient, mkStubRoom, withClientContextRenderOptions } from "../../../../test-utils"; -import { - hasAccessToNotificationMenu, - hasAccessToOptionsMenu, -} from "../../../../../src/components/viewmodels/roomlist/utils"; +import { RoomListItemViewModel } from "../../../../../src/components/viewmodels/roomlist/RoomListItemViewModel"; +import { createTestClient, flushPromises } from "../../../../test-utils"; import { RoomNotificationState } from "../../../../../src/stores/notifications/RoomNotificationState"; import { RoomNotificationStateStore } from "../../../../../src/stores/notifications/RoomNotificationStateStore"; -import * as UseCallModule from "../../../../../src/hooks/useCall"; +import { NotificationStateEvents } from "../../../../../src/stores/notifications/NotificationState"; import { type MessagePreview, MessagePreviewStore } from "../../../../../src/stores/room-list/MessagePreviewStore"; +import { UPDATE_EVENT } from "../../../../../src/stores/AsyncStore"; +import SettingsStore from "../../../../../src/settings/SettingsStore"; import DMRoomMap from "../../../../../src/utils/DMRoomMap"; -import { useMessagePreviewToggle } from "../../../../../src/components/viewmodels/roomlist/useMessagePreviewToggle"; +import { DefaultTagID } from "../../../../../src/stores/room-list/models"; +import dispatcher from "../../../../../src/dispatcher/dispatcher"; +import { Action } from "../../../../../src/dispatcher/actions"; +import { CallStore } from "../../../../../src/stores/CallStore"; +import type { Call } from "../../../../../src/models/Call"; jest.mock("../../../../../src/components/viewmodels/roomlist/utils", () => ({ - hasAccessToOptionsMenu: jest.fn().mockReturnValue(false), - hasAccessToNotificationMenu: jest.fn().mockReturnValue(false), + hasAccessToOptionsMenu: jest.fn().mockReturnValue(true), + hasAccessToNotificationMenu: jest.fn().mockReturnValue(true), })); -jest.mock("../../../../../src/components/viewmodels/roomlist/useMessagePreviewToggle", () => ({ - useMessagePreviewToggle: jest.fn().mockReturnValue({ shouldShowMessagePreview: true }), +jest.mock("../../../../../src/stores/CallStore", () => ({ + __esModule: true, + CallStore: { + instance: { + getCall: jest.fn(), + on: jest.fn(), + off: jest.fn(), + emit: jest.fn(), + }, + }, + CallStoreEvent: { + ConnectedCalls: "connected_calls", + }, })); describe("RoomListItemViewModel", () => { + let matrixClient: MatrixClient; let room: Room; + let notificationState: RoomNotificationState; + let viewModel: RoomListItemViewModel; beforeEach(() => { - const matrixClient = createTestClient(); - room = mkStubRoom("roomId", "roomName", matrixClient); + matrixClient = createTestClient(); + room = new Room("!room:server", matrixClient, matrixClient.getSafeUserId(), { + pendingEventOrdering: PendingEventOrdering.Detached, + }); + + // Set room name + room.name = "Test Room"; + + notificationState = new RoomNotificationState(room, false); + jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockReturnValue(notificationState); const dmRoomMap = { - getUserIdForRoomId: jest.fn(), - getDMRoomsForUserId: jest.fn(), + getUserIdForRoomId: jest.fn().mockReturnValue(undefined), } as unknown as DMRoomMap; DMRoomMap.setShared(dmRoomMap); - mocked(useMessagePreviewToggle).mockReturnValue({ - shouldShowMessagePreview: false, - toggleMessagePreview: jest.fn(), + jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => { + if (setting === "RoomList.showMessagePreview") return false; + return false; }); + jest.spyOn(SettingsStore, "watchSetting").mockImplementation(() => "watcher-id"); + + jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue(null); + jest.spyOn(CallStore.instance, "getCall").mockReturnValue(null); }); afterEach(() => { + viewModel?.dispose(); jest.restoreAllMocks(); }); - it("should dispatch view room action on openRoom", async () => { - const { result: vm } = renderHook( - () => useRoomListItemViewModel(room), - withClientContextRenderOptions(room.client), - ); + describe("Initialization", () => { + it("should initialize with room data", async () => { + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); - const fn = jest.spyOn(dispatcher, "dispatch"); - vm.current.openRoom(); - expect(fn).toHaveBeenCalledWith( - expect.objectContaining({ - action: Action.ViewRoom, - room_id: room.roomId, - metricsTrigger: "RoomList", - }), - ); - }); + // Wait for async initialization + await flushPromises(); - it("should show context menu if user has access to options menu", async () => { - mocked(hasAccessToOptionsMenu).mockReturnValue(true); - const { result: vm } = renderHook( - () => useRoomListItemViewModel(room), - withClientContextRenderOptions(room.client), - ); - expect(vm.current.showContextMenu).toBe(true); - }); - - it("should show hover menu if user has access to options menu", async () => { - mocked(hasAccessToOptionsMenu).mockReturnValue(true); - const { result: vm } = renderHook( - () => useRoomListItemViewModel(room), - withClientContextRenderOptions(room.client), - ); - expect(vm.current.showHoverMenu).toBe(true); - }); - - it("should show hover menu if user has access to notification menu", async () => { - mocked(hasAccessToNotificationMenu).mockReturnValue(true); - const { result: vm } = renderHook( - () => useRoomListItemViewModel(room), - withClientContextRenderOptions(room.client), - ); - expect(vm.current.showHoverMenu).toBe(true); - }); - - it("should not show hover menu if user has an invitation notification", async () => { - mocked(hasAccessToOptionsMenu).mockReturnValue(true); - - const notificationState = new RoomNotificationState(room, false); - jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockReturnValue(notificationState); - jest.spyOn(notificationState, "invited", "get").mockReturnValue(false); - - const { result: vm } = renderHook( - () => useRoomListItemViewModel(room), - withClientContextRenderOptions(room.client), - ); - expect(vm.current.showHoverMenu).toBe(true); - }); - - it("should return a message preview if one is available and they are enabled", async () => { - jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue({ - text: "Message look like this", - } as MessagePreview); - mocked(useMessagePreviewToggle).mockReturnValue({ - shouldShowMessagePreview: true, - toggleMessagePreview: jest.fn(), + const snapshot = viewModel.getSnapshot(); + expect(snapshot.id).toBe("!room:server"); + expect(snapshot.name).toBe("Test Room"); }); - const { result: vm } = renderHook( - () => useRoomListItemViewModel(room), - withClientContextRenderOptions(room.client), - ); - await waitFor(() => expect(vm.current.messagePreview).toBe("Message look like this")); + it("should load message preview when enabled", async () => { + jest.spyOn(SettingsStore, "getValue").mockReturnValue(true); + jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue({ + text: "Hello world!", + } as MessagePreview); + + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + // Wait for async message preview load + await flushPromises(); + + expect(viewModel.getSnapshot().messagePreview).toBe("Hello world!"); + }); + + it("should not load message preview when disabled", async () => { + jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); + + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + await flushPromises(); + + expect(viewModel.getSnapshot().messagePreview).toBeUndefined(); + }); }); - it("should hide message previews when disabled", async () => { - jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue({ - text: "Message look like this", - } as MessagePreview); + describe("Notification state", () => { + it("should reflect notification state", async () => { + jest.spyOn(notificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true); + jest.spyOn(notificationState, "count", "get").mockReturnValue(5); - const { result: vm, rerender } = renderHook( - () => useRoomListItemViewModel(room), - withClientContextRenderOptions(room.client), - ); + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); - // This doesn't seem to test that the hook actually triggers an update, - // but I can't see how to test that. - rerender(); + await flushPromises(); - expect(vm.current.messagePreview).toBe(undefined); - }); - - it("should check message preview when room change", async () => { - const otherRoom = mkStubRoom("roomId2", "roomName2", room.client); - - jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue({ - text: "Message look like this", - } as MessagePreview); - mocked(useMessagePreviewToggle).mockReturnValue({ - shouldShowMessagePreview: true, - toggleMessagePreview: jest.fn(), + const snapshot = viewModel.getSnapshot(); + expect(snapshot.notification.hasAnyNotificationOrActivity).toBe(true); + expect(snapshot.notification.count).toBe(5); }); - const { result: vm, rerender } = renderHook((props) => useRoomListItemViewModel(props), { - initialProps: room, - ...withClientContextRenderOptions(room.client), - }); - await waitFor(() => expect(vm.current.messagePreview).toBe("Message look like this")); + it("should update when notification state changes", async () => { + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); - jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue(null); - rerender(otherRoom); - await waitFor(() => expect(vm.current.messagePreview).toBe(undefined)); - }); + await flushPromises(); + expect(viewModel.getSnapshot().notification.count).toBe(0); - describe("notification", () => { - let notificationState: RoomNotificationState; - beforeEach(() => { - notificationState = new RoomNotificationState(room, false); - jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockReturnValue(notificationState); + jest.spyOn(notificationState, "count", "get").mockReturnValue(3); + notificationState.emit(NotificationStateEvents.Update); + + await flushPromises(); + expect(viewModel.getSnapshot().notification.count).toBe(3); }); - it("should show notification decoration if there is call has participant", () => { - jest.spyOn(UseCallModule, "useParticipantCount").mockReturnValue(1); - - const { result: vm } = renderHook( - () => useRoomListItemViewModel(room), - withClientContextRenderOptions(room.client), - ); - expect(vm.current.showNotificationDecoration).toBe(true); - }); - - it.each([ - { - label: "hasAnyNotificationOrActivity", - mock: () => jest.spyOn(notificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true), - }, - { label: "muted", mock: () => jest.spyOn(notificationState, "muted", "get").mockReturnValue(true) }, - ])("should show notification decoration if $label=true", ({ mock }) => { - mock(); - const { result: vm } = renderHook( - () => useRoomListItemViewModel(room), - withClientContextRenderOptions(room.client), - ); - expect(vm.current.showNotificationDecoration).toBe(true); - }); - - it("should be bold if there is a notification", () => { + it("should show bold text when has notifications", async () => { jest.spyOn(notificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true); - const { result: vm } = renderHook( - () => useRoomListItemViewModel(room), - withClientContextRenderOptions(room.client), - ); - expect(vm.current.isBold).toBe(true); + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + await flushPromises(); + + expect(viewModel.getSnapshot().isBold).toBe(true); }); - it("should recompute notification state when room changes", () => { - const newRoom = mkStubRoom("room2", "Room 2", room.client); - const newNotificationState = new RoomNotificationState(newRoom, false); + it("should show mention badge", async () => { + jest.spyOn(notificationState, "isMention", "get").mockReturnValue(true); - const { result, rerender } = renderHook((room) => useRoomListItemViewModel(room), { - ...withClientContextRenderOptions(room.client), - initialProps: room, - }); + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); - expect(result.current.showNotificationDecoration).toBe(false); + await flushPromises(); - jest.spyOn(newNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true); - jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockReturnValue(newNotificationState); - rerender(newRoom); + expect(viewModel.getSnapshot().notification.isMention).toBe(true); + }); - expect(result.current.showNotificationDecoration).toBe(true); + it("should show invitation state", async () => { + jest.spyOn(notificationState, "invited", "get").mockReturnValue(true); + + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + await flushPromises(); + + expect(viewModel.getSnapshot().notification.invited).toBe(true); }); }); - describe("a11yLabel", () => { - let notificationState: RoomNotificationState; - beforeEach(() => { - notificationState = new RoomNotificationState(room, false); - jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockReturnValue(notificationState); + describe("Message preview", () => { + it("should update message preview when store emits update", async () => { + jest.spyOn(SettingsStore, "getValue").mockReturnValue(true); + jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue({ + text: "Initial message", + } as MessagePreview); + + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + await flushPromises(); + expect(viewModel.getSnapshot().messagePreview).toBe("Initial message"); + + // Update preview + jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue({ + text: "Updated message", + } as MessagePreview); + + MessagePreviewStore.instance.emit(UPDATE_EVENT); + + await flushPromises(); + expect(viewModel.getSnapshot().messagePreview).toBe("Updated message"); }); - it.each([ - { - label: "unsent message", - mock: () => jest.spyOn(notificationState, "isUnsentMessage", "get").mockReturnValue(true), - expected: "Open room roomName with an unsent message.", - }, - { - label: "invitation", - mock: () => jest.spyOn(notificationState, "invited", "get").mockReturnValue(true), - expected: "Open room roomName invitation.", - }, - { - label: "mention", - mock: () => { - jest.spyOn(notificationState, "isMention", "get").mockReturnValue(true); - jest.spyOn(notificationState, "count", "get").mockReturnValue(3); - }, - expected: "Open room roomName with 3 unread messages including mentions.", - }, - { - label: "unread", - mock: () => { - jest.spyOn(notificationState, "hasUnreadCount", "get").mockReturnValue(true); - jest.spyOn(notificationState, "count", "get").mockReturnValue(3); - }, - expected: "Open room roomName with 3 unread messages.", - }, - { - label: "default", - expected: "Open room roomName", - }, - ])("should return the $label label", ({ mock, expected }) => { - mock?.(); - const { result: vm } = renderHook( - () => useRoomListItemViewModel(room), - withClientContextRenderOptions(room.client), - ); - expect(vm.current.a11yLabel).toBe(expected); + it("should show/hide preview when setting changes", async () => { + let showPreview = false; + let watchCallback: any; + + jest.spyOn(SettingsStore, "getValue").mockImplementation(() => showPreview); + jest.spyOn(SettingsStore, "watchSetting").mockImplementation((_setting, _room, callback) => { + watchCallback = callback; + return "watcher-id"; + }); + jest.spyOn(MessagePreviewStore.instance, "getPreviewForRoom").mockResolvedValue({ + text: "Test message", + } as MessagePreview); + + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + await flushPromises(); + expect(viewModel.getSnapshot().messagePreview).toBeUndefined(); + + // Enable previews + showPreview = true; + watchCallback(null, "device", true); + + await flushPromises(); + expect(viewModel.getSnapshot().messagePreview).toBe("Test message"); + }); + }); + + describe("Room tags", () => { + it("should reflect favorite tag", async () => { + room.tags = { [DefaultTagID.Favourite]: { order: 0 } }; + + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + await flushPromises(); + + expect(viewModel.getSnapshot().isFavourite).toBe(true); + }); + + it("should reflect low priority tag", async () => { + room.tags = { [DefaultTagID.LowPriority]: { order: 0 } }; + + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + await flushPromises(); + + expect(viewModel.getSnapshot().isLowPriority).toBe(true); + }); + + it("should update when room tags change", async () => { + room.tags = {}; + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + await flushPromises(); + expect(viewModel.getSnapshot().isFavourite).toBe(false); + + room.tags = { [DefaultTagID.Favourite]: { order: 0 } }; + const tagEvent = { + getContent: () => ({ tags: { [DefaultTagID.Favourite]: { order: 0 } } }), + } as MatrixEvent; + room.emit(RoomEvent.Tags, tagEvent, room); + + await flushPromises(); + expect(viewModel.getSnapshot().isFavourite).toBe(true); + }); + }); + + describe("Call state", () => { + it("should show voice call indicator", async () => { + const mockCall = { + callType: CallType.Voice, + participants: new Map([[matrixClient.getUserId()!, {}]]), + } as unknown as Call; + + jest.spyOn(CallStore.instance, "getCall").mockReturnValue(mockCall); + + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + await flushPromises(); + + expect(viewModel.getSnapshot().notification.callType).toBe("voice"); + }); + + it("should show video call indicator", async () => { + const mockCall = { + callType: CallType.Video, + participants: new Map([[matrixClient.getUserId()!, {}]]), + } as unknown as Call; + + jest.spyOn(CallStore.instance, "getCall").mockReturnValue(mockCall); + + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + await flushPromises(); + + expect(viewModel.getSnapshot().notification.callType).toBe("video"); + }); + + it("should not show call indicator when no participants", async () => { + const mockCall = { + callType: CallType.Voice, + participants: new Map(), + } as unknown as Call; + + jest.spyOn(CallStore.instance, "getCall").mockReturnValue(mockCall); + + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + await flushPromises(); + + expect(viewModel.getSnapshot().notification.callType).toBeUndefined(); + }); + }); + + describe("Room name updates", () => { + it("should update when room name changes", async () => { + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + await flushPromises(); + expect(viewModel.getSnapshot().name).toBe("Test Room"); + + room.name = "Updated Room"; + room.emit(RoomEvent.Name, room); + + await flushPromises(); + expect(viewModel.getSnapshot().name).toBe("Updated Room"); + }); + }); + + describe("DM detection", () => { + it("should detect DM rooms", async () => { + const dmRoomMap = DMRoomMap.shared(); + jest.spyOn(dmRoomMap, "getUserIdForRoomId").mockReturnValue("@user:server"); + + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + await flushPromises(); + + // DM rooms should not show copy room link option + expect(viewModel.getSnapshot().canCopyRoomLink).toBe(false); + }); + + it("should detect non-DM rooms", async () => { + const dmRoomMap = DMRoomMap.shared(); + jest.spyOn(dmRoomMap, "getUserIdForRoomId").mockReturnValue(undefined); + + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + await flushPromises(); + + expect(viewModel.getSnapshot().canCopyRoomLink).toBe(true); + }); + }); + + describe("Actions", () => { + it("should dispatch view room action on openRoom", () => { + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); + + viewModel.onOpenRoom(); + + expect(dispatchSpy).toHaveBeenCalledWith({ + action: Action.ViewRoom, + room_id: "!room:server", + metricsTrigger: "RoomList", + }); + }); + + it("should return room object", () => { + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + expect(viewModel.getSnapshot().room).toBe(room); + }); + + it("should dispatch view_invite action when onInvite is called", () => { + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); + + viewModel.onInvite(); + + expect(dispatchSpy).toHaveBeenCalledWith({ + action: "view_invite", + roomId: "!room:server", + }); + }); + + it("should dispatch copy_room action when onCopyRoomLink is called", () => { + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); + + viewModel.onCopyRoomLink(); + + expect(dispatchSpy).toHaveBeenCalledWith({ + action: "copy_room", + room_id: "!room:server", + }); + }); + + it("should dispatch leave_room action when onLeaveRoom is called for normal room", () => { + room.tags = {}; + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); + + viewModel.onLeaveRoom(); + + expect(dispatchSpy).toHaveBeenCalledWith({ + action: "leave_room", + room_id: "!room:server", + }); + }); + + it("should dispatch forget_room action when onLeaveRoom is called for archived room", () => { + room.tags = { [DefaultTagID.Archived]: { order: 0 } }; + + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); + + viewModel.onLeaveRoom(); + + expect(dispatchSpy).toHaveBeenCalledWith({ + action: "forget_room", + room_id: "!room:server", + }); + }); + }); + + describe("Cleanup", () => { + it("should unsubscribe from all events on dispose", () => { + viewModel = new RoomListItemViewModel({ room, client: matrixClient }); + + const offSpy = jest.spyOn(notificationState, "off"); + + viewModel.dispose(); + + expect(offSpy).toHaveBeenCalled(); }); }); }); diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx deleted file mode 100644 index c8ede64320..0000000000 --- a/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx +++ /dev/null @@ -1,341 +0,0 @@ -/* -Copyright 2025 New Vector Ltd. - -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 { range } from "lodash"; -import { act, renderHook, waitFor } from "jest-matrix-react"; -import { mocked } from "jest-mock"; - -import RoomListStoreV3, { LISTS_UPDATE_EVENT } from "../../../../../src/stores/room-list-v3/RoomListStoreV3"; -import { mkStubRoom } from "../../../../test-utils"; -import { useRoomListViewModel } from "../../../../../src/components/viewmodels/roomlist/RoomListViewModel"; -import { FilterKey } from "../../../../../src/stores/room-list-v3/skip-list/filters"; -import { hasCreateRoomRights, createRoom } from "../../../../../src/components/viewmodels/roomlist/utils"; -import dispatcher from "../../../../../src/dispatcher/dispatcher"; -import { Action } from "../../../../../src/dispatcher/actions"; -import { SdkContextClass } from "../../../../../src/contexts/SDKContext"; -import SpaceStore from "../../../../../src/stores/spaces/SpaceStore"; -import { UPDATE_SELECTED_SPACE } from "../../../../../src/stores/spaces"; - -jest.mock("../../../../../src/components/viewmodels/roomlist/utils", () => ({ - hasCreateRoomRights: jest.fn().mockReturnValue(false), - createRoom: jest.fn(), -})); - -describe("RoomListViewModel", () => { - function mockAndCreateRooms() { - const rooms = range(10).map((i) => mkStubRoom(`foo${i}:matrix.org`, `Foo ${i}`, undefined)); - const fn = jest - .spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace") - .mockImplementation(() => ({ spaceId: "home", rooms: [...rooms] })); - return { rooms, fn }; - } - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it("should return a list of rooms", async () => { - const { rooms } = mockAndCreateRooms(); - const { result: vm } = renderHook(() => useRoomListViewModel()); - - expect(vm.current.roomsResult.rooms).toHaveLength(10); - for (const room of rooms) { - expect(vm.current.roomsResult.rooms).toContain(room); - } - }); - - it("should update list of rooms on event from room list store", async () => { - const { rooms } = mockAndCreateRooms(); - const { result: vm } = renderHook(() => useRoomListViewModel()); - - const newRoom = mkStubRoom("bar:matrix.org", "Bar", undefined); - rooms.push(newRoom); - await act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT)); - - await waitFor(() => { - expect(vm.current.roomsResult.rooms).toContain(newRoom); - }); - }); - - describe("Filters", () => { - it("should provide list of available filters", () => { - mockAndCreateRooms(); - const { result: vm } = renderHook(() => useRoomListViewModel()); - // should have 6 filters - expect(vm.current.primaryFilters).toHaveLength(7); - // check the order - for (const [i, name] of [ - "Unreads", - "People", - "Rooms", - "Favourites", - "Mentions", - "Invites", - "Low priority", - ].entries()) { - expect(vm.current.primaryFilters[i].name).toEqual(name); - expect(vm.current.primaryFilters[i].active).toEqual(false); - } - }); - - it("should get filtered rooms from RLS on toggle", () => { - const { fn } = mockAndCreateRooms(); - const { result: vm } = renderHook(() => useRoomListViewModel()); - // Let's say we toggle the People toggle - const i = vm.current.primaryFilters.findIndex((f) => f.name === "People"); - act(() => { - vm.current.primaryFilters[i].toggle(); - }); - expect(fn).toHaveBeenCalledWith([FilterKey.PeopleFilter]); - expect(vm.current.primaryFilters[i].active).toEqual(true); - }); - - it("should change active property on toggle", () => { - mockAndCreateRooms(); - const { result: vm } = renderHook(() => useRoomListViewModel()); - // Let's say we toggle the People filter - const i = vm.current.primaryFilters.findIndex((f) => f.name === "People"); - expect(vm.current.primaryFilters[i].active).toEqual(false); - act(() => { - vm.current.primaryFilters[i].toggle(); - }); - expect(vm.current.primaryFilters[i].active).toEqual(true); - - // Let's say that we toggle the Favourite filter - const j = vm.current.primaryFilters.findIndex((f) => f.name === "Favourites"); - act(() => { - vm.current.primaryFilters[j].toggle(); - }); - expect(vm.current.primaryFilters[i].active).toEqual(false); - expect(vm.current.primaryFilters[j].active).toEqual(true); - }); - - it("should return the current active primary filter", async () => { - // Let's say that the user's preferred sorting is alphabetic - mockAndCreateRooms(); - const { result: vm } = renderHook(() => useRoomListViewModel()); - // Toggle people filter - const i = vm.current.primaryFilters.findIndex((f) => f.name === "People"); - expect(vm.current.primaryFilters[i].active).toEqual(false); - act(() => vm.current.primaryFilters[i].toggle()); - - // The active primary filter should be the People filter - expect(vm.current.activePrimaryFilter).toEqual(vm.current.primaryFilters[i]); - }); - - it("should not remove all filters when active space is changed", async () => { - mockAndCreateRooms(); - const { result: vm } = renderHook(() => useRoomListViewModel()); - - // Let's first toggle the People filter - const i = vm.current.primaryFilters.findIndex((f) => f.name === "People"); - act(() => { - vm.current.primaryFilters[i].toggle(); - }); - expect(vm.current.primaryFilters[i].active).toEqual(true); - - // Simulate a space change - await act(() => SpaceStore.instance.emit(UPDATE_SELECTED_SPACE)); - - // Primary filter should remain unchanged - expect(vm.current.activePrimaryFilter?.name).toEqual("People"); - }); - }); - - describe("Create room and chat", () => { - it("should be canCreateRoom=false if hasCreateRoomRights=false", () => { - mocked(hasCreateRoomRights).mockReturnValue(false); - const { result } = renderHook(() => useRoomListViewModel()); - expect(result.current.canCreateRoom).toBe(false); - }); - - it("should be canCreateRoom=true if hasCreateRoomRights=true", () => { - mocked(hasCreateRoomRights).mockReturnValue(true); - const { result } = renderHook(() => useRoomListViewModel()); - expect(result.current.canCreateRoom).toBe(true); - }); - - it("should call createRoom", () => { - const { result } = renderHook(() => useRoomListViewModel()); - result.current.createRoom(); - expect(mocked(createRoom)).toHaveBeenCalled(); - }); - - it("should dispatch Action.CreateChat", () => { - const spy = jest.spyOn(dispatcher, "fire"); - const { result } = renderHook(() => useRoomListViewModel()); - result.current.createChatRoom(); - expect(spy).toHaveBeenCalledWith(Action.CreateChat); - }); - }); - - describe("Sticky room and active index", () => { - function expectActiveRoom(vm: ReturnType, i: number, roomId: string) { - expect(vm.activeIndex).toEqual(i); - expect(vm.roomsResult.rooms[i].roomId).toEqual(roomId); - } - - it("active index is calculated with the last opened room in a space", () => { - // Let's say there's two spaces: !space1:matrix.org and !space2:matrix.org - // Let's also say that the current active space is !space1:matrix.org - let currentSpace = "!space1:matrix.org"; - jest.spyOn(SpaceStore.instance, "activeSpace", "get").mockImplementation(() => currentSpace); - - const rooms = range(10).map((i) => mkStubRoom(`foo${i}:matrix.org`, `Foo ${i}`, undefined)); - // Let's say all the rooms are in space1 - const roomsInSpace1 = { spaceId: currentSpace, rooms: [...rooms] }; - // Let's say all rooms with even index are in space 2 - const roomsInSpace2 = { spaceId: "!space2:matrix.org", rooms: [...rooms].filter((_, i) => i % 2 === 0) }; - jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockImplementation(() => - currentSpace === "!space1:matrix.org" ? roomsInSpace1 : roomsInSpace2, - ); - - // Let's say that the room at index 4 is currently active - const roomId = rooms[4].roomId; - jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId); - - const { result: vm } = renderHook(() => useRoomListViewModel()); - expect(vm.current.activeIndex).toEqual(4); - - // Let's say that space is changed to "!space2:matrix.org" - currentSpace = "!space2:matrix.org"; - // Let's say that room[6] is active in space 2 - const activeRoomIdInSpace2 = rooms[6].roomId; - jest.spyOn(SpaceStore.instance, "getLastSelectedRoomIdForSpace").mockImplementation( - () => activeRoomIdInSpace2, - ); - act(() => { - RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT); - }); - - // Active index should be 3 even without the room change event. - expectActiveRoom(vm.current, 3, activeRoomIdInSpace2); - }); - - it("active room and active index are retained on order change", () => { - const { rooms } = mockAndCreateRooms(); - - // Let's say that the room at index 5 is active - const roomId = rooms[5].roomId; - jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId); - - const { result: vm } = renderHook(() => useRoomListViewModel()); - expect(vm.current.activeIndex).toEqual(5); - - // Let's say that room at index 9 moves to index 5 - const room9 = rooms[9]; - rooms.splice(9, 1); - rooms.splice(5, 0, room9); - act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT)); - - // Active room index should still be 5 - expectActiveRoom(vm.current, 5, roomId); - - // Let's add 2 new rooms from index 0 - const newRoom1 = mkStubRoom("bar1:matrix.org", "Bar 1", undefined); - const newRoom2 = mkStubRoom("bar2:matrix.org", "Bar 2", undefined); - rooms.unshift(newRoom1, newRoom2); - act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT)); - - // Active room index should still be 5 - expectActiveRoom(vm.current, 5, roomId); - }); - - it("active room and active index are updated when another room is opened", () => { - const { rooms } = mockAndCreateRooms(); - const roomId = rooms[5].roomId; - jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId); - - const { result: vm } = renderHook(() => useRoomListViewModel()); - expectActiveRoom(vm.current, 5, roomId); - - // Let's say that room at index 9 becomes active - const room = rooms[9]; - act(() => { - dispatcher.dispatch( - { - action: Action.ActiveRoomChanged, - oldRoomId: null, - newRoomId: room.roomId, - }, - true, - ); - }); - - // Active room index should change to reflect new room - expectActiveRoom(vm.current, 9, room.roomId); - }); - - it("active room and active index are updated when active index spills out of rooms array bounds", () => { - const { rooms } = mockAndCreateRooms(); - // Let's say that the room at index 5 is active - const roomId = rooms[5].roomId; - jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId); - - const { result: vm } = renderHook(() => useRoomListViewModel()); - expectActiveRoom(vm.current, 5, roomId); - - // Let's say that we remove rooms from the start of the array - for (let i = 0; i < 4; ++i) { - // We should be able to do 4 deletions before we run out of rooms - rooms.splice(0, 1); - act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT)); - expectActiveRoom(vm.current, 5, roomId); - } - - // If we remove one more room from the start, there's not going to be enough rooms - // to maintain the active index. - rooms.splice(0, 1); - act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT)); - expectActiveRoom(vm.current, 0, roomId); - }); - - it("active room and active index are retained when rooms that appear after the active room are deleted", () => { - const { rooms } = mockAndCreateRooms(); - // Let's say that the room at index 5 is active - const roomId = rooms[5].roomId; - jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId); - - const { result: vm } = renderHook(() => useRoomListViewModel()); - expectActiveRoom(vm.current, 5, roomId); - - // Let's say that we remove rooms from the start of the array - for (let i = 0; i < 4; ++i) { - // Deleting rooms after index 5 (active) should not update the active index - rooms.splice(6, 1); - act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT)); - expectActiveRoom(vm.current, 5, roomId); - } - }); - - it("active room index becomes undefined when active room is deleted", () => { - const { rooms } = mockAndCreateRooms(); - // Let's say that the room at index 5 is active - let roomId: string | null = rooms[5].roomId; - jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId); - - const { result: vm } = renderHook(() => useRoomListViewModel()); - expectActiveRoom(vm.current, 5, roomId); - - // Let's remove the active room (i.e room at index 5) - rooms.splice(5, 1); - roomId = null; - act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT)); - expect(vm.current.activeIndex).toBeUndefined(); - }); - - it("active room index is initially undefined", () => { - mockAndCreateRooms(); - - // Let's say that there's no active room currently - jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => null); - - const { result: vm } = renderHook(() => useRoomListViewModel()); - expect(vm.current.activeIndex).toEqual(undefined); - }); - }); -}); diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListViewViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListViewViewModel-test.tsx new file mode 100644 index 0000000000..ee3faabc65 --- /dev/null +++ b/test/unit-tests/components/viewmodels/roomlist/RoomListViewViewModel-test.tsx @@ -0,0 +1,546 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * 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 { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix"; +import { mocked } from "jest-mock"; + +import { RoomListViewViewModel } from "../../../../../src/components/viewmodels/roomlist/RoomListViewViewModel"; +import { createTestClient, flushPromises, mkStubRoom, stubClient } from "../../../../test-utils"; +import RoomListStoreV3, { RoomListStoreV3Event } from "../../../../../src/stores/room-list-v3/RoomListStoreV3"; +import SpaceStore from "../../../../../src/stores/spaces/SpaceStore"; +import { FilterKey } from "../../../../../src/stores/room-list-v3/skip-list/filters"; +import dispatcher from "../../../../../src/dispatcher/dispatcher"; +import { Action } from "../../../../../src/dispatcher/actions"; +import { hasCreateRoomRights } from "../../../../../src/components/viewmodels/roomlist/utils"; +import { SdkContextClass } from "../../../../../src/contexts/SDKContext"; +import DMRoomMap from "../../../../../src/utils/DMRoomMap"; + +jest.mock("../../../../../src/components/viewmodels/roomlist/utils", () => ({ + hasCreateRoomRights: jest.fn().mockReturnValue(false), + hasAccessToOptionsMenu: jest.fn().mockReturnValue(true), + hasAccessToNotificationMenu: jest.fn().mockReturnValue(true), +})); + +describe("RoomListViewViewModel", () => { + let matrixClient: MatrixClient; + let room1: Room; + let room2: Room; + let room3: Room; + let viewModel: RoomListViewViewModel; + + beforeEach(() => { + matrixClient = createTestClient(); + room1 = mkStubRoom("!room1:server", "Room 1", matrixClient); + room2 = mkStubRoom("!room2:server", "Room 2", matrixClient); + room3 = mkStubRoom("!room3:server", "Room 3", matrixClient); + + // Setup DMRoomMap + const dmRoomMap = { + getUserIdForRoomId: jest.fn().mockReturnValue(null), + } as unknown as DMRoomMap; + DMRoomMap.setShared(dmRoomMap); + + jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({ + spaceId: "home", + rooms: [room1, room2, room3], + }); + + jest.spyOn(RoomListStoreV3.instance, "isLoadingRooms", "get").mockReturnValue(false); + jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(null); + jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(null); + + mocked(hasCreateRoomRights).mockReturnValue(false); + }); + + afterEach(() => { + viewModel?.dispose(); + jest.restoreAllMocks(); + }); + + describe("Initialization", () => { + it("should initialize with correct snapshot", () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + const snapshot = viewModel.getSnapshot(); + expect(snapshot.roomIds).toEqual(["!room1:server", "!room2:server", "!room3:server"]); + expect(snapshot.isRoomListEmpty).toBe(false); + expect(snapshot.isLoadingRooms).toBe(false); + expect(snapshot.roomListState.spaceId).toBe("home"); + expect(snapshot.filterIds.length).toBeGreaterThan(0); + expect(snapshot.activeFilterId).toBeUndefined(); + }); + + it("should initialize with empty room list", () => { + jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({ + spaceId: "home", + rooms: [], + }); + + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + expect(viewModel.getSnapshot().roomIds).toEqual([]); + expect(viewModel.getSnapshot().isRoomListEmpty).toBe(true); + }); + + it("should set canCreateRoom based on user rights", () => { + mocked(hasCreateRoomRights).mockReturnValue(true); + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + expect(viewModel.getSnapshot().canCreateRoom).toBe(true); + }); + }); + + describe("Room list updates", () => { + it("should update room list when ListsUpdate event fires", () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + const newRoom = mkStubRoom("!room4:server", "Room 4", matrixClient); + jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({ + spaceId: "home", + rooms: [room1, room2, room3, newRoom], + }); + + RoomListStoreV3.instance.emit(RoomListStoreV3Event.ListsUpdate); + + expect(viewModel.getSnapshot().roomIds).toEqual([ + "!room1:server", + "!room2:server", + "!room3:server", + "!room4:server", + ]); + }); + + it("should update loading state when ListsLoaded event fires", () => { + jest.spyOn(RoomListStoreV3.instance, "isLoadingRooms", "get").mockReturnValue(true); + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + expect(viewModel.getSnapshot().isLoadingRooms).toBe(true); + + RoomListStoreV3.instance.emit(RoomListStoreV3Event.ListsLoaded); + + expect(viewModel.getSnapshot().isLoadingRooms).toBe(false); + }); + }); + + describe("Space switching", () => { + it("should update room list when space changes", () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + const spaceRoomList = [room1, room2]; + + jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({ + spaceId: "!space:server", + rooms: spaceRoomList, + }); + + jest.spyOn(SpaceStore.instance, "getLastSelectedRoomIdForSpace").mockReturnValue("!room1:server"); + + RoomListStoreV3.instance.emit(RoomListStoreV3Event.ListsUpdate); + + expect(viewModel.getSnapshot().roomListState.spaceId).toBe("!space:server"); + expect(viewModel.getSnapshot().roomIds).toEqual(["!room1:server", "!room2:server"]); + }); + + it("should clear view models when space changes", () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + // Get view models for visible rooms + const vm1 = viewModel.getRoomItemViewModel("!room1:server"); + const vm2 = viewModel.getRoomItemViewModel("!room2:server"); + + const disposeSpy1 = jest.spyOn(vm1, "dispose"); + const disposeSpy2 = jest.spyOn(vm2, "dispose"); + + // Change space + jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({ + spaceId: "!space:server", + rooms: [room3], + }); + + RoomListStoreV3.instance.emit(RoomListStoreV3Event.ListsUpdate); + + expect(disposeSpy1).toHaveBeenCalled(); + expect(disposeSpy2).toHaveBeenCalled(); + }); + }); + + describe("Active room tracking", () => { + it("should update active room index when room is selected", async () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server"); + + dispatcher.dispatch({ + action: Action.ActiveRoomChanged, + oldRoomId: "!room1:server", + newRoomId: "!room2:server", + }); + + // Use setTimeout to allow the dispatcher callback to run + await flushPromises(); + expect(viewModel.getSnapshot().roomListState.activeRoomIndex).toBe(1); + }); + + it("should return undefined active room index when no room is selected", async () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(null); + + dispatcher.dispatch({ + action: Action.ActiveRoomChanged, + oldRoomId: "!room1:server", + newRoomId: null, + }); + + // Use setTimeout to allow the dispatcher callback to run + await flushPromises(); + expect(viewModel.getSnapshot().roomListState.activeRoomIndex).toBeUndefined(); + }); + }); + + describe("Sticky room behavior", () => { + it("should keep selected room at same index when room list updates", async () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + // Select room at index 1 + jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server"); + dispatcher.dispatch({ + action: Action.ActiveRoomChanged, + newRoomId: "!room2:server", + }); + + await flushPromises(); + expect(viewModel.getSnapshot().roomListState.activeRoomIndex).toBe(1); + + // Simulate room list update that would move room2 to front + jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({ + spaceId: "home", + rooms: [room2, room1, room3], // room2 moved to front + }); + + RoomListStoreV3.instance.emit(RoomListStoreV3Event.ListsUpdate); + + // Active room should still be at index 1 (sticky behavior) + expect(viewModel.getSnapshot().roomListState.activeRoomIndex).toBe(1); + expect(viewModel.getSnapshot().roomIds[1]).toBe("!room2:server"); + }); + + it("should not apply sticky behavior when user changes rooms", async () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + // Select room at index 1 + jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server"); + dispatcher.dispatch({ + action: Action.ActiveRoomChanged, + newRoomId: "!room2:server", + }); + + await flushPromises(); + + // User switches to room3 + jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room3:server"); + dispatcher.dispatch({ + action: Action.ActiveRoomChanged, + oldRoomId: "!room2:server", + newRoomId: "!room3:server", + }); + + await flushPromises(); + expect(viewModel.getSnapshot().roomListState.activeRoomIndex).toBe(2); + }); + }); + + describe("Filters", () => { + it("should toggle filter on", () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + expect(viewModel.getSnapshot().activeFilterId).toBeUndefined(); + + jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({ + spaceId: "home", + rooms: [room1], + filterKeys: [FilterKey.UnreadFilter], + }); + + viewModel.onToggleFilter("unread"); + + expect(viewModel.getSnapshot().activeFilterId).toBe("unread"); + expect(viewModel.getSnapshot().roomIds).toEqual(["!room1:server"]); + }); + + it("should toggle filter off", () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + // Turn filter on + jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({ + spaceId: "home", + rooms: [room1], + filterKeys: [FilterKey.UnreadFilter], + }); + viewModel.onToggleFilter("unread"); + + expect(viewModel.getSnapshot().activeFilterId).toBe("unread"); + + // Turn filter off + jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({ + spaceId: "home", + rooms: [room1, room2, room3], + }); + viewModel.onToggleFilter("unread"); + + expect(viewModel.getSnapshot().activeFilterId).toBeUndefined(); + expect(viewModel.getSnapshot().roomIds).toEqual(["!room1:server", "!room2:server", "!room3:server"]); + }); + + it("should clear view models when filter changes", () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + // Get view models + const vm1 = viewModel.getRoomItemViewModel("!room1:server"); + const disposeSpy = jest.spyOn(vm1, "dispose"); + + jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({ + spaceId: "home", + rooms: [room2], + filterKeys: [FilterKey.UnreadFilter], + }); + + viewModel.onToggleFilter("unread"); + + expect(disposeSpy).toHaveBeenCalled(); + }); + }); + + describe("Room item view models", () => { + it("should create room item view model on demand", () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + const itemViewModel = viewModel.getRoomItemViewModel("!room1:server"); + + expect(itemViewModel).toBeDefined(); + expect(itemViewModel.getSnapshot().room).toBe(room1); + }); + + it("should reuse existing room item view model", () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + const itemViewModel1 = viewModel.getRoomItemViewModel("!room1:server"); + const itemViewModel2 = viewModel.getRoomItemViewModel("!room1:server"); + + expect(itemViewModel1).toBe(itemViewModel2); + }); + + it("should throw error when requesting view model for non-existent room", () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + expect(() => { + viewModel.getRoomItemViewModel("!nonexistent:server"); + }).toThrow(); + }); + + it("should dispose view models for rooms no longer visible", () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + const vm1 = viewModel.getRoomItemViewModel("!room1:server"); + const vm2 = viewModel.getRoomItemViewModel("!room2:server"); + const vm3 = viewModel.getRoomItemViewModel("!room3:server"); + + const disposeSpy1 = jest.spyOn(vm1, "dispose"); + const disposeSpy3 = jest.spyOn(vm3, "dispose"); + + // Update to show only middle room (index 1) + viewModel.updateVisibleRooms(1, 2); + + expect(disposeSpy1).toHaveBeenCalled(); + expect(disposeSpy3).toHaveBeenCalled(); + + // vm2 should still exist + const vm2Again = viewModel.getRoomItemViewModel("!room2:server"); + expect(vm2Again).toBe(vm2); + }); + }); + + describe("Room creation", () => { + it("should dispatch CreateChat action when createChatRoom is called", () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + const dispatchSpy = jest.spyOn(dispatcher, "fire"); + + viewModel.createChatRoom(); + + expect(dispatchSpy).toHaveBeenCalledWith(Action.CreateChat); + }); + + it("should dispatch CreateRoom action without parent space", () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); + + viewModel.createRoom(); + + expect(dispatchSpy).toHaveBeenCalledWith({ + action: Action.CreateRoom, + }); + }); + + it("should dispatch CreateRoom action with parent space", () => { + const spaceRoom = mkStubRoom("!space:server", "Space", matrixClient); + jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(spaceRoom); + + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); + + viewModel.createRoom(); + + expect(dispatchSpy).toHaveBeenCalledWith({ + action: Action.CreateRoom, + parent_space: spaceRoom, + }); + }); + }); + + describe("Keyboard navigation (ViewRoomDelta)", () => { + beforeEach(() => { + // stubClient sets up MatrixClientPeg which is needed when ViewRoom action is dispatched + stubClient(); + }); + + it("should navigate to next room when delta is 1", async () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room1:server"); + + const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); + + dispatcher.dispatch({ + action: Action.ViewRoomDelta, + delta: 1, + unread: false, + }); + + await flushPromises(); + + expect(dispatchSpy).toHaveBeenCalledWith( + expect.objectContaining({ + action: Action.ViewRoom, + room_id: "!room2:server", + }), + ); + }); + + it("should navigate to previous room when delta is -1", async () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room2:server"); + + const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); + + dispatcher.dispatch({ + action: Action.ViewRoomDelta, + delta: -1, + unread: false, + }); + + await flushPromises(); + + expect(dispatchSpy).toHaveBeenCalledWith( + expect.objectContaining({ + action: Action.ViewRoom, + room_id: "!room1:server", + }), + ); + }); + + it("should wrap around to last room when navigating backwards from first room", async () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!room1:server"); + + const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); + + dispatcher.dispatch({ + action: Action.ViewRoomDelta, + delta: -1, + unread: false, + }); + + await flushPromises(); + + expect(dispatchSpy).toHaveBeenCalledWith( + expect.objectContaining({ + action: Action.ViewRoom, + room_id: "!room3:server", + }), + ); + }); + + it("should not navigate when current room is not found", async () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("!unknown:server"); + + const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); + dispatchSpy.mockClear(); + + dispatcher.dispatch({ + action: Action.ViewRoomDelta, + delta: 1, + unread: false, + }); + + await flushPromises(); + + // Should not dispatch ViewRoom since current room wasn't found + expect(dispatchSpy).not.toHaveBeenCalledWith( + expect.objectContaining({ + action: Action.ViewRoom, + }), + ); + }); + + it("should not navigate when no room is selected", async () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(null); + + const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); + dispatchSpy.mockClear(); + + dispatcher.dispatch({ + action: Action.ViewRoomDelta, + delta: 1, + unread: false, + }); + + await flushPromises(); + + expect(dispatchSpy).not.toHaveBeenCalledWith( + expect.objectContaining({ + action: Action.ViewRoom, + }), + ); + }); + }); + + describe("Cleanup", () => { + it("should dispose all room item view models on dispose", () => { + viewModel = new RoomListViewViewModel({ client: matrixClient }); + + const vm1 = viewModel.getRoomItemViewModel("!room1:server"); + const vm2 = viewModel.getRoomItemViewModel("!room2:server"); + + const disposeSpy1 = jest.spyOn(vm1, "dispose"); + const disposeSpy2 = jest.spyOn(vm2, "dispose"); + + viewModel.dispose(); + + expect(disposeSpy1).toHaveBeenCalled(); + expect(disposeSpy2).toHaveBeenCalled(); + }); + }); +}); diff --git a/test/unit-tests/components/viewmodels/roomlist/useRoomListNavigation-test.ts b/test/unit-tests/components/viewmodels/roomlist/useRoomListNavigation-test.ts deleted file mode 100644 index 1ae8606697..0000000000 --- a/test/unit-tests/components/viewmodels/roomlist/useRoomListNavigation-test.ts +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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 { renderHook } from "jest-matrix-react"; -import { type Room } from "matrix-js-sdk/src/matrix"; -import { waitFor } from "@testing-library/dom"; - -import { SdkContextClass } from "../../../../../src/contexts/SDKContext"; -import dispatcher from "../../../../../src/dispatcher/dispatcher"; -import { mkStubRoom, stubClient } from "../../../../test-utils"; -import { useRoomListNavigation } from "../../../../../src/components/viewmodels/roomlist/useRoomListNavigation"; -import { Action } from "../../../../../src/dispatcher/actions"; -import DMRoomMap from "../../../../../src/utils/DMRoomMap"; -import { RoomNotificationStateStore } from "../../../../../src/stores/notifications/RoomNotificationStateStore"; -import { type RoomNotificationState } from "../../../../../src/stores/notifications/RoomNotificationState"; - -describe("useRoomListNavigation", () => { - let rooms: Room[]; - - beforeEach(() => { - const matrixClient = stubClient(); - rooms = [ - mkStubRoom("room1", "Room 1", matrixClient), - mkStubRoom("room2", "Room 2", matrixClient), - mkStubRoom("room3", "Room 3", matrixClient), - ]; - - DMRoomMap.makeShared(matrixClient); - jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(undefined); - jest.spyOn(dispatcher, "dispatch"); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("should navigate to the next room based on delta", async () => { - jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("room1"); - - renderHook(() => useRoomListNavigation(rooms)); - dispatcher.dispatch({ - action: Action.ViewRoomDelta, - delta: 1, - unread: false, - }); - - await waitFor(() => - expect(dispatcher.dispatch).toHaveBeenCalledWith({ - action: Action.ViewRoom, - room_id: "room2", - show_room_tile: true, - metricsTrigger: "WebKeyboardShortcut", - metricsViaKeyboard: true, - }), - ); - }); - - it("should navigate to the previous room based on delta", async () => { - jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("room2"); - - renderHook(() => useRoomListNavigation(rooms)); - dispatcher.dispatch({ - action: Action.ViewRoomDelta, - delta: -1, - unread: false, - }); - - await waitFor(() => - expect(dispatcher.dispatch).toHaveBeenCalledWith({ - action: Action.ViewRoom, - room_id: "room1", - show_room_tile: true, - metricsTrigger: "WebKeyboardShortcut", - metricsViaKeyboard: true, - }), - ); - }); - - it("should wrap around to the first room when navigating past the last room", async () => { - jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("room3"); - - renderHook(() => useRoomListNavigation(rooms)); - dispatcher.dispatch({ - action: Action.ViewRoomDelta, - delta: 1, - unread: false, - }); - - await waitFor(() => - expect(dispatcher.dispatch).toHaveBeenCalledWith({ - action: Action.ViewRoom, - room_id: "room1", - show_room_tile: true, - metricsTrigger: "WebKeyboardShortcut", - metricsViaKeyboard: true, - }), - ); - }); - - it("should wrap around to the last room when navigating before the first room", async () => { - jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("room1"); - - renderHook(() => useRoomListNavigation(rooms)); - dispatcher.dispatch({ - action: Action.ViewRoomDelta, - delta: -1, - unread: false, - }); - - await waitFor(() => - expect(dispatcher.dispatch).toHaveBeenCalledWith({ - action: Action.ViewRoom, - room_id: "room3", - show_room_tile: true, - metricsTrigger: "WebKeyboardShortcut", - metricsViaKeyboard: true, - }), - ); - }); - - it("should filter rooms to only unread when unread=true", async () => { - jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue("room1"); - jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation( - (room) => - ({ - isUnread: room.roomId !== "room1", - }) as RoomNotificationState, - ); - - renderHook(() => useRoomListNavigation(rooms)); - - dispatcher.dispatch({ - action: Action.ViewRoomDelta, - delta: 1, - unread: true, - }); - - await waitFor(() => - expect(dispatcher.dispatch).toHaveBeenCalledWith({ - action: Action.ViewRoom, - room_id: "room2", - show_room_tile: true, - metricsTrigger: "WebKeyboardShortcut", - metricsViaKeyboard: true, - }), - ); - }); -}); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/EmptyRoomList-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/EmptyRoomList-test.tsx deleted file mode 100644 index 92466f685c..0000000000 --- a/test/unit-tests/components/views/rooms/RoomListPanel/EmptyRoomList-test.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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, screen } from "jest-matrix-react"; -import userEvent from "@testing-library/user-event"; - -import { type RoomListViewState } from "../../../../../../src/components/viewmodels/roomlist/RoomListViewModel"; -import { EmptyRoomList } from "../../../../../../src/components/views/rooms/RoomListPanel/EmptyRoomList"; -import { FilterKey } from "../../../../../../src/stores/room-list-v3/skip-list/filters"; - -describe("", () => { - let vm: RoomListViewState; - - beforeEach(() => { - vm = { - isLoadingRooms: false, - roomsResult: { spaceId: "home", rooms: [] }, - primaryFilters: [], - createRoom: jest.fn(), - createChatRoom: jest.fn(), - canCreateRoom: true, - activeIndex: undefined, - }; - }); - - test("should render the default placeholder when there is no filter", async () => { - const user = userEvent.setup(); - - const { asFragment } = render(); - expect(screen.getByText("No chats yet")).toBeInTheDocument(); - expect(asFragment()).toMatchSnapshot(); - - await user.click(screen.getByRole("button", { name: "Start chat" })); - expect(vm.createChatRoom).toHaveBeenCalled(); - - await user.click(screen.getByRole("button", { name: "New room" })); - expect(vm.createRoom).toHaveBeenCalled(); - }); - - test("should not render the new room button if the user doesn't have the rights to create a room", async () => { - const newState = { ...vm, canCreateRoom: false }; - - const { asFragment } = render(); - expect(screen.queryByRole("button", { name: "New room" })).toBeNull(); - expect(asFragment()).toMatchSnapshot(); - }); - - it.each([ - { key: FilterKey.UnreadFilter, name: "unread", action: "Show all chats" }, - { key: FilterKey.MentionsFilter, name: "mention", action: "See all activity" }, - { key: FilterKey.InvitesFilter, name: "invite", action: "See all activity" }, - { key: FilterKey.LowPriorityFilter, name: "low priority", action: "See all activity" }, - ])("should display the empty state for the $name filter", async ({ key, name, action }) => { - const user = userEvent.setup(); - const activePrimaryFilter = { - toggle: jest.fn(), - active: true, - name, - key, - }; - const newState = { - ...vm, - activePrimaryFilter, - }; - - const { asFragment } = render(); - await user.click(screen.getByRole("button", { name: action })); - expect(activePrimaryFilter.toggle).toHaveBeenCalled(); - expect(asFragment()).toMatchSnapshot(); - }); - - it.each([ - { key: FilterKey.FavouriteFilter, name: "favourite" }, - { key: FilterKey.PeopleFilter, name: "people" }, - { key: FilterKey.RoomsFilter, name: "rooms" }, - ])("should display empty state for filter $name", ({ name, key }) => { - const activePrimaryFilter = { - toggle: jest.fn(), - active: true, - name, - key, - }; - const newState = { ...vm, activePrimaryFilter }; - const { asFragment } = render(); - expect(asFragment()).toMatchSnapshot(); - }); -}); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomList-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomList-test.tsx deleted file mode 100644 index fa7b351bea..0000000000 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomList-test.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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 { type MatrixClient } from "matrix-js-sdk/src/matrix"; -import { render } from "jest-matrix-react"; -import { fireEvent } from "@testing-library/dom"; -import { VirtuosoMockContext } from "@element-hq/web-shared-components"; - -import { type RoomListViewState } from "../../../../../../src/components/viewmodels/roomlist/RoomListViewModel"; -import { RoomList } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomList"; -import DMRoomMap from "../../../../../../src/utils/DMRoomMap"; -import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext"; -import { Landmark, LandmarkNavigation } from "../../../../../../src/accessibility/LandmarkNavigation"; -import { mkRoom, stubClient } from "../../../../../test-utils"; - -describe("", () => { - let matrixClient: MatrixClient; - let vm: RoomListViewState; - - beforeEach(() => { - matrixClient = stubClient(); - const rooms = Array.from({ length: 10 }, (_, i) => mkRoom(matrixClient, `room${i}`)); - vm = { - isLoadingRooms: false, - roomsResult: { spaceId: "home", rooms }, - primaryFilters: [], - createRoom: jest.fn(), - createChatRoom: jest.fn(), - canCreateRoom: true, - activeIndex: undefined, - }; - - // Needed to render a room list cell - DMRoomMap.makeShared(matrixClient); - jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(undefined); - }); - - it("should render a room list", () => { - const { asFragment } = render(, { - wrapper: ({ children }) => ( - - - <>{children} - - - ), - }); - // At the moment the context prop on Virtuoso gets rendered in the dom as "[object Object]". - // This is a general issue with the react-virtuoso library. - // TODO: Update the snapshot when the following issue is resolved: https://github.com/petyosi/react-virtuoso/issues/1281 - expect(asFragment()).toMatchSnapshot(); - }); - - it.each([ - { shortcut: { key: "F6", ctrlKey: true, shiftKey: true }, isPreviousLandmark: true, label: "PreviousLandmark" }, - { shortcut: { key: "F6", ctrlKey: true }, isPreviousLandmark: false, label: "NextLandmark" }, - ])("should navigate to the landmark on NextLandmark.$label action", ({ shortcut, isPreviousLandmark }) => { - const spyFindLandmark = jest.spyOn(LandmarkNavigation, "findAndFocusNextLandmark").mockReturnValue(); - const { getByTestId } = render(, { - wrapper: ({ children }) => ( - - - <>{children} - - - ), - }); - const roomList = getByTestId("room-list"); - fireEvent.keyDown(roomList, shortcut); - - expect(spyFindLandmark).toHaveBeenCalledWith(Landmark.ROOM_LIST, isPreviousLandmark); - }); -}); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemMenuView-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemMenuView-test.tsx deleted file mode 100644 index 58ab0c672b..0000000000 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemMenuView-test.tsx +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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 { mocked } from "jest-mock"; -import { render, screen } from "jest-matrix-react"; -import userEvent from "@testing-library/user-event"; - -import { - type RoomListItemMenuViewState, - useRoomListItemMenuViewModel, -} from "../../../../../../src/components/viewmodels/roomlist/RoomListItemMenuViewModel"; -import type { MatrixClient, Room } from "matrix-js-sdk/src/matrix"; -import { mkRoom, stubClient } from "../../../../../test-utils"; -import { RoomListItemMenuView } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListItemMenuView"; -import { RoomNotifState } from "../../../../../../src/RoomNotifs"; - -jest.mock("../../../../../../src/components/viewmodels/roomlist/RoomListItemMenuViewModel", () => ({ - useRoomListItemMenuViewModel: jest.fn(), -})); - -describe("", () => { - const defaultValue: RoomListItemMenuViewState = { - showMoreOptionsMenu: true, - showNotificationMenu: true, - isFavourite: true, - isLowPriority: true, - canInvite: true, - canMarkAsUnread: true, - canMarkAsRead: true, - canCopyRoomLink: true, - isNotificationAllMessage: true, - isNotificationMentionOnly: true, - isNotificationAllMessageLoud: true, - isNotificationMute: true, - copyRoomLink: jest.fn(), - markAsUnread: jest.fn(), - markAsRead: jest.fn(), - leaveRoom: jest.fn(), - toggleLowPriority: jest.fn(), - toggleFavorite: jest.fn(), - invite: jest.fn(), - setRoomNotifState: jest.fn(), - }; - - let matrixClient: MatrixClient; - let room: Room; - - beforeEach(() => { - mocked(useRoomListItemMenuViewModel).mockReturnValue(defaultValue); - matrixClient = stubClient(); - room = mkRoom(matrixClient, "room1"); - }); - - function renderMenu() { - return render(); - } - - it("should render the more options menu", () => { - const { asFragment } = renderMenu(); - expect(screen.getByRole("button", { name: "More Options" })).toBeInTheDocument(); - expect(asFragment()).toMatchSnapshot(); - }); - - it("should render the notification options menu", () => { - const { asFragment } = renderMenu(); - expect(screen.getByRole("button", { name: "Notification options" })).toBeInTheDocument(); - expect(asFragment()).toMatchSnapshot(); - }); - - it("should not render the more options menu when showMoreOptionsMenu is false", () => { - mocked(useRoomListItemMenuViewModel).mockReturnValue({ ...defaultValue, showMoreOptionsMenu: false }); - renderMenu(); - expect(screen.queryByRole("button", { name: "More Options" })).toBeNull(); - }); - - it("should not render the notification options menu when showNotificationMenu is false", () => { - mocked(useRoomListItemMenuViewModel).mockReturnValue({ ...defaultValue, showNotificationMenu: false }); - renderMenu(); - expect(screen.queryByRole("button", { name: "Notification options" })).toBeNull(); - }); - - it("should display all the buttons and have the actions linked for the more options menu", async () => { - const user = userEvent.setup(); - renderMenu(); - - const openMenu = screen.getByRole("button", { name: "More Options" }); - await user.click(openMenu); - - await user.click(screen.getByRole("menuitem", { name: "Mark as read" })); - expect(defaultValue.markAsRead).toHaveBeenCalled(); - - await user.click(openMenu); - await user.click(screen.getByRole("menuitem", { name: "Mark as unread" })); - expect(defaultValue.markAsUnread).toHaveBeenCalled(); - - await user.click(openMenu); - await user.click(screen.getByRole("menuitemcheckbox", { name: "Favourited" })); - expect(defaultValue.toggleFavorite).toHaveBeenCalled(); - - await user.click(openMenu); - await user.click(screen.getByRole("menuitemcheckbox", { name: "Low priority" })); - expect(defaultValue.toggleLowPriority).toHaveBeenCalled(); - - await user.click(openMenu); - await user.click(screen.getByRole("menuitem", { name: "Invite" })); - expect(defaultValue.invite).toHaveBeenCalled(); - - await user.click(openMenu); - await user.click(screen.getByRole("menuitem", { name: "Copy room link" })); - expect(defaultValue.copyRoomLink).toHaveBeenCalled(); - - await user.click(openMenu); - await user.click(screen.getByRole("menuitem", { name: "Leave room" })); - expect(defaultValue.leaveRoom).toHaveBeenCalled(); - }); - - it("should display all the buttons and have the actions linked for the notification options menu", async () => { - const user = userEvent.setup(); - renderMenu(); - - const openMenu = screen.getByRole("button", { name: "Notification options" }); - await user.click(openMenu); - - await user.click(screen.getByRole("menuitem", { name: "Match default settings" })); - expect(defaultValue.setRoomNotifState).toHaveBeenCalledWith(RoomNotifState.AllMessages); - - await user.click(openMenu); - await user.click(screen.getByRole("menuitem", { name: "All messages" })); - expect(defaultValue.setRoomNotifState).toHaveBeenCalledWith(RoomNotifState.AllMessagesLoud); - - await user.click(openMenu); - await user.click(screen.getByRole("menuitem", { name: "Mentions and keywords" })); - expect(defaultValue.setRoomNotifState).toHaveBeenCalledWith(RoomNotifState.MentionsOnly); - - await user.click(openMenu); - await user.click(screen.getByRole("menuitem", { name: "Mute room" })); - expect(defaultValue.setRoomNotifState).toHaveBeenCalledWith(RoomNotifState.Mute); - }); -}); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx deleted file mode 100644 index b6127e1189..0000000000 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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 { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix"; -import { render, screen, waitFor } from "jest-matrix-react"; -import userEvent from "@testing-library/user-event"; -import { mocked } from "jest-mock"; -import { CallType } from "matrix-js-sdk/src/webrtc/call"; - -import { mkRoom, stubClient, withClientContextRenderOptions } from "../../../../../test-utils"; -import { RoomListItemView } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListItemView"; -import DMRoomMap from "../../../../../../src/utils/DMRoomMap"; -import { - type RoomListItemViewState, - useRoomListItemViewModel, -} from "../../../../../../src/components/viewmodels/roomlist/RoomListItemViewModel"; -import { RoomNotificationState } from "../../../../../../src/stores/notifications/RoomNotificationState"; - -jest.mock("../../../../../../src/components/viewmodels/roomlist/RoomListItemViewModel", () => ({ - useRoomListItemViewModel: jest.fn(), -})); - -describe("", () => { - let defaultValue: RoomListItemViewState; - let matrixClient: MatrixClient; - let room: Room; - - const renderRoomListItem = (props: Partial> = {}) => { - const defaultProps = { - room, - isSelected: false, - isFocused: false, - onFocus: jest.fn(), - roomIndex: 0, - roomCount: 1, - listIsScrolling: false, - }; - - return render(, withClientContextRenderOptions(matrixClient)); - }; - - beforeEach(() => { - matrixClient = stubClient(); - room = mkRoom(matrixClient, "room1"); - - DMRoomMap.makeShared(matrixClient); - jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(undefined); - - const notificationState = new RoomNotificationState(room, false); - jest.spyOn(notificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true); - jest.spyOn(notificationState, "isNotification", "get").mockReturnValue(true); - jest.spyOn(notificationState, "count", "get").mockReturnValue(1); - - defaultValue = { - openRoom: jest.fn(), - showContextMenu: false, - showHoverMenu: false, - notificationState, - a11yLabel: "Open room room1", - isBold: false, - isVideoRoom: false, - callConnectionState: null, - callType: CallType.Video, - hasParticipantInCall: false, - name: room.name, - showNotificationDecoration: false, - messagePreview: undefined, - }; - - mocked(useRoomListItemViewModel).mockReturnValue(defaultValue); - }); - - test("should render a room item", () => { - const onClick = jest.fn(); - const { asFragment } = renderRoomListItem({ - onClick, - roomCount: 0, - }); - expect(asFragment()).toMatchSnapshot(); - }); - - test("should render a room item with a message preview", () => { - defaultValue.messagePreview = "The message looks like this"; - - const onClick = jest.fn(); - const { asFragment } = renderRoomListItem({ - onClick, - }); - expect(asFragment()).toMatchSnapshot(); - }); - - test("should call openRoom when clicked", async () => { - const user = userEvent.setup(); - renderRoomListItem(); - - await user.click(screen.getByRole("option", { name: `Open room ${room.name}` })); - expect(defaultValue.openRoom).toHaveBeenCalled(); - }); - - test("should be selected if isSelected=true", async () => { - const { asFragment } = renderRoomListItem({ - isSelected: true, - }); - - expect(screen.queryByRole("option", { name: `Open room ${room.name}` })).toHaveAttribute( - "aria-selected", - "true", - ); - expect(asFragment()).toMatchSnapshot(); - }); - - test("should display notification decoration", async () => { - mocked(useRoomListItemViewModel).mockReturnValue({ - ...defaultValue, - showNotificationDecoration: true, - }); - - const { asFragment } = renderRoomListItem(); - - expect(screen.getByTestId("notification-decoration")).toBeInTheDocument(); - expect(asFragment()).toMatchSnapshot(); - }); - - test("should not display notification decoration when hovered", async () => { - const user = userEvent.setup(); - - mocked(useRoomListItemViewModel).mockReturnValue({ - ...defaultValue, - showNotificationDecoration: true, - }); - - renderRoomListItem(); - - const listItem = screen.getByRole("option", { name: `Open room ${room.name}` }); - await user.hover(listItem); - - expect(screen.queryByRole("notification-decoration")).toBeNull(); - }); - - test("should render the context menu", async () => { - const user = userEvent.setup(); - - mocked(useRoomListItemViewModel).mockReturnValue({ - ...defaultValue, - showContextMenu: true, - }); - - renderRoomListItem(); - - const button = screen.getByRole("option", { name: `Open room ${room.name}` }); - await user.pointer([{ target: button }, { keys: "[MouseRight]", target: button }]); - await waitFor(() => expect(screen.getByRole("menu")).toBeInTheDocument()); - // Menu should close - await user.keyboard("{Escape}"); - expect(screen.queryByRole("menu")).toBeNull(); - }); -}); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx deleted file mode 100644 index 8276c7340f..0000000000 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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, { act } from "react"; -import { render, screen } from "jest-matrix-react"; -import userEvent from "@testing-library/user-event"; - -import { type RoomListViewState } from "../../../../../../src/components/viewmodels/roomlist/RoomListViewModel"; -import { RoomListPrimaryFilters } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters"; -import { FilterKey } from "../../../../../../src/stores/room-list-v3/skip-list/filters"; - -describe("", () => { - let vm: RoomListViewState; - const filterToggleMocks = [jest.fn(), jest.fn(), jest.fn()]; - - let resizeCallback: ResizeObserverCallback; - - beforeEach(() => { - // Reset mocks between tests - filterToggleMocks.forEach((mock) => mock.mockClear()); - - // Mock ResizeObserver - global.ResizeObserver = jest.fn().mockImplementation((callback) => { - resizeCallback = callback; - return { - observe: jest.fn(), - unobserve: jest.fn(), - disconnect: jest.fn(), - }; - }); - - vm = { - primaryFilters: [ - { name: "People", active: true, toggle: filterToggleMocks[0], key: FilterKey.PeopleFilter }, - { name: "Rooms", active: false, toggle: filterToggleMocks[1], key: FilterKey.RoomsFilter }, - { name: "Unreads", active: false, toggle: filterToggleMocks[2], key: FilterKey.UnreadFilter }, - ], - } as unknown as RoomListViewState; - }); - - function mockFiltersOffsetLeft() { - // Use `getByText` instead of `getByRole` to bypass the aria-hidden - jest.spyOn(screen.getByText("People"), "offsetLeft", "get").mockReturnValue(0); - jest.spyOn(screen.getByText("Rooms"), "offsetLeft", "get").mockReturnValue(30); - jest.spyOn(screen.getByText("Unreads"), "offsetLeft", "get").mockReturnValue(60); - - // @ts-ignore - act(() => resizeCallback([{ target: screen.getByRole("listbox", { name: "Room list filters" }) }])); - } - - it("should renders all filters correctly", () => { - const { asFragment } = render(); - mockFiltersOffsetLeft(); - - // Check that all filters are rendered - expect(screen.getByRole("option", { name: "People" })).toBeInTheDocument(); - expect(screen.getByRole("option", { name: "Rooms" })).toBeInTheDocument(); - expect(screen.getByRole("option", { name: "Unreads" })).toBeInTheDocument(); - - // Check that the active filter is marked as selected - expect(screen.getByRole("option", { name: "People" })).toHaveAttribute("aria-selected", "true"); - expect(screen.getByRole("option", { name: "Rooms" })).toHaveAttribute("aria-selected", "false"); - expect(screen.getByRole("option", { name: "Unreads" })).toHaveAttribute("aria-selected", "false"); - - expect(asFragment()).toMatchSnapshot(); - }); - - it("should call toggle function when a filter is clicked", async () => { - const user = userEvent.setup(); - render(); - mockFiltersOffsetLeft(); - - // Click on an inactive filter - await user.click(screen.getByRole("option", { name: "People" })); - - // Check that the toggle function was called - expect(filterToggleMocks[0]).toHaveBeenCalledTimes(1); - }); - - function makeUnreadWrapping() { - // Use `getByText` instead of `getByRole` to bypass the aria-hidden - jest.spyOn(screen.getByText("People"), "offsetLeft", "get").mockReturnValue(0); - jest.spyOn(screen.getByText("Rooms"), "offsetLeft", "get").mockReturnValue(30); - // Unreads is wrapping - jest.spyOn(screen.getByText("Unreads"), "offsetLeft", "get").mockReturnValue(0); - - // @ts-ignore - act(() => resizeCallback([{ target: screen.getByRole("listbox", { name: "Room list filters" }) }])); - } - - it("should hide or display filters if they are wrapping", async () => { - const user = userEvent.setup(); - render(); - mockFiltersOffsetLeft(); - - // No filter is wrapping, so chevron shouldn't be visible - expect(screen.queryByRole("button", { name: "Expand filter list" })).toBeNull(); - expect(screen.queryByRole("option", { name: "Unreads" })).toBeVisible(); - - makeUnreadWrapping(); - - // The Unreads filter is wrapping, it should not be visible - expect(screen.queryByRole("option", { name: "Unreads" })).toBeNull(); - // Now filters are wrapping, so chevron should be visible - await user.click(screen.getByRole("button", { name: "Expand filter list" })); - // The list is expanded, so Unreads should be visible - expect(screen.getByRole("option", { name: "Unreads" })).toBeVisible(); - }); - - it("should move the active filter if the list is collapsed and the filter is wrapping", async () => { - vm = { - primaryFilters: [ - { name: "People", active: false, toggle: filterToggleMocks[0], key: FilterKey.PeopleFilter }, - { name: "Rooms", active: false, toggle: filterToggleMocks[1], key: FilterKey.RoomsFilter }, - { name: "Unreads", active: true, toggle: filterToggleMocks[2], key: FilterKey.UnreadFilter }, - ], - } as unknown as RoomListViewState; - - const user = userEvent.setup(); - render(); - makeUnreadWrapping(); - - // Unread filter should be moved to the first position - expect(screen.getByRole("listbox", { name: "Room list filters" }).children[0]).toBe( - screen.getByRole("option", { name: "Unreads" }), - ); - - // When the list is expanded, the Unreads filter should move to its original position - await user.click(screen.getByRole("button", { name: "Expand filter list" })); - expect(screen.getByRole("listbox", { name: "Room list filters" }).children[0]).not.toEqual( - screen.getByRole("option", { name: "Unreads" }), - ); - }); - - it("should hide the filter is the previous is on the same vertical position", async () => { - render(); - mockFiltersOffsetLeft(); - - jest.spyOn(screen.getByRole("option", { name: "People" }), "offsetLeft", "get").mockReturnValue(0); - // Rooms is wrapping - jest.spyOn(screen.getByRole("option", { name: "Rooms" }), "offsetLeft", "get").mockReturnValue(0); - - // @ts-ignore - act(() => resizeCallback([{ target: screen.getByRole("listbox", { name: "Room list filters" }) }])); - - // The Unreads filter is wrapping, it should not be visible - expect(screen.queryByRole("option", { name: "Rooms" })).toBeNull(); - // Now filters are wrapping, so chevron should be visible - expect(screen.getByRole("button", { name: "Expand filter list" })).toBeVisible(); - }); -}); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListView-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListView-test.tsx deleted file mode 100644 index 0081c6f350..0000000000 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListView-test.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * 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 { mocked } from "jest-mock"; -import { render, screen } from "jest-matrix-react"; -import React from "react"; - -import { - type RoomListViewState, - useRoomListViewModel, -} from "../../../../../../src/components/viewmodels/roomlist/RoomListViewModel"; -import { RoomListView } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListView"; -import { mkRoom, stubClient } from "../../../../../test-utils"; - -jest.mock("../../../../../../src/components/viewmodels/roomlist/RoomListViewModel", () => ({ - useRoomListViewModel: jest.fn(), -})); - -describe("", () => { - const defaultValue: RoomListViewState = { - isLoadingRooms: false, - roomsResult: { spaceId: "home", rooms: [] }, - primaryFilters: [], - createRoom: jest.fn(), - createChatRoom: jest.fn(), - canCreateRoom: true, - activeIndex: undefined, - }; - const matrixClient = stubClient(); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it("should render the loading room list", () => { - mocked(useRoomListViewModel).mockReturnValue({ - ...defaultValue, - isLoadingRooms: true, - }); - - const roomList = render(); - expect(roomList.container.querySelector(".mx_RoomListSkeleton")).not.toBeNull(); - }); - - it("should render an empty room list", () => { - mocked(useRoomListViewModel).mockReturnValue(defaultValue); - - render(); - expect(screen.getByText("No chats yet")).toBeInTheDocument(); - }); - - it("should render a room list", () => { - mocked(useRoomListViewModel).mockReturnValue({ - ...defaultValue, - roomsResult: { spaceId: "home", rooms: [mkRoom(matrixClient, "testing room")] }, - }); - - render(); - expect(screen.getByRole("listbox", { name: "Room list" })).toBeInTheDocument(); - }); -}); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/EmptyRoomList-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/EmptyRoomList-test.tsx.snap deleted file mode 100644 index 140e1f366b..0000000000 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/EmptyRoomList-test.tsx.snap +++ /dev/null @@ -1,279 +0,0 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing - -exports[` should display empty state for filter favourite 1`] = ` - -
- - You don't have favourite chats yet - - - You can add a chat to your favourites in the chat settings - -
-
-`; - -exports[` should display empty state for filter people 1`] = ` - -
- - You don’t have direct chats with anyone yet - - - You can deselect filters in order to see your other chats - -
-
-`; - -exports[` should display empty state for filter rooms 1`] = ` - -
- - You’re not in any room yet - - - You can deselect filters in order to see your other chats - -
-
-`; - -exports[` should display the empty state for the invite filter 1`] = ` - -
- - You don't have any unread invites - - -
-
-`; - -exports[` should display the empty state for the low priority filter 1`] = ` - -
- - You don't have any low priority rooms - - -
-
-`; - -exports[` should display the empty state for the mention filter 1`] = ` - -
- - You don't have any unread mentions - - -
-
-`; - -exports[` should display the empty state for the unread filter 1`] = ` - -
- - Congrats! You don’t have any unread messages - - -
-
-`; - -exports[` should not render the new room button if the user doesn't have the rights to create a room 1`] = ` - -
- - No chats yet - - - Get started by messaging someone - -
- -
-
-
-`; - -exports[` should render the default placeholder when there is no filter 1`] = ` - -
- - No chats yet - - - Get started by messaging someone or by creating a room - -
- - -
-
-
-`; diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap deleted file mode 100644 index eb833e64fa..0000000000 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap +++ /dev/null @@ -1,1255 +0,0 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing - -exports[` should render a room list 1`] = ` - -
-
-
-
- - - -
-
-
-
- - - -
-
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
-`; diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemMenuView-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemMenuView-test.tsx.snap deleted file mode 100644 index 8842b91e6f..0000000000 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemMenuView-test.tsx.snap +++ /dev/null @@ -1,155 +0,0 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing - -exports[` should render the more options menu 1`] = ` - -
- - -
-
-`; - -exports[` should render the notification options menu 1`] = ` - -
- - -
-
-`; diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap deleted file mode 100644 index f46588370f..0000000000 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap +++ /dev/null @@ -1,234 +0,0 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing - -exports[` should be selected if isSelected=true 1`] = ` - - - -`; - -exports[` should display notification decoration 1`] = ` - - - -`; - -exports[` should render a room item 1`] = ` - - - -`; - -exports[` should render a room item with a message preview 1`] = ` - - - -`; diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPrimaryFilters-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPrimaryFilters-test.tsx.snap deleted file mode 100644 index ec71f70c95..0000000000 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPrimaryFilters-test.tsx.snap +++ /dev/null @@ -1,47 +0,0 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing - -exports[` should renders all filters correctly 1`] = ` - -
-
- - - -
-
-
-`;