11import * as _ from 'lodash' ;
2- import { runInAction } from 'mobx' ;
2+ import { action , runInAction } from 'mobx' ;
33
44import { CollectedEvent , HttpExchangeView , WebSocketStream } from '../../types' ;
55
@@ -25,143 +25,195 @@ export class ViewEventContextMenuBuilder {
2525 private accountStore : AccountStore ,
2626 private uiStore : UiStore ,
2727
28- private onPin : ( event : CollectedEvent ) => void ,
29- private onDelete : ( event : CollectedEvent ) => void ,
30- private onBuildRuleFromExchange : ( exchange : HttpExchangeView ) => void ,
31- private onPrepareToResendRequest : ( exchange : HttpExchangeView ) => void ,
32- private onAddFilter : ( filter : Filter ) => void
33- ) { }
34-
35- private readonly BaseOptions = {
36- Pin : {
37- type : 'option' ,
38- label : 'Toggle Pinning' ,
39- callback : this . onPin
40- } ,
41- Delete : {
42- type : 'option' ,
43- label : 'Delete' ,
44- callback : this . onDelete
28+ private getSelectedEvents : ( ) => ReadonlyArray < CollectedEvent > ,
29+ private callbacks : {
30+ onPin : ( ) => void ,
31+ onDelete : ( event : CollectedEvent ) => void ,
32+ onDeleteSelection : ( ) => void ,
33+ onBuildRuleFromExchange : ( exchange : HttpExchangeView ) => void ,
34+ onBuildRuleFromSelectedExchanges : ( ) => void ,
35+ onPrepareToResendRequest : ( exchange : HttpExchangeView ) => void ,
36+ onAddFilter : ( filter : Filter ) => void
4537 }
46- } as const ;
38+ ) { }
4739
4840 getContextMenuCallback ( event : CollectedEvent ) {
4941 return ( mouseEvent : React . MouseEvent ) => {
50- const isPaidUser = this . accountStore . user . isPaidUser ( ) ;
42+ const { selectedEventIds } = this . uiStore ;
43+ const isMultiSelected = selectedEventIds . size > 1 && selectedEventIds . has ( event . id ) ;
5144
52- const preferredExportFormat = this . uiStore . exportSnippetFormat
53- ? getCodeSnippetOptionFromKey ( this . uiStore . exportSnippetFormat )
54- : undefined ;
45+ if ( isMultiSelected ) {
46+ this . showMultiSelectionMenu ( mouseEvent ) ;
47+ } else if ( event . isHttp ( ) ) {
48+ this . showHttpEventMenu ( mouseEvent , event ) ;
49+ } else {
50+ this . showBasicEventMenu ( mouseEvent , event ) ;
51+ }
52+ } ;
53+ }
5554
56- if ( event . isHttp ( ) ) {
57- const menuOptions = [
58- this . BaseOptions . Pin ,
59- {
60- type : 'submenu' ,
61- label : 'Filter traffic like this' ,
62- items : [
63- {
64- type : 'option' ,
65- label : 'Show only this hostname' ,
66- callback : ( ) => this . onAddFilter (
67- new HostnameFilter ( `hostname=${ event . request . parsedUrl . hostname } ` )
68- )
69- } ,
70- {
71- type : 'option' ,
72- label : 'Hide this hostname' ,
73- callback : ( ) => this . onAddFilter (
74- new HostnameFilter ( `hostname!=${ event . request . parsedUrl . hostname } ` )
75- )
76- }
77- ]
78- } ,
79- {
80- type : 'option' ,
81- label : 'Copy Request URL' ,
82- callback : ( data : HttpExchangeView ) => copyToClipboard ( data . request . url )
83- } ,
84- ...( ! isPaidUser ? [
85- { type : 'separator' } ,
86- { type : 'option' , label : 'With Pro:' , enabled : false , callback : ( ) => { } }
87- ] as const : [ ] ) ,
88- ...( this . onPrepareToResendRequest ? [
89- {
90- type : 'option' ,
91- enabled : isPaidUser ,
92- label : 'Resend Request' ,
93- callback : ( data : HttpExchangeView ) => this . onPrepareToResendRequest ! ( data )
94- }
95- ] as const : [ ] ) ,
55+ private showMultiSelectionMenu ( mouseEvent : React . MouseEvent ) {
56+ const isPaidUser = this . accountStore . user . isPaidUser ( ) ;
57+ const selectedEvents = this . getSelectedEvents ( ) ;
58+ const count = selectedEvents . length ;
59+ const httpCount = selectedEvents . filter ( e => e . isHttp ( ) && ! e . isWebSocket ( ) ) . length ;
60+
61+ const menuOptions : Array < ContextMenuItem < void > > = [
62+ {
63+ type : 'option' ,
64+ label : 'Toggle Pinning' ,
65+ callback : ( ) => this . callbacks . onPin ( )
66+ } ,
67+ ...( httpCount > 0 ? [ {
68+ type : 'option' as const ,
69+ enabled : isPaidUser ,
70+ label : `Create ${ httpCount } Matching Rule${ httpCount !== 1 ? 's' : '' } ` ,
71+ callback : ( ) => this . callbacks . onBuildRuleFromSelectedExchanges ( )
72+ } ] : [ ] ) ,
73+ {
74+ type : 'option' ,
75+ label : `Delete ${ count } Event${ count !== 1 ? 's' : '' } ` ,
76+ callback : ( ) => this . callbacks . onDeleteSelection ( )
77+ }
78+ ] ;
79+
80+ this . uiStore . handleContextMenuEvent (
81+ mouseEvent ,
82+ menuOptions ,
83+ undefined as void
84+ ) ;
85+ }
86+
87+ private showHttpEventMenu ( mouseEvent : React . MouseEvent , event : HttpExchange | WebSocketStream ) {
88+ const isPaidUser = this . accountStore . user . isPaidUser ( ) ;
89+
90+ const preferredExportFormat = this . uiStore . exportSnippetFormat
91+ ? getCodeSnippetOptionFromKey ( this . uiStore . exportSnippetFormat )
92+ : undefined ;
93+
94+ const menuOptions = [
95+ {
96+ type : 'option' ,
97+ label : 'Toggle Pinning' ,
98+ callback : action ( ( data : HttpExchangeView ) => { data . pinned = ! data . pinned ; } )
99+ } ,
100+ {
101+ type : 'submenu' ,
102+ label : 'Filter traffic like this' ,
103+ items : [
96104 {
97105 type : 'option' ,
98- enabled : isPaidUser ,
99- label : `Create Matching Modify Rule` ,
100- callback : this . onBuildRuleFromExchange
106+ label : 'Show only this hostname' ,
107+ callback : ( ) => this . callbacks . onAddFilter (
108+ new HostnameFilter ( `hostname=${ event . request . parsedUrl . hostname } ` )
109+ )
101110 } ,
102111 {
103112 type : 'option' ,
104- enabled : isPaidUser ,
105- label : `Export Exchange as HAR` ,
106- callback : exportHar
107- } ,
108- // If you have a preferred default format, we show that option at the top level:
109- ...( preferredExportFormat && isPaidUser ? [ {
110- type : 'option' ,
111- label : `Copy as ${ getCodeSnippetFormatName ( preferredExportFormat ) } Snippet` ,
113+ label : 'Hide this hostname' ,
114+ callback : ( ) => this . callbacks . onAddFilter (
115+ new HostnameFilter ( `hostname!=${ event . request . parsedUrl . hostname } ` )
116+ )
117+ }
118+ ]
119+ } ,
120+ {
121+ type : 'option' ,
122+ label : 'Copy Request URL' ,
123+ callback : ( data : HttpExchangeView ) => copyToClipboard ( data . request . url )
124+ } ,
125+ ...( ! isPaidUser ? [
126+ { type : 'separator' } ,
127+ { type : 'option' , label : 'With Pro:' , enabled : false , callback : ( ) => { } }
128+ ] as const : [ ] ) ,
129+ ...( this . callbacks . onPrepareToResendRequest ? [
130+ {
131+ type : 'option' ,
132+ enabled : isPaidUser ,
133+ label : 'Resend Request' ,
134+ callback : ( data : HttpExchangeView ) => this . callbacks . onPrepareToResendRequest ! ( data )
135+ }
136+ ] as const : [ ] ) ,
137+ {
138+ type : 'option' ,
139+ enabled : isPaidUser ,
140+ label : `Create Matching Modify Rule` ,
141+ callback : this . callbacks . onBuildRuleFromExchange
142+ } ,
143+ {
144+ type : 'option' ,
145+ enabled : isPaidUser ,
146+ label : `Export Exchange as HAR` ,
147+ callback : exportHar
148+ } ,
149+ // If you have a preferred default format, we show that option at the top level:
150+ ...( preferredExportFormat && isPaidUser ? [ {
151+ type : 'option' ,
152+ label : `Copy as ${ getCodeSnippetFormatName ( preferredExportFormat ) } Snippet` ,
153+ callback : async ( data : HttpExchange ) => {
154+ copyToClipboard (
155+ await generateCodeSnippet ( data , preferredExportFormat , {
156+ waitForBodyDecoding : true
157+ } )
158+ ) ;
159+ }
160+ } ] as const : [ ] ) ,
161+ {
162+ type : 'submenu' ,
163+ enabled : isPaidUser ,
164+ label : `Copy as Code Snippet` ,
165+ items : Object . keys ( snippetExportOptions ) . map ( ( snippetGroupName ) => ( {
166+ type : 'submenu' as const ,
167+ label : snippetGroupName ,
168+ items : snippetExportOptions [ snippetGroupName ] . map ( ( snippetOption ) => ( {
169+ type : 'option' as const ,
170+ label : getCodeSnippetFormatName ( snippetOption ) ,
112171 callback : async ( data : HttpExchange ) => {
172+ // When you pick an option here, it updates your preferred default option
173+ runInAction ( ( ) => {
174+ this . uiStore . exportSnippetFormat = getCodeSnippetFormatKey ( snippetOption ) ;
175+ } ) ;
176+
113177 copyToClipboard (
114- await generateCodeSnippet ( data , preferredExportFormat , {
178+ await generateCodeSnippet ( data , snippetOption , {
115179 waitForBodyDecoding : true
116180 } )
117181 ) ;
118182 }
119- } ] as const : [ ] ) ,
120- {
121- type : 'submenu' ,
122- enabled : isPaidUser ,
123- label : `Copy as Code Snippet` ,
124- items : Object . keys ( snippetExportOptions ) . map ( ( snippetGroupName ) => ( {
125- type : 'submenu' as const ,
126- label : snippetGroupName ,
127- items : snippetExportOptions [ snippetGroupName ] . map ( ( snippetOption ) => ( {
128- type : 'option' as const ,
129- label : getCodeSnippetFormatName ( snippetOption ) ,
130- callback : async ( data : HttpExchange ) => {
131- // When you pick an option here, it updates your preferred default option
132- runInAction ( ( ) => {
133- this . uiStore . exportSnippetFormat = getCodeSnippetFormatKey ( snippetOption ) ;
134- } ) ;
135-
136- copyToClipboard (
137- await generateCodeSnippet ( data , snippetOption , {
138- waitForBodyDecoding : true
139- } )
140- ) ;
141- }
142- } ) )
143- } ) )
144- } ,
145- this . BaseOptions . Delete
146- ] ;
147-
148- const sortedOptions = _ . sortBy ( menuOptions , ( o : ContextMenuItem < any > ) =>
149- o . type === 'separator' || ! ( o . enabled ?? true )
150- ) as Array < ContextMenuItem < HttpExchange | WebSocketStream > > ;
151-
152- this . uiStore . handleContextMenuEvent (
153- mouseEvent ,
154- sortedOptions ,
155- event
156- )
157- } else {
158- // For non-HTTP events, we just show the super-basic globally supported options:
159- this . uiStore . handleContextMenuEvent ( mouseEvent , [
160- this . BaseOptions . Pin ,
161- this . BaseOptions . Delete
162- ] , event ) ;
183+ } ) )
184+ } ) )
185+ } ,
186+ {
187+ type : 'option' ,
188+ label : 'Delete' ,
189+ callback : this . callbacks . onDelete
163190 }
164- } ;
191+ ] ;
192+
193+ const sortedOptions = _ . sortBy ( menuOptions , ( o : ContextMenuItem < unknown > ) =>
194+ o . type === 'separator' || ! ( o . enabled ?? true )
195+ ) as Array < ContextMenuItem < HttpExchange | WebSocketStream > > ;
196+
197+ this . uiStore . handleContextMenuEvent (
198+ mouseEvent ,
199+ sortedOptions ,
200+ event
201+ )
202+ }
203+
204+ private showBasicEventMenu ( mouseEvent : React . MouseEvent , event : CollectedEvent ) {
205+ this . uiStore . handleContextMenuEvent ( mouseEvent , [
206+ {
207+ type : 'option' ,
208+ label : 'Toggle Pinning' ,
209+ callback : action ( ( data : CollectedEvent ) => { data . pinned = ! data . pinned ; } )
210+ } ,
211+ {
212+ type : 'option' ,
213+ label : 'Delete' ,
214+ callback : this . callbacks . onDelete
215+ }
216+ ] , event ) ;
165217 }
166218
167- }
219+ }
0 commit comments