Skip to content

Commit 923e894

Browse files
committed
Ability to pin or exclude headers from display
Also copy header to value to clipboard
1 parent d164770 commit 923e894

File tree

4 files changed

+180
-16
lines changed

4 files changed

+180
-16
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { action, runInAction } from 'mobx';
2+
import { copyToClipboard } from '../../util/ui';
3+
4+
import { AccountStore } from '../../model/account/account-store';
5+
import { UiStore } from '../../model/ui/ui-store';
6+
import { ContextMenuItem } from '../../model/ui/context-menu';
7+
import { HeaderClickedData,HeadersHeaderClickedData } from './http/header-details';
8+
import {IEList} from '../../model/IncludeExcludeList';
9+
10+
export class HeadersHeaderContextMenuBuilder {
11+
12+
constructor(
13+
private uiStore: UiStore
14+
) {}
15+
16+
getContextMenuCallback(event: HeadersHeaderClickedData) {
17+
return (mouseEvent: React.MouseEvent) => {
18+
let excluded = event.HeadersIncludeExcludeList.GetKeysOnList(IEList.Exclude);
19+
20+
this.uiStore.handleContextMenuEvent(mouseEvent, event, [
21+
22+
{
23+
type: 'submenu',
24+
enabled: excluded.length > 0,
25+
label: `Excluded`,
26+
items: [
27+
{
28+
type: 'option',
29+
label: `Clear All Excluded Headers`,
30+
callback: async (data) => data.HeadersIncludeExcludeList.ClearList(IEList.Exclude)
31+
32+
},
33+
...
34+
(excluded.map((headerName) => ({
35+
36+
type: 'option',
37+
label: `Clear '${headerName}'`,
38+
callback: async (data: HeadersHeaderClickedData) =>
39+
data.HeadersIncludeExcludeList.RemoveFromList(headerName,IEList.Exclude)
40+
41+
42+
}
43+
))
44+
) as ContextMenuItem<HeadersHeaderClickedData>[]
45+
]
46+
}
47+
48+
49+
]
50+
51+
);
52+
};
53+
}
54+
}
55+
56+
export class HeadersContextMenuBuilder {
57+
58+
constructor(
59+
private accountStore: AccountStore,
60+
private uiStore: UiStore
61+
) {}
62+
63+
getContextMenuCallback(event: HeaderClickedData) {
64+
return (mouseEvent: React.MouseEvent) => {
65+
const { isPaidUser } = this.accountStore;
66+
let isPinned = event.HeadersIncludeExcludeList.IsKeyOnList(event.header_name,IEList.Favorite);
67+
68+
this.uiStore.handleContextMenuEvent(mouseEvent, event, [
69+
{
70+
type: 'option',
71+
label: (isPinned ? `Unpin` : `Pin`) + ` This Header`,
72+
callback: async (data) => {
73+
isPinned ? data.HeadersIncludeExcludeList.RemoveFromList(data.header_name,IEList.Favorite) : data.HeadersIncludeExcludeList.AddOrUpdateToList(data.header_name,IEList.Favorite);
74+
}
75+
},
76+
{
77+
type: 'option',
78+
label: `Exclude This Header`,
79+
callback: async (data) => {
80+
data.HeadersIncludeExcludeList.AddOrUpdateToList(data.header_name,IEList.Exclude);
81+
}
82+
},
83+
{
84+
type: 'option',
85+
label: `Copy Header Value`,
86+
callback: async (data) =>
87+
copyToClipboard( data.header_value )
88+
89+
90+
}
91+
92+
]
93+
94+
);
95+
};
96+
}
97+
}

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

+61-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Headers } from '../../../types';
77

88
import { getHeaderDocs } from '../../../model/http/http-docs';
99
import { AccountStore } from '../../../model/account/account-store';
10+
import { UiStore } from '../../../model/ui/ui-store';
1011

1112
import { CollapsibleSection } from '../../common/collapsible-section';
1213
import { DocsLink } from '../../common/docs-link';
@@ -18,6 +19,21 @@ import {
1819

1920
import { CookieHeaderDescription } from './set-cookie-header-description';
2021
import { UserAgentHeaderDescription } from './user-agent-header-description';
22+
import {IEList, IncludeExcludeList} from '../../../model/IncludeExcludeList';
23+
import { ArrowIcon, Icon, WarningIcon } from '../../../icons';
24+
import { filterProps } from '../../component-utils';
25+
import { HeadersContextMenuBuilder } from '../headers-context-menu-builder';
26+
27+
//the header itself over the headers section
28+
export interface HeadersHeaderClickedData {
29+
HeadersIncludeExcludeList : IncludeExcludeList<string>
30+
}
31+
32+
export interface HeaderClickedData {
33+
HeadersIncludeExcludeList : IncludeExcludeList<string>,
34+
header_name : string;
35+
header_value : string;
36+
}
2137

2238
const HeadersGrid = styled.section`
2339
display: grid;
@@ -74,13 +90,44 @@ const getHeaderDescription = (
7490
{ headerDocs }
7591
</p>
7692
};
77-
78-
export const HeaderDetails = inject('accountStore')(observer((props: {
93+
const RowPin = styled(
94+
filterProps(Icon, 'pinned')
95+
).attrs((p: { pinned: boolean }) => ({
96+
icon: ['fas', 'thumbtack'],
97+
title: p.pinned ? "This header is pinned, it will appear at the top of the list by default" : ''
98+
}))`
99+
font-size: 90%;
100+
background-color: ${p => p.theme.containerBackground};
101+
/* Without this, 0 width pins create a large & invisible but still clickable icon */
102+
overflow: hidden;
103+
transition: width 0.1s, padding 0.1s, margin 0.1s;
104+
${(p: { pinned: boolean }) =>
105+
p.pinned
106+
? `
107+
width: auto;
108+
padding: 4px;
109+
height: 40%;
110+
&& { margin-right: -3px; }
111+
`
112+
: `
113+
padding: 0px 0;
114+
width: 0 !important;
115+
margin: 0 !important;
116+
`
117+
}
118+
`;
119+
export const HeaderDetails = inject('accountStore','uiStore')(observer((props: {
79120
headers: Headers,
80121
requestUrl: URL,
81-
accountStore?: AccountStore
122+
accountStore?: AccountStore,
123+
uiStore?: UiStore,
124+
HeadersIncludeExcludeList : IncludeExcludeList<string>,
82125
}) => {
83-
const headerNames = Object.keys(props.headers).sort();
126+
let contextMenuBuilder = new HeadersContextMenuBuilder(props.accountStore!,props.uiStore!);
127+
let all_headers = Object.keys(props.headers);
128+
const filtered = props.HeadersIncludeExcludeList.FilterArrayAgainstList(all_headers.sort(), IEList.Favorite, true );
129+
const headerNames = Array.from( props.HeadersIncludeExcludeList.SortArrayAgainstList(filtered, IEList.Favorite));
130+
let hiddenCount = all_headers.length - headerNames.length;
84131

85132
return headerNames.length === 0 ?
86133
<BlankContentPlaceholder>(None)</BlankContentPlaceholder>
@@ -111,8 +158,8 @@ export const HeaderDetails = inject('accountStore')(observer((props: {
111158
)
112159

113160
return <CollapsibleSection withinGrid={true} key={key}>
114-
<HeaderKeyValue>
115-
<HeaderName>{ name }: </HeaderName>
161+
<HeaderKeyValue onContextMenu={contextMenuBuilder.getContextMenuCallback({header_name:name,header_value:value,HeadersIncludeExcludeList:props.HeadersIncludeExcludeList})}>
162+
<HeaderName><RowPin pinned={props.HeadersIncludeExcludeList.IsKeyOnList(name,IEList.Favorite)} /> { name }: </HeaderName>
116163
<span>{ value }</span>
117164
</HeaderKeyValue>
118165

@@ -124,5 +171,13 @@ export const HeaderDetails = inject('accountStore')(observer((props: {
124171
</HeaderDescriptionContainer> }
125172
</CollapsibleSection>
126173
}) }
174+
{
175+
hiddenCount > 0 ?
176+
<CollapsibleSection withinGrid={true}><HeaderKeyValue>
177+
<HeaderName>Plus {hiddenCount} hidden...</HeaderName>
178+
179+
</HeaderKeyValue></CollapsibleSection>
180+
: <BlankContentPlaceholder />
181+
}
127182
</HeadersGrid>;
128183
}));

src/components/view/http/http-request-card.tsx

+11-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { getSummaryColour } from '../../../model/events/categorization';
1212
import { getMethodDocs } from '../../../model/http/http-docs';
1313
import { nameHandlerClass } from '../../../model/rules/rule-descriptions';
1414
import { HandlerClassKey } from '../../../model/rules/rules';
15+
import {IEList, IncludeExcludeList} from '../../../model/IncludeExcludeList';
16+
import {HeadersHeaderContextMenuBuilder} from '../headers-context-menu-builder';
1517

1618
import {
1719
CollapsibleCardHeading,
@@ -86,8 +88,11 @@ const MatchedRulePill = styled(inject('uiStore')((p: {
8688
margin-right: 5px;
8789
}
8890
`;
91+
const HeadersIncludeExcludeList = new IncludeExcludeList<string>();
8992

90-
const RawRequestDetails = (p: { request: HtkRequest }) => {
93+
94+
const RawRequestDetails = inject('uiStore') ((p: { uiStore?: UiStore, request: HtkRequest }) => {
95+
const headerHeaderContextMenuBuilder = new HeadersHeaderContextMenuBuilder(p.uiStore!);
9196
const methodDocs = getMethodDocs(p.request.method);
9297
const methodDetails = [
9398
methodDocs && <Markdown
@@ -132,10 +137,11 @@ const RawRequestDetails = (p: { request: HtkRequest }) => {
132137
}
133138
</CollapsibleSection>
134139

135-
<ContentLabelBlock>Headers</ContentLabelBlock>
136-
<HeaderDetails headers={p.request.headers} requestUrl={p.request.parsedUrl} />
140+
<ContentLabelBlock onContextMenu={headerHeaderContextMenuBuilder.getContextMenuCallback({HeadersIncludeExcludeList:HeadersIncludeExcludeList})}>Headers</ContentLabelBlock>
141+
<HeaderDetails headers={p.request.headers} requestUrl={p.request.parsedUrl} HeadersIncludeExcludeList={HeadersIncludeExcludeList} />
137142
</div>;
138-
}
143+
});
144+
139145

140146
interface HttpRequestCardProps extends CollapsibleCardProps {
141147
exchange: HttpExchange;
@@ -146,7 +152,7 @@ interface HttpRequestCardProps extends CollapsibleCardProps {
146152
onRuleClicked: () => void;
147153
}
148154

149-
export const HttpRequestCard = observer((props: HttpRequestCardProps) => {
155+
export const HttpRequestCard = observer(( props: HttpRequestCardProps) => {
150156
const { exchange, matchedRuleData, onRuleClicked } = props;
151157
const { request } = exchange;
152158

src/components/view/http/http-response-card.tsx

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as _ from 'lodash';
22
import * as React from 'react';
3-
import { observer } from 'mobx-react';
3+
import { inject, observer } from 'mobx-react';
44
import { get } from 'typesafe-get';
55

66
import { HtkResponse, Omit } from '../../../types';
@@ -9,6 +9,9 @@ import { Theme } from '../../../styles';
99
import { ApiExchange } from '../../../model/api/api-interfaces';
1010
import { getStatusColor } from '../../../model/events/categorization';
1111
import { getStatusDocs, getStatusMessage } from '../../../model/http/http-docs';
12+
import {IEList, IncludeExcludeList} from '../../../model/IncludeExcludeList';
13+
import {HeadersHeaderContextMenuBuilder} from '../headers-context-menu-builder';
14+
import { UiStore } from '../../../model/ui/ui-store';
1215

1316
import {
1417
CollapsibleCard,
@@ -36,10 +39,13 @@ interface HttpResponseCardProps extends CollapsibleCardProps {
3639
theme: Theme;
3740
requestUrl: URL;
3841
response: HtkResponse;
42+
uiStore?: UiStore;
3943
apiExchange: ApiExchange | undefined;
4044
}
45+
const HeadersIncludeExcludeList = new IncludeExcludeList<string>();
4146

42-
export const HttpResponseCard = observer((props: HttpResponseCardProps) => {
47+
export const HttpResponseCard = inject('uiStore') (observer(( props: HttpResponseCardProps) => {
48+
const headerHeaderContextMenuBuilder = new HeadersHeaderContextMenuBuilder(props.uiStore!);
4349
const { response, requestUrl, theme, apiExchange } = props;
4450

4551
const apiResponseDescription = get(apiExchange, 'response', 'description');
@@ -85,8 +91,8 @@ export const HttpResponseCard = observer((props: HttpResponseCardProps) => {
8591
}
8692
</CollapsibleSection>
8793

88-
<ContentLabelBlock>Headers</ContentLabelBlock>
89-
<HeaderDetails headers={response.headers} requestUrl={requestUrl} />
94+
<ContentLabelBlock onContextMenu={headerHeaderContextMenuBuilder.getContextMenuCallback({HeadersIncludeExcludeList:HeadersIncludeExcludeList})}>Headers</ContentLabelBlock>
95+
<HeaderDetails headers={response.headers} requestUrl={requestUrl} HeadersIncludeExcludeList={HeadersIncludeExcludeList} />
9096
</div>
9197
</CollapsibleCard>;
92-
});
98+
}));

0 commit comments

Comments
 (0)