Skip to content

Commit 007b37f

Browse files
committed
Initial multi-select support for events
Pin/Delete/Export/Create Rules all work as expected
1 parent 504213c commit 007b37f

8 files changed

+162
-17
lines changed

src/components/view/http/http-details-footer.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export const HttpDetailsFooter = inject('rulesStore')(
9393

9494
event: CollectedEvent,
9595
onDelete: (event: CollectedEvent) => void,
96+
onPin: (event: CollectedEvent) => void,
9697
onScrollToEvent: (event: CollectedEvent) => void,
9798
onBuildRuleFromExchange: (event: HttpExchange) => void,
9899
isPaidUser: boolean,
@@ -107,9 +108,7 @@ export const HttpDetailsFooter = inject('rulesStore')(
107108
/>
108109
<PinButton
109110
pinned={pinned}
110-
onClick={action(() => {
111-
event.pinned = !event.pinned;
112-
})}
111+
onClick={() => props.onPin(event)}
113112
/>
114113
<DeleteButton
115114
pinned={pinned}

src/components/view/http/http-details-pane.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export class HttpDetailsPane extends React.Component<{
6666

6767
navigate: (path: string) => void,
6868
onDelete: (event: CollectedEvent) => void,
69+
onPin: (event: CollectedEvent) => void,
6970
onScrollToEvent: (event: CollectedEvent) => void,
7071
onBuildRuleFromExchange: (exchange: HttpExchange) => void,
7172

@@ -83,6 +84,7 @@ export class HttpDetailsPane extends React.Component<{
8384
const {
8485
exchange,
8586
onDelete,
87+
onPin,
8688
onScrollToEvent,
8789
onBuildRuleFromExchange,
8890
uiStore,
@@ -129,6 +131,7 @@ export class HttpDetailsPane extends React.Component<{
129131
<HttpDetailsFooter
130132
event={exchange}
131133
onDelete={onDelete}
134+
onPin={onPin}
132135
onScrollToEvent={onScrollToEvent}
133136
onBuildRuleFromExchange={onBuildRuleFromExchange}
134137
navigate={navigate}

src/components/view/view-event-list-buttons.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const ClearAllButton = observer((props: {
2828

2929
export const ExportAsHarButton = inject('accountStore')(observer((props: {
3030
className?: string,
31+
isMultiSelectEnabled: boolean,
3132
accountStore?: AccountStore,
3233
events: CollectedEvent[]
3334
}) => {
@@ -45,8 +46,9 @@ export const ExportAsHarButton = inject('accountStore')(observer((props: {
4546
}
4647
disabled={!isPaidUser || props.events.length === 0}
4748
onClick={async () => {
49+
const serialize = props.isMultiSelectEnabled ? props.events.filter(evt=> evt.mulitSelected) : props.events;
4850
const harContent = JSON.stringify(
49-
await generateHar(props.events)
51+
await generateHar(serialize)
5052
);
5153
const filename = `HTTPToolkit_${
5254
dateFns.format(Date.now(), 'YYYY-MM-DD_HH-mm')

src/components/view/view-event-list-footer.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export const ViewEventListFooter = styled(observer((props: {
6161
onClear: () => void,
6262
onFiltersConsidered: (filters: FilterSet | undefined) => void,
6363
onScrollToEnd: () => void,
64+
isMultiSelectEnabled: boolean,
6465

6566
allEvents: CollectedEvent[],
6667
filteredEvents: CollectedEvent[],
@@ -87,7 +88,7 @@ export const ViewEventListFooter = styled(observer((props: {
8788
<ButtonsContainer>
8889
<PlayPauseButton />
8990
<ScrollToEndButton onScrollToEnd={props.onScrollToEnd} />
90-
<ExportAsHarButton events={props.filteredEvents} />
91+
<ExportAsHarButton events={props.filteredEvents} isMultiSelectEnabled={props.isMultiSelectEnabled} />
9192
<ImportHarButton />
9293
<ClearAllButton
9394
disabled={props.allEvents.length === 0}

src/components/view/view-event-list.tsx

+82-8
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ import { StatusCode } from '../common/status-code';
3535
import { HEADER_FOOTER_HEIGHT } from './view-event-list-footer';
3636
import { ViewEventContextMenuBuilder } from './view-context-menu-builder';
3737

38+
const USE_MULTI_SELECT_CHECKBOXES=false;// if this is enabled then a checkbox is shown when multi-select is enabled to allow controlling the checked rows that way rather than using the list directly
39+
const MULTI_SELECT_ROW_CLASSNAME="multiSelected";
40+
3841
const SCROLL_BOTTOM_MARGIN = 5; // If you're in the last 5 pixels of the scroll area, we say you're at the bottom
3942

4043
const EmptyStateOverlay = styled(EmptyState)`
@@ -52,6 +55,8 @@ interface ViewEventListProps {
5255
filteredEvents: CollectedEvent[];
5356
selectedEvent: CollectedEvent | undefined;
5457
isPaused: boolean;
58+
isMultiSelectEnabled: boolean;
59+
onMultiSelectToggled: () => void;
5560

5661
contextMenuBuilder: ViewEventContextMenuBuilder;
5762

@@ -156,6 +161,15 @@ const Status = styled(Column)`
156161
flex-shrink: 0;
157162
flex-grow: 0;
158163
`;
164+
const MultiSelect = styled(Column)`
165+
flex-basis: 20px;
166+
${(p: { isMultiSelectEnabled: boolean }) => p.isMultiSelectEnabled && USE_MULTI_SELECT_CHECKBOXES ? "margin-right: 25px !important;" : "margin-right: 15px !important;" }
167+
flex-shrink: 0;
168+
margin-left: -20px !important;
169+
170+
title: "Multi-select events";
171+
flex-grow: 0;
172+
`;
159173

160174
const Source = styled(Column)`
161175
flex-basis: 49px;
@@ -229,6 +243,10 @@ const EventListRow = styled.div`
229243
user-select: none;
230244
cursor: pointer;
231245
246+
&.multiSelected {
247+
background-color: ${p => p.theme.highlightBackground};
248+
color: ${p => p.theme.highlightColor};
249+
}
232250
&.selected {
233251
background-color: ${p => p.theme.highlightBackground};
234252
color: ${p => p.theme.highlightColor};
@@ -322,6 +340,7 @@ interface EventRowProps extends ListChildComponentProps {
322340
selectedEvent: CollectedEvent | undefined;
323341
events: CollectedEvent[];
324342
contextMenuBuilder: ViewEventContextMenuBuilder;
343+
isMultiSelectEnabled: boolean;
325344
}
326345
}
327346

@@ -352,6 +371,7 @@ const EventRow = observer((props: EventRowProps) => {
352371
return <ExchangeRow
353372
index={index}
354373
isSelected={isSelected}
374+
isMultiSelectEnabled={props.data.isMultiSelectEnabled}
355375
style={style}
356376
exchange={event}
357377
contextMenuBuilder={contextMenuBuilder}
@@ -376,15 +396,31 @@ const EventRow = observer((props: EventRowProps) => {
376396
}
377397
});
378398

399+
interface RowCheckboxProps {
400+
checked:boolean;
401+
whenChecked: React.ChangeEventHandler<HTMLInputElement>;
402+
isMultiSelectEnabled: boolean;
403+
}
404+
405+
406+
const RowCheckbox = styled.input.attrs( (props : RowCheckboxProps) => ({
407+
type: "checkbox", checked: props.checked, onChange: props.whenChecked
408+
409+
}))<RowCheckboxProps>`
410+
${props => props.isMultiSelectEnabled && USE_MULTI_SELECT_CHECKBOXES ? `` : `width: 0 !important;`}
411+
`;
412+
379413
const ExchangeRow = inject('uiStore')(observer(({
380414
index,
381415
isSelected,
382416
style,
383417
exchange,
384-
contextMenuBuilder
418+
contextMenuBuilder,
419+
isMultiSelectEnabled
385420
}: {
386421
index: number,
387422
isSelected: boolean,
423+
isMultiSelectEnabled: boolean,
388424
style: {},
389425
exchange: HttpExchange,
390426
contextMenuBuilder: ViewEventContextMenuBuilder
@@ -403,10 +439,11 @@ const ExchangeRow = inject('uiStore')(observer(({
403439
data-event-id={exchange.id}
404440
tabIndex={isSelected ? 0 : -1}
405441
onContextMenu={contextMenuBuilder.getContextMenuCallback(exchange)}
406-
className={isSelected ? 'selected' : ''}
442+
className={isSelected ? 'selected' : exchange.mulitSelected ? MULTI_SELECT_ROW_CLASSNAME : ''}
407443
style={style}
408444
>
409445
<RowPin pinned={pinned}/>
446+
<RowCheckbox checked={exchange.mulitSelected} whenChecked={exchange.onMultiSelected} isMultiSelectEnabled={isMultiSelectEnabled} />
410447
<RowMarker category={category} title={describeEventCategory(category)} />
411448
<Method pinned={pinned}>{ request.method }</Method>
412449
<Status>
@@ -492,7 +529,7 @@ const RTCConnectionRow = observer(({
492529
data-event-id={event.id}
493530
tabIndex={isSelected ? 0 : -1}
494531

495-
className={isSelected ? 'selected' : ''}
532+
className={isSelected ? 'selected' : event.mulitSelected ? MULTI_SELECT_ROW_CLASSNAME : ''}
496533
style={style}
497534
>
498535
<RowPin pinned={pinned}/>
@@ -536,7 +573,7 @@ const RTCStreamRow = observer(({
536573
data-event-id={event.id}
537574
tabIndex={isSelected ? 0 : -1}
538575

539-
className={isSelected ? 'selected' : ''}
576+
className={isSelected ? 'selected' : event.mulitSelected ? MULTI_SELECT_ROW_CLASSNAME : ''}
540577
style={style}
541578
>
542579
<RowPin pinned={pinned}/>
@@ -604,7 +641,7 @@ const BuiltInApiRow = observer((p: {
604641
tabIndex={p.isSelected ? 0 : -1}
605642

606643
onContextMenu={p.contextMenuBuilder.getContextMenuCallback(p.exchange)}
607-
className={p.isSelected ? 'selected' : ''}
644+
className={p.isSelected ? 'selected' : p.exchange.mulitSelected ? MULTI_SELECT_ROW_CLASSNAME : ''}
608645
style={p.style}
609646
>
610647
<RowPin pinned={pinned}/>
@@ -659,7 +696,7 @@ const TlsRow = observer((p: {
659696
data-event-id={tlsEvent.id}
660697
tabIndex={p.isSelected ? 0 : -1}
661698

662-
className={p.isSelected ? 'selected' : ''}
699+
className={p.isSelected ? 'selected' : tlsEvent.mulitSelected ? MULTI_SELECT_ROW_CLASSNAME : ''}
663700
style={p.style}
664701
>
665702
{
@@ -686,12 +723,14 @@ export class ViewEventList extends React.Component<ViewEventListProps> {
686723
return {
687724
selectedEvent: this.props.selectedEvent,
688725
events: this.props.filteredEvents,
726+
isMultiSelectEnabled: this.props.isMultiSelectEnabled,
689727
contextMenuBuilder: this.props.contextMenuBuilder
690728
};
691729
}
692730

693731
private listBodyRef = React.createRef<HTMLDivElement>();
694732
private listRef = React.createRef<List>();
733+
private AreMultipleEventsSelected = false;
695734

696735
private KeyBoundListWindow = observer(
697736
React.forwardRef<HTMLDivElement>(
@@ -714,6 +753,7 @@ export class ViewEventList extends React.Component<ViewEventListProps> {
714753
return <ListContainer>
715754
<TableHeader>
716755
<MarkerHeader />
756+
<MultiSelect isMultiSelectEnabled={this.props.isMultiSelectEnabled}><input type="Checkbox" onChange={(evt) => this.props.onMultiSelectToggled()} /></MultiSelect>
717757
<Method>Method</Method>
718758
<Status>Status</Status>
719759
<Source>Source</Source>
@@ -876,20 +916,54 @@ export class ViewEventList extends React.Component<ViewEventListProps> {
876916
const eventIndex = parseInt(ariaRowIndex, 10) - 1;
877917
const event = this.props.filteredEvents[eventIndex];
878918
if (event !== this.props.selectedEvent) {
879-
this.onEventSelected(eventIndex);
919+
this.onEventSelected(eventIndex, mouseEvent);
880920
} else {
881921
// Clicking the selected row deselects it
882922
this.onEventDeselected();
883923
}
884924
}
885925

886926
@action.bound
887-
onEventSelected(index: number) {
927+
onEventSelected(index: number, mouseEvent: React.MouseEvent) {
928+
if (this.props.isMultiSelectEnabled){
929+
const eventIndex = index;
930+
const event = this.props.filteredEvents[eventIndex];
931+
if ( (! USE_MULTI_SELECT_CHECKBOXES || mouseEvent.shiftKey) && ! mouseEvent.ctrlKey && this.AreMultipleEventsSelected){ //if using the checkboxes then only clear otehr checkboxes when shift key is hit
932+
this.props.filteredEvents.forEach(evt => evt.mulitSelected = false);//to increase the perf here we should cache the selected events in a list, then we can uncheck them quickly and clear the list rather than doing this every click
933+
this.AreMultipleEventsSelected=false;
934+
}
935+
if (! USE_MULTI_SELECT_CHECKBOXES){
936+
if (! mouseEvent.ctrlKey){
937+
if (this.props.selectedEvent){
938+
this.props.selectedEvent.mulitSelected = false;
939+
}
940+
event.mulitSelected = true;
941+
}
942+
}
943+
944+
945+
if (mouseEvent.ctrlKey){
946+
event.mulitSelected = ! event.mulitSelected;
947+
this.AreMultipleEventsSelected=true; //even if technically only one is selected we are safe to set this to true it just does the above reset first
948+
}
949+
if (mouseEvent.shiftKey){
950+
this.AreMultipleEventsSelected = true; //even if technically only one is selected we are safe to set this to true it just does the above reset first
951+
if (this.props.selectedEvent){
952+
let curIndex = this.props.filteredEvents.indexOf(this.props.selectedEvent);
953+
for(let x = curIndex < eventIndex ? curIndex : eventIndex; x <= (curIndex < eventIndex ? eventIndex : curIndex); x++){
954+
this.props.filteredEvents[x].mulitSelected = true;
955+
}
956+
}
957+
}
958+
}
888959
this.props.onSelected(this.props.filteredEvents[index]);
889960
}
890961

891962
@action.bound
892963
onEventDeselected() {
964+
if (! USE_MULTI_SELECT_CHECKBOXES && this.props.selectedEvent){
965+
this.props.selectedEvent.mulitSelected = false;
966+
}
893967
this.props.onSelected(undefined);
894968
}
895969

0 commit comments

Comments
 (0)