@@ -9,8 +9,9 @@ Please see LICENSE files in the repository root for full details.
9
9
*/
10
10
11
11
import React , { ComponentProps , ReactNode } from "react" ;
12
- import { MatrixEvent , RoomMember , EventType } from "matrix-js-sdk/src/matrix" ;
12
+ import { EventType , MatrixEvent , MatrixEventEvent , RoomMember } from "matrix-js-sdk/src/matrix" ;
13
13
import { KnownMembership } from "matrix-js-sdk/src/types" ;
14
+ import { throttle } from "lodash" ;
14
15
15
16
import { _t } from "../../../languageHandler" ;
16
17
import { formatList } from "../../../utils/FormattingUtils" ;
@@ -22,6 +23,8 @@ import { Layout } from "../../../settings/enums/Layout";
22
23
import RightPanelStore from "../../../stores/right-panel/RightPanelStore" ;
23
24
import AccessibleButton from "./AccessibleButton" ;
24
25
import RoomContext from "../../../contexts/RoomContext" ;
26
+ import { arrayHasDiff } from "../../../utils/arrays.ts" ;
27
+ import { objectHasDiff } from "../../../utils/objects.ts" ;
25
28
26
29
const onPinnedMessagesClick = ( ) : void => {
27
30
RightPanelStore . instance . setCard ( { phase : RightPanelPhases . PinnedMessages } , false ) ;
@@ -69,9 +72,14 @@ enum TransitionType {
69
72
70
73
const SEP = "," ;
71
74
72
- export default class EventListSummary extends React . Component <
73
- IProps & Required < Pick < IProps , "summaryLength" | "threshold" | "avatarsMaxLength" | "layout" > >
74
- > {
75
+ type Props = IProps & Required < Pick < IProps , "summaryLength" | "threshold" | "avatarsMaxLength" | "layout" > > ;
76
+
77
+ interface State {
78
+ userEvents : Record < string , IUserEvents [ ] > ;
79
+ summaryMembers : RoomMember [ ] ;
80
+ }
81
+
82
+ export default class EventListSummary extends React . Component < Props , State > {
75
83
public static contextType = RoomContext ;
76
84
declare public context : React . ContextType < typeof RoomContext > ;
77
85
@@ -82,15 +90,122 @@ export default class EventListSummary extends React.Component<
82
90
layout : Layout . Group ,
83
91
} ;
84
92
85
- public shouldComponentUpdate ( nextProps : IProps ) : boolean {
93
+ public constructor ( props : Props ) {
94
+ super ( props ) ;
95
+
96
+ this . state = this . generateState ( ) ;
97
+ }
98
+
99
+ private generateState ( ) : State {
100
+ const eventsToRender = this . props . events ;
101
+
102
+ // Map user IDs to latest Avatar Member. ES6 Maps are ordered by when the key was created,
103
+ // so this works perfectly for us to match event order whilst storing the latest Avatar Member
104
+ const latestUserAvatarMember = new Map < string , RoomMember > ( ) ;
105
+
106
+ // Object mapping user IDs to an array of IUserEvents
107
+ const userEvents : Record < string , IUserEvents [ ] > = { } ;
108
+ eventsToRender . forEach ( ( e , index ) => {
109
+ const type = e . getType ( ) ;
110
+
111
+ let userKey = e . getSender ( ) ! ;
112
+ if ( e . isState ( ) && type === EventType . RoomThirdPartyInvite ) {
113
+ userKey = e . getContent ( ) . display_name ;
114
+ } else if ( e . isState ( ) && type === EventType . RoomMember ) {
115
+ userKey = e . getStateKey ( ) ! ;
116
+ } else if ( e . isRedacted ( ) && e . getUnsigned ( ) ?. redacted_because ) {
117
+ userKey = e . getUnsigned ( ) . redacted_because ! . sender ;
118
+ }
119
+
120
+ // Initialise a user's events
121
+ if ( ! userEvents [ userKey ] ) {
122
+ userEvents [ userKey ] = [ ] ;
123
+ }
124
+
125
+ let displayName = userKey ;
126
+ if ( e . isRedacted ( ) ) {
127
+ const sender = this . context ?. room ?. getMember ( userKey ) ;
128
+ if ( sender ) {
129
+ displayName = sender . name ;
130
+ latestUserAvatarMember . set ( userKey , sender ) ;
131
+ }
132
+ } else if ( e . target && TARGET_AS_DISPLAY_NAME_EVENTS . includes ( type as EventType ) ) {
133
+ displayName = e . target . name ;
134
+ latestUserAvatarMember . set ( userKey , e . target ) ;
135
+ } else if ( e . sender && type !== EventType . RoomThirdPartyInvite ) {
136
+ displayName = e . sender . name ;
137
+ latestUserAvatarMember . set ( userKey , e . sender ) ;
138
+ }
139
+
140
+ userEvents [ userKey ] . push ( {
141
+ mxEvent : e ,
142
+ displayName,
143
+ index : index ,
144
+ } ) ;
145
+ } ) ;
146
+
147
+ return {
148
+ userEvents,
149
+ summaryMembers : Array . from ( latestUserAvatarMember . values ( ) ) ,
150
+ } ;
151
+ }
152
+
153
+ public componentDidMount ( ) : void {
154
+ this . bindSentinelListeners ( this . props . events ) ;
155
+ }
156
+
157
+ public componentDidUpdate ( prevProps : Readonly < Props > ) : void {
158
+ if ( prevProps . events !== this . props . events ) {
159
+ this . unbindSentinelListeners ( prevProps . events ) ;
160
+ this . bindSentinelListeners ( this . props . events ) ;
161
+ this . setState ( this . generateState ( ) ) ;
162
+ }
163
+ }
164
+
165
+ public componentWillUnmount ( ) : void {
166
+ this . unbindSentinelListeners ( this . props . events ) ;
167
+ }
168
+
169
+ private bindSentinelListeners ( events : MatrixEvent [ ] ) : void {
170
+ for ( const event of events ) {
171
+ event . on ( MatrixEventEvent . SentinelUpdated , this . onEventSentinelUpdated ) ;
172
+ }
173
+ }
174
+
175
+ private unbindSentinelListeners ( events : MatrixEvent [ ] ) : void {
176
+ for ( const event of events ) {
177
+ event . on ( MatrixEventEvent . SentinelUpdated , this . onEventSentinelUpdated ) ;
178
+ }
179
+ }
180
+
181
+ private onEventSentinelUpdated = throttle (
182
+ ( ) : void => {
183
+ console . log ( "@@ SENTINEL UPDATED" ) ;
184
+ this . setState ( this . generateState ( ) ) ;
185
+ } ,
186
+ 500 ,
187
+ { leading : true , trailing : true } ,
188
+ ) ;
189
+
190
+ public shouldComponentUpdate ( nextProps : Props , nextState : State ) : boolean {
86
191
// Update if
87
192
// - The number of summarised events has changed
88
193
// - or if the summary is about to toggle to become collapsed
89
194
// - or if there are fewEvents, meaning the child eventTiles are shown as-is
195
+ // - or if the summary members have changed
196
+ // - or if the one of IUserEvents within userEvents have changed
90
197
return (
91
198
nextProps . events . length !== this . props . events . length ||
92
199
nextProps . events . length < this . props . threshold ||
93
- nextProps . layout !== this . props . layout
200
+ nextProps . layout !== this . props . layout ||
201
+ arrayHasDiff ( nextState . summaryMembers , this . state . summaryMembers ) ||
202
+ arrayHasDiff ( Object . values ( nextState . userEvents ) , Object . values ( this . state . userEvents ) ) ||
203
+ Object . keys ( nextState . userEvents ) . length !== Object . keys ( this . state . userEvents ) . length ||
204
+ Object . keys ( nextState . userEvents ) . some ( ( userId ) =>
205
+ nextState . userEvents [ userId ] . some ( ( event , i ) =>
206
+ objectHasDiff ( event , this . state . userEvents [ userId ] ?. [ i ] ?? { } ) ,
207
+ ) ,
208
+ )
94
209
) ;
95
210
}
96
211
@@ -492,54 +607,7 @@ export default class EventListSummary extends React.Component<
492
607
}
493
608
494
609
public render ( ) : React . ReactNode {
495
- const eventsToRender = this . props . events ;
496
-
497
- // Map user IDs to latest Avatar Member. ES6 Maps are ordered by when the key was created,
498
- // so this works perfectly for us to match event order whilst storing the latest Avatar Member
499
- const latestUserAvatarMember = new Map < string , RoomMember > ( ) ;
500
-
501
- // Object mapping user IDs to an array of IUserEvents
502
- const userEvents : Record < string , IUserEvents [ ] > = { } ;
503
- eventsToRender . forEach ( ( e , index ) => {
504
- const type = e . getType ( ) ;
505
-
506
- let userKey = e . getSender ( ) ! ;
507
- if ( e . isState ( ) && type === EventType . RoomThirdPartyInvite ) {
508
- userKey = e . getContent ( ) . display_name ;
509
- } else if ( e . isState ( ) && type === EventType . RoomMember ) {
510
- userKey = e . getStateKey ( ) ! ;
511
- } else if ( e . isRedacted ( ) && e . getUnsigned ( ) ?. redacted_because ) {
512
- userKey = e . getUnsigned ( ) . redacted_because ! . sender ;
513
- }
514
-
515
- // Initialise a user's events
516
- if ( ! userEvents [ userKey ] ) {
517
- userEvents [ userKey ] = [ ] ;
518
- }
519
-
520
- let displayName = userKey ;
521
- if ( e . isRedacted ( ) ) {
522
- const sender = this . context ?. room ?. getMember ( userKey ) ;
523
- if ( sender ) {
524
- displayName = sender . name ;
525
- latestUserAvatarMember . set ( userKey , sender ) ;
526
- }
527
- } else if ( e . target && TARGET_AS_DISPLAY_NAME_EVENTS . includes ( type as EventType ) ) {
528
- displayName = e . target . name ;
529
- latestUserAvatarMember . set ( userKey , e . target ) ;
530
- } else if ( e . sender && type !== EventType . RoomThirdPartyInvite ) {
531
- displayName = e . sender . name ;
532
- latestUserAvatarMember . set ( userKey , e . sender ) ;
533
- }
534
-
535
- userEvents [ userKey ] . push ( {
536
- mxEvent : e ,
537
- displayName,
538
- index : index ,
539
- } ) ;
540
- } ) ;
541
-
542
- const aggregate = this . getAggregate ( userEvents ) ;
610
+ const aggregate = this . getAggregate ( this . state . userEvents ) ;
543
611
544
612
// Sort types by order of lowest event index within sequence
545
613
const orderedTransitionSequences = Object . keys ( aggregate . names ) . sort (
@@ -554,7 +622,7 @@ export default class EventListSummary extends React.Component<
554
622
onToggle = { this . props . onToggle }
555
623
startExpanded = { this . props . startExpanded }
556
624
children = { this . props . children }
557
- summaryMembers = { [ ... latestUserAvatarMember . values ( ) ] }
625
+ summaryMembers = { this . state . summaryMembers }
558
626
layout = { this . props . layout }
559
627
summaryText = { this . generateSummary ( aggregate . names , orderedTransitionSequences ) }
560
628
/>
0 commit comments