@@ -76,6 +76,14 @@ interface IProps {
76
76
// a timeline representing. If it has a room, we maintain RRs etc for
77
77
// that room.
78
78
timelineSet : EventTimelineSet ;
79
+ // overlay events from a second timelineset on the main timeline
80
+ // added to support virtual rooms
81
+ // events from the overlay timeline set will be added by localTimestamp
82
+ // into the main timeline
83
+ // back paging not yet supported
84
+ overlayTimelineSet ?: EventTimelineSet ;
85
+ // filter events from overlay timeline
86
+ overlayTimelineSetFilter ?: ( event : MatrixEvent ) => boolean ;
79
87
showReadReceipts ?: boolean ;
80
88
// Enable managing RRs and RMs. These require the timelineSet to have a room.
81
89
manageReadReceipts ?: boolean ;
@@ -236,14 +244,15 @@ class TimelinePanel extends React.Component<IProps, IState> {
236
244
private readonly messagePanel = createRef < MessagePanel > ( ) ;
237
245
private readonly dispatcherRef : string ;
238
246
private timelineWindow ?: TimelineWindow ;
247
+ private overlayTimelineWindow ?: TimelineWindow ;
239
248
private unmounted = false ;
240
- private readReceiptActivityTimer : Timer ;
241
- private readMarkerActivityTimer : Timer ;
249
+ private readReceiptActivityTimer : Timer | null = null ;
250
+ private readMarkerActivityTimer : Timer | null = null ;
242
251
243
252
// A map of <callId, LegacyCallEventGrouper>
244
253
private callEventGroupers = new Map < string , LegacyCallEventGrouper > ( ) ;
245
254
246
- constructor ( props , context ) {
255
+ constructor ( props : IProps , context : React . ContextType < typeof RoomContext > ) {
247
256
super ( props , context ) ;
248
257
this . context = context ;
249
258
@@ -642,7 +651,12 @@ class TimelinePanel extends React.Component<IProps, IState> {
642
651
data : IRoomTimelineData ,
643
652
) : void => {
644
653
// ignore events for other timeline sets
645
- if ( data . timeline . getTimelineSet ( ) !== this . props . timelineSet ) return ;
654
+ if (
655
+ data . timeline . getTimelineSet ( ) !== this . props . timelineSet
656
+ && data . timeline . getTimelineSet ( ) !== this . props . overlayTimelineSet
657
+ ) {
658
+ return ;
659
+ }
646
660
647
661
if ( ! Thread . hasServerSideSupport && this . context . timelineRenderingType === TimelineRenderingType . Thread ) {
648
662
if ( toStartOfTimeline && ! this . state . canBackPaginate ) {
@@ -680,21 +694,27 @@ class TimelinePanel extends React.Component<IProps, IState> {
680
694
// timeline window.
681
695
//
682
696
// see https://github.com/vector-im/vector-web/issues/1035
683
- this . timelineWindow . paginate ( EventTimeline . FORWARDS , 1 , false ) . then ( ( ) => {
684
- if ( this . unmounted ) { return ; }
685
-
686
- const { events, liveEvents, firstVisibleEventIndex } = this . getEvents ( ) ;
687
- this . buildLegacyCallEventGroupers ( events ) ;
688
- const lastLiveEvent = liveEvents [ liveEvents . length - 1 ] ;
689
-
690
- const updatedState : Partial < IState > = {
691
- events,
692
- liveEvents,
693
- firstVisibleEventIndex,
694
- } ;
697
+ this . timelineWindow ! . paginate ( EventTimeline . FORWARDS , 1 , false )
698
+ . then ( ( ) => {
699
+ if ( this . overlayTimelineWindow ) {
700
+ return this . overlayTimelineWindow . paginate ( EventTimeline . FORWARDS , 1 , false ) ;
701
+ }
702
+ } )
703
+ . then ( ( ) => {
704
+ if ( this . unmounted ) { return ; }
705
+
706
+ const { events, liveEvents, firstVisibleEventIndex } = this . getEvents ( ) ;
707
+ this . buildLegacyCallEventGroupers ( events ) ;
708
+ const lastLiveEvent = liveEvents [ liveEvents . length - 1 ] ;
709
+
710
+ const updatedState : Partial < IState > = {
711
+ events,
712
+ liveEvents,
713
+ firstVisibleEventIndex,
714
+ } ;
695
715
696
- let callRMUpdated ;
697
- if ( this . props . manageReadMarkers ) {
716
+ let callRMUpdated = false ;
717
+ if ( this . props . manageReadMarkers ) {
698
718
// when a new event arrives when the user is not watching the
699
719
// window, but the window is in its auto-scroll mode, make sure the
700
720
// read marker is visible.
@@ -703,28 +723,28 @@ class TimelinePanel extends React.Component<IProps, IState> {
703
723
// read-marker when a remote echo of an event we have just sent takes
704
724
// more than the timeout on userActiveRecently.
705
725
//
706
- const myUserId = MatrixClientPeg . get ( ) . credentials . userId ;
707
- callRMUpdated = false ;
708
- if ( ev . getSender ( ) !== myUserId && ! UserActivity . sharedInstance ( ) . userActiveRecently ( ) ) {
709
- updatedState . readMarkerVisible = true ;
710
- } else if ( lastLiveEvent && this . getReadMarkerPosition ( ) === 0 ) {
726
+ const myUserId = MatrixClientPeg . get ( ) . credentials . userId ;
727
+ callRMUpdated = false ;
728
+ if ( ev . getSender ( ) !== myUserId && ! UserActivity . sharedInstance ( ) . userActiveRecently ( ) ) {
729
+ updatedState . readMarkerVisible = true ;
730
+ } else if ( lastLiveEvent && this . getReadMarkerPosition ( ) === 0 ) {
711
731
// we know we're stuckAtBottom, so we can advance the RM
712
732
// immediately, to save a later render cycle
713
733
714
- this . setReadMarker ( lastLiveEvent . getId ( ) , lastLiveEvent . getTs ( ) , true ) ;
715
- updatedState . readMarkerVisible = false ;
716
- updatedState . readMarkerEventId = lastLiveEvent . getId ( ) ;
717
- callRMUpdated = true ;
734
+ this . setReadMarker ( lastLiveEvent . getId ( ) ?? null , lastLiveEvent . getTs ( ) , true ) ;
735
+ updatedState . readMarkerVisible = false ;
736
+ updatedState . readMarkerEventId = lastLiveEvent . getId ( ) ;
737
+ callRMUpdated = true ;
738
+ }
718
739
}
719
- }
720
740
721
- this . setState < null > ( updatedState , ( ) => {
722
- this . messagePanel . current ?. updateTimelineMinHeight ( ) ;
723
- if ( callRMUpdated ) {
724
- this . props . onReadMarkerUpdated ?.( ) ;
725
- }
741
+ this . setState ( updatedState as IState , ( ) => {
742
+ this . messagePanel . current ?. updateTimelineMinHeight ( ) ;
743
+ if ( callRMUpdated ) {
744
+ this . props . onReadMarkerUpdated ?.( ) ;
745
+ }
746
+ } ) ;
726
747
} ) ;
727
- } ) ;
728
748
} ;
729
749
730
750
private onRoomTimelineReset = ( room : Room , timelineSet : EventTimelineSet ) : void => {
@@ -735,7 +755,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
735
755
}
736
756
} ;
737
757
738
- public canResetTimeline = ( ) => this . messagePanel ?. current . isAtBottom ( ) ;
758
+ public canResetTimeline = ( ) => this . messagePanel ?. current ? .isAtBottom ( ) ;
739
759
740
760
private onRoomRedaction = ( ev : MatrixEvent , room : Room ) : void => {
741
761
if ( this . unmounted ) return ;
@@ -1337,6 +1357,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
1337
1357
private loadTimeline ( eventId ?: string , pixelOffset ?: number , offsetBase ?: number , scrollIntoView = true ) : void {
1338
1358
const cli = MatrixClientPeg . get ( ) ;
1339
1359
this . timelineWindow = new TimelineWindow ( cli , this . props . timelineSet , { windowLimit : this . props . timelineCap } ) ;
1360
+ this . overlayTimelineWindow = this . props . overlayTimelineSet
1361
+ ? new TimelineWindow ( cli , this . props . overlayTimelineSet , { windowLimit : this . props . timelineCap } )
1362
+ : undefined ;
1340
1363
1341
1364
const onLoaded = ( ) => {
1342
1365
if ( this . unmounted ) return ;
@@ -1351,8 +1374,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
1351
1374
this . advanceReadMarkerPastMyEvents ( ) ;
1352
1375
1353
1376
this . setState ( {
1354
- canBackPaginate : this . timelineWindow . canPaginate ( EventTimeline . BACKWARDS ) ,
1355
- canForwardPaginate : this . timelineWindow . canPaginate ( EventTimeline . FORWARDS ) ,
1377
+ canBackPaginate : ! ! this . timelineWindow ? .canPaginate ( EventTimeline . BACKWARDS ) ,
1378
+ canForwardPaginate : ! ! this . timelineWindow ? .canPaginate ( EventTimeline . FORWARDS ) ,
1356
1379
timelineLoading : false ,
1357
1380
} , ( ) => {
1358
1381
// initialise the scroll state of the message panel
@@ -1433,12 +1456,19 @@ class TimelinePanel extends React.Component<IProps, IState> {
1433
1456
// if we've got an eventId, and the timeline exists, we can skip
1434
1457
// the promise tick.
1435
1458
this . timelineWindow . load ( eventId , INITIAL_SIZE ) ;
1459
+ this . overlayTimelineWindow ?. load ( undefined , INITIAL_SIZE ) ;
1436
1460
// in this branch this method will happen in sync time
1437
1461
onLoaded ( ) ;
1438
1462
return ;
1439
1463
}
1440
1464
1441
- const prom = this . timelineWindow . load ( eventId , INITIAL_SIZE ) ;
1465
+ const prom = this . timelineWindow . load ( eventId , INITIAL_SIZE ) . then ( async ( ) => {
1466
+ if ( this . overlayTimelineWindow ) {
1467
+ // @TODO (kerrya) use timestampToEvent to load the overlay timeline
1468
+ // with more correct position when main TL eventId is truthy
1469
+ await this . overlayTimelineWindow . load ( undefined , INITIAL_SIZE ) ;
1470
+ }
1471
+ } ) ;
1442
1472
this . buildLegacyCallEventGroupers ( ) ;
1443
1473
this . setState ( {
1444
1474
events : [ ] ,
@@ -1471,7 +1501,23 @@ class TimelinePanel extends React.Component<IProps, IState> {
1471
1501
1472
1502
// get the list of events from the timeline window and the pending event list
1473
1503
private getEvents ( ) : Pick < IState , "events" | "liveEvents" | "firstVisibleEventIndex" > {
1474
- const events : MatrixEvent [ ] = this . timelineWindow . getEvents ( ) ;
1504
+ const mainEvents : MatrixEvent [ ] = this . timelineWindow ?. getEvents ( ) || [ ] ;
1505
+ const eventFilter = this . props . overlayTimelineSetFilter || Boolean ;
1506
+ const overlayEvents = this . overlayTimelineWindow ?. getEvents ( ) . filter ( eventFilter ) || [ ] ;
1507
+
1508
+ // maintain the main timeline event order as returned from the HS
1509
+ // merge overlay events at approximately the right position based on local timestamp
1510
+ const events = overlayEvents . reduce ( ( acc : MatrixEvent [ ] , overlayEvent : MatrixEvent ) => {
1511
+ // find the first main tl event with a later timestamp
1512
+ const index = acc . findIndex ( event => event . localTimestamp > overlayEvent . localTimestamp ) ;
1513
+ // insert overlay event into timeline at approximately the right place
1514
+ if ( index > - 1 ) {
1515
+ acc . splice ( index , 0 , overlayEvent ) ;
1516
+ } else {
1517
+ acc . push ( overlayEvent ) ;
1518
+ }
1519
+ return acc ;
1520
+ } , [ ...mainEvents ] ) ;
1475
1521
1476
1522
// `arrayFastClone` performs a shallow copy of the array
1477
1523
// we want the last event to be decrypted first but displayed last
@@ -1483,20 +1529,20 @@ class TimelinePanel extends React.Component<IProps, IState> {
1483
1529
client . decryptEventIfNeeded ( event ) ;
1484
1530
} ) ;
1485
1531
1486
- const firstVisibleEventIndex = this . checkForPreJoinUISI ( events ) ;
1532
+ const firstVisibleEventIndex = this . checkForPreJoinUISI ( mainEvents ) ;
1487
1533
1488
1534
// Hold onto the live events separately. The read receipt and read marker
1489
1535
// should use this list, so that they don't advance into pending events.
1490
1536
const liveEvents = [ ...events ] ;
1491
1537
1492
1538
// if we're at the end of the live timeline, append the pending events
1493
- if ( ! this . timelineWindow . canPaginate ( EventTimeline . FORWARDS ) ) {
1539
+ if ( ! this . timelineWindow ? .canPaginate ( EventTimeline . FORWARDS ) ) {
1494
1540
const pendingEvents = this . props . timelineSet . getPendingEvents ( ) ;
1495
1541
events . push ( ...pendingEvents . filter ( event => {
1496
1542
const {
1497
1543
shouldLiveInRoom,
1498
1544
threadId,
1499
- } = this . props . timelineSet . room . eventShouldLiveIn ( event , pendingEvents ) ;
1545
+ } = this . props . timelineSet . room ! . eventShouldLiveIn ( event , pendingEvents ) ;
1500
1546
1501
1547
if ( this . context . timelineRenderingType === TimelineRenderingType . Thread ) {
1502
1548
return threadId === this . context . threadId ;
0 commit comments