@@ -52,13 +52,15 @@ interface ViewEventListProps {
5252 events : ReadonlyArray < CollectedEvent > ;
5353 filteredEvents : ReadonlyArray < CollectedEvent > ;
5454 selectedEvent : CollectedEvent | undefined ;
55+ selectedEventIds : Set < string > ;
5556 isPaused : boolean ;
5657
5758 contextMenuBuilder : ViewEventContextMenuBuilder ;
5859 uiStore : UiStore ;
5960
6061 moveSelection : ( distance : number ) => void ;
6162 onSelected : ( event : CollectedEvent | undefined ) => void ;
63+ onEventToggled : ( event : CollectedEvent ) => void ;
6264}
6365
6466const ListContainer = styled . div < { role : 'table' } > `
@@ -253,6 +255,18 @@ const EventListRow = styled.div<{ role: 'row' }>`
253255 }
254256 }
255257
258+ &.multi-selected {
259+ background-color: ${ p => p . theme . highlightBackground } ;
260+ border: 2px solid ${ p => p . theme . popColor } ;
261+ color: ${ p => p . theme . highlightColor } ;
262+ fill: ${ p => p . theme . highlightColor } ;
263+ box-sizing: border-box;
264+ * {
265+ color: ${ p => p . theme . highlightColor } ;
266+ fill: ${ p => p . theme . highlightColor } ;
267+ }
268+ }
269+
256270 &:focus {
257271 outline: thin dotted ${ p => p . theme . popColor } ;
258272 }
@@ -330,22 +344,25 @@ export const TableHeaderRow = styled.div<{ role: 'row' }>`
330344interface EventRowProps extends ListChildComponentProps {
331345 data : {
332346 selectedEvent : CollectedEvent | undefined ;
347+ selectedEventIds : Set < string > ;
333348 events : ReadonlyArray < CollectedEvent > ;
334349 contextMenuBuilder : ViewEventContextMenuBuilder ;
335350 }
336351}
337352
338353const EventRow = observer ( ( props : EventRowProps ) => {
339354 const { index, style } = props ;
340- const { events, selectedEvent, contextMenuBuilder } = props . data ;
355+ const { events, selectedEvent, selectedEventIds , contextMenuBuilder } = props . data ;
341356 const event = events [ index ] ;
342357
343358 const isSelected = ( selectedEvent === event ) ;
359+ const isMultiSelected = selectedEventIds . has ( event . id ) ;
344360
345361 if ( event . isTlsFailure ( ) || event . isTlsTunnel ( ) ) {
346362 return < TlsRow
347363 index = { index }
348364 isSelected = { isSelected }
365+ isMultiSelected = { isMultiSelected }
349366 style = { style }
350367 tlsEvent = { event }
351368 /> ;
@@ -354,6 +371,7 @@ const EventRow = observer((props: EventRowProps) => {
354371 return < BuiltInApiRow
355372 index = { index }
356373 isSelected = { isSelected }
374+ isMultiSelected = { isMultiSelected }
357375 style = { style }
358376 exchange = { event }
359377 contextMenuBuilder = { contextMenuBuilder }
@@ -362,6 +380,7 @@ const EventRow = observer((props: EventRowProps) => {
362380 return < ExchangeRow
363381 index = { index }
364382 isSelected = { isSelected }
383+ isMultiSelected = { isMultiSelected }
365384 style = { style }
366385 exchange = { event }
367386 contextMenuBuilder = { contextMenuBuilder }
@@ -371,13 +390,15 @@ const EventRow = observer((props: EventRowProps) => {
371390 return < RTCConnectionRow
372391 index = { index }
373392 isSelected = { isSelected }
393+ isMultiSelected = { isMultiSelected }
374394 style = { style }
375395 event = { event }
376396 /> ;
377397 } else if ( event . isRTCDataChannel ( ) || event . isRTCMediaTrack ( ) ) {
378398 return < RTCStreamRow
379399 index = { index }
380400 isSelected = { isSelected }
401+ isMultiSelected = { isMultiSelected }
381402 style = { style }
382403 event = { event }
383404 /> ;
@@ -389,12 +410,14 @@ const EventRow = observer((props: EventRowProps) => {
389410const ExchangeRow = inject ( 'uiStore' ) ( observer ( ( {
390411 index,
391412 isSelected,
413+ isMultiSelected,
392414 style,
393415 exchange,
394416 contextMenuBuilder
395417} : {
396418 index : number ,
397419 isSelected : boolean ,
420+ isMultiSelected : boolean ,
398421 style : { } ,
399422 exchange : HttpExchange ,
400423 contextMenuBuilder : ViewEventContextMenuBuilder
@@ -406,6 +429,8 @@ const ExchangeRow = inject('uiStore')(observer(({
406429 category
407430 } = exchange ;
408431
432+ const className = isSelected ? 'selected' : isMultiSelected ? 'multi-selected' : '' ;
433+
409434 return < TrafficEventListRow
410435 role = "row"
411436 aria-label = {
@@ -431,7 +456,7 @@ const ExchangeRow = inject('uiStore')(observer(({
431456 data-event-id = { exchange . id }
432457 tabIndex = { isSelected ? 0 : - 1 }
433458 onContextMenu = { contextMenuBuilder . getContextMenuCallback ( exchange ) }
434- className = { isSelected ? 'selected' : '' }
459+ className = { className }
435460 style = { style }
436461 >
437462 < RowPin aria-label = { pinned ? 'Pinned' : undefined } pinned = { pinned } />
@@ -503,16 +528,20 @@ const ConnectedSpinnerIcon = styled(Icon).attrs(() => ({
503528const RTCConnectionRow = observer ( ( {
504529 index,
505530 isSelected,
531+ isMultiSelected,
506532 style,
507533 event
508534} : {
509535 index : number ,
510536 isSelected : boolean ,
537+ isMultiSelected : boolean ,
511538 style : { } ,
512539 event : RTCConnection
513540} ) => {
514541 const { category, pinned } = event ;
515542
543+ const className = isSelected ? 'selected' : isMultiSelected ? 'multi-selected' : '' ;
544+
516545 return < TrafficEventListRow
517546 role = "row"
518547 aria-label = {
@@ -530,7 +559,7 @@ const RTCConnectionRow = observer(({
530559 data-event-id = { event . id }
531560 tabIndex = { isSelected ? 0 : - 1 }
532561
533- className = { isSelected ? 'selected' : '' }
562+ className = { className }
534563 style = { style }
535564 >
536565 < RowPin pinned = { pinned } />
@@ -557,16 +586,20 @@ const RTCConnectionRow = observer(({
557586const RTCStreamRow = observer ( ( {
558587 index,
559588 isSelected,
589+ isMultiSelected,
560590 style,
561591 event
562592} : {
563593 index : number ,
564594 isSelected : boolean ,
595+ isMultiSelected : boolean ,
565596 style : { } ,
566597 event : RTCStream
567598} ) => {
568599 const { category, pinned } = event ;
569600
601+ const className = isSelected ? 'selected' : isMultiSelected ? 'multi-selected' : '' ;
602+
570603 return < TrafficEventListRow
571604 role = "row"
572605 aria-label = {
@@ -598,7 +631,7 @@ const RTCStreamRow = observer(({
598631 data-event-id = { event . id }
599632 tabIndex = { isSelected ? 0 : - 1 }
600633
601- className = { isSelected ? 'selected' : '' }
634+ className = { className }
602635 style = { style }
603636 >
604637 < RowPin pinned = { pinned } />
@@ -648,6 +681,7 @@ const BuiltInApiRow = observer((p: {
648681 index : number ,
649682 exchange : HttpExchange ,
650683 isSelected : boolean ,
684+ isMultiSelected : boolean ,
651685 style : { } ,
652686 contextMenuBuilder : ViewEventContextMenuBuilder
653687} ) => {
@@ -668,6 +702,8 @@ const BuiltInApiRow = observer((p: {
668702 . map ( param => `${ param . name } =${ JSON . stringify ( param . value ) } ` )
669703 . join ( ', ' ) ;
670704
705+ const className = p . isSelected ? 'selected' : p . isMultiSelected ? 'multi-selected' : '' ;
706+
671707 return < TrafficEventListRow
672708 role = "row"
673709 aria-label = {
@@ -688,7 +724,7 @@ const BuiltInApiRow = observer((p: {
688724 tabIndex = { p . isSelected ? 0 : - 1 }
689725
690726 onContextMenu = { p . contextMenuBuilder . getContextMenuCallback ( p . exchange ) }
691- className = { p . isSelected ? 'selected' : '' }
727+ className = { className }
692728 style = { p . style }
693729 >
694730 < RowPin pinned = { pinned } />
@@ -712,6 +748,7 @@ const TlsRow = observer((p: {
712748 index : number ,
713749 tlsEvent : FailedTlsConnection | TlsTunnel ,
714750 isSelected : boolean ,
751+ isMultiSelected : boolean ,
715752 style : { }
716753} ) => {
717754 const { tlsEvent } = p ;
@@ -728,14 +765,16 @@ const TlsRow = observer((p: {
728765
729766 const connectionTarget = tlsEvent . upstreamHostname || 'unknown domain' ;
730767
768+ const className = p . isSelected ? 'selected' : p . isMultiSelected ? 'multi-selected' : '' ;
769+
731770 return < TlsListRow
732771 role = "row"
733772 aria-label = { `${ description } connection to ${ connectionTarget } ` }
734773 aria-rowindex = { p . index + 1 }
735774 data-event-id = { tlsEvent . id }
736775 tabIndex = { p . isSelected ? 0 : - 1 }
737776
738- className = { p . isSelected ? 'selected' : '' }
777+ className = { className }
739778 style = { p . style }
740779 >
741780 {
@@ -761,6 +800,7 @@ export class ViewEventList extends React.Component<ViewEventListProps> {
761800 @computed get listItemData ( ) : EventRowProps [ 'data' ] {
762801 return {
763802 selectedEvent : this . props . selectedEvent ,
803+ selectedEventIds : this . props . selectedEventIds ,
764804 events : this . props . filteredEvents ,
765805 contextMenuBuilder : this . props . contextMenuBuilder
766806 } ;
@@ -1002,11 +1042,18 @@ export class ViewEventList extends React.Component<ViewEventListProps> {
10021042
10031043 const eventIndex = parseInt ( ariaRowIndex , 10 ) - 1 ;
10041044 const event = this . props . filteredEvents [ eventIndex ] ;
1005- if ( event !== this . props . selectedEvent ) {
1006- this . onEventSelected ( eventIndex ) ;
1045+
1046+ // Handle multi-selection with Ctrl+Click (or Cmd+Click on Mac)
1047+ if ( mouseEvent . ctrlKey || mouseEvent . metaKey ) {
1048+ this . onEventToggled ( event ) ;
10071049 } else {
1008- // Clicking the selected row deselects it
1009- this . onEventDeselected ( ) ;
1050+ // Normal single selection behavior
1051+ if ( event !== this . props . selectedEvent ) {
1052+ this . onEventSelected ( eventIndex ) ;
1053+ } else {
1054+ // Clicking the selected row deselects it
1055+ this . onEventDeselected ( ) ;
1056+ }
10101057 }
10111058 }
10121059
@@ -1020,6 +1067,11 @@ export class ViewEventList extends React.Component<ViewEventListProps> {
10201067 this . props . onSelected ( undefined ) ;
10211068 }
10221069
1070+ @action . bound
1071+ onEventToggled ( event : CollectedEvent ) {
1072+ this . props . onEventToggled ( event ) ;
1073+ }
1074+
10231075 @action . bound
10241076 onKeyDown ( event : React . KeyboardEvent < HTMLDivElement > ) {
10251077 const { moveSelection } = this . props ;
0 commit comments