Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add ability to target headers by name #360

Merged
merged 4 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 59 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"react-router-dom": "^6.24.0",
"react-select": "^5.8.3",
"react-use": "^17.5.1",
"tiny-invariant": "^1.3.3",
"tree-kill": "^1.2.2",
Expand Down
5 changes: 2 additions & 3 deletions src/codegen/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { generateOptions } from './options'
import { getContentTypeWithCharsetHeader } from '@/utils/headers'
import { REQUIRED_IMPORTS } from '@/constants/imports'
import { generateImportStatement } from './imports'
import { cleanupRecording } from './codegen.utils'
import { cleanupRecording, shouldIncludeHeaderInScript } from './codegen.utils'
import { groupProxyData } from '@/utils/groups'

interface GenerateScriptParams {
Expand Down Expand Up @@ -186,9 +186,8 @@ function generateSleep(timing: ThinkTime['timing']): string {
}

export function generateRequestParams(request: ProxyData['request']): string {
const headersToExclude = ['Cookie', 'User-Agent', 'Host', 'Content-Length']
const headers = request.headers
.filter(([name]) => !headersToExclude.includes(name))
.filter(([name]) => shouldIncludeHeaderInScript(name))
.map(([name, value]) => `'${name}': \`${value}\``)
.join(',')

Expand Down
7 changes: 7 additions & 0 deletions src/codegen/codegen.utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { flow } from 'lodash-es'
import { ProxyData } from '@/types'
import { getLocationHeader, getUpgradeHeader } from '@/utils/headers'
import { canonicalHeaderKey } from '@/rules/utils'

const HEADERS_TO_EXCLUDE = ['Cookie', 'User-Agent', 'Host', 'Content-Length']

// TODO: find a well-maintained library for this
export function stringify(value: unknown): string {
Expand Down Expand Up @@ -120,3 +123,7 @@ export const removeWebsocketRequests = (recording: ProxyData[]) => {
export function cleanupRecording(recording: ProxyData[]) {
return flow(removeWebsocketRequests, mergeRedirects)(recording)
}

export function shouldIncludeHeaderInScript(key: string) {
return !HEADERS_TO_EXCLUDE.includes(canonicalHeaderKey(key))
}
40 changes: 40 additions & 0 deletions src/components/Form/ControlledReactSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Control, Controller, FieldValues, Path } from 'react-hook-form'
import { StyledReactSelect } from '../StyledReactSelect'
import { ComponentProps, ReactNode } from 'react'

type Option = { label: ReactNode; value: string; disabled?: boolean }

interface ControlledSelectProps<T extends FieldValues, O extends Option> {
name: Path<T>
control: Control<T>
options: O[]
selectProps?: ComponentProps<typeof StyledReactSelect<O>>
onChange?: (value?: O['value']) => void
}

export function ControlledReactSelect<T extends FieldValues, O extends Option>({
name,
control,
options,
onChange,
selectProps = {},
}: ControlledSelectProps<T, O>) {
return (
<Controller
name={name}
control={control}
render={({ field }) => (
<StyledReactSelect
onChange={(option) =>
onChange ? onChange(option?.value) : field.onChange(option?.value)
}
onBlur={field.onBlur}
value={options.find((option) => option.value === field.value)}
isDisabled={field.disabled}
options={options}
{...selectProps}
/>
)}
/>
)
}
8 changes: 6 additions & 2 deletions src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import { Outlet, useLocation } from 'react-router-dom'

import { Sidebar } from './Sidebar'
import { ActivityBar } from './ActivityBar'
import { useEffect, useState } from 'react'
import { useEffect } from 'react'
import { PinRightIcon } from '@radix-ui/react-icons'
import { useLocalStorage } from 'react-use'

export function Layout() {
const [isSidebarExpanded, setIsSidebarExpanded] = useState(true)
const [isSidebarExpanded, setIsSidebarExpanded] = useLocalStorage(
'isSidebarExpanded',
true
)
Comment on lines +13 to +16
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to the feature, but added this as QoL to avoid closing the sidebar every time I did cmd+r to refresh the app.

const location = useLocation()

const handleVisibleChange = (index: number, visible: boolean) => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Layout/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { SearchField } from '@/components/SearchField'
import { useState } from 'react'

interface SidebarProps {
isExpanded: boolean
isExpanded?: boolean
onCollapseSidebar: () => void
}

Expand Down
74 changes: 74 additions & 0 deletions src/components/StyledReactSelect/StyledReactSelect.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { StylesConfig, Theme } from 'react-select'

export function getThemeConfig(theme: Theme) {
return {
...theme,
spacing: {
...theme.spacing,
baseUnit: 2,
menuGutter: 2,
},

colors: {
...theme.colors,
primary: 'var(--focus-8)', // active border
primary50: 'var(--accent-9)', // focus item
primary25: 'var(--accent-9)', // item hover
neutral0: 'var(--color-surface)', // input background
neutral20: 'var(--gray-a7)', // border
neutral30: 'var(--gray-a8)', // border hover
neutral50: 'var(--gray-a10)', // placeholder
neutral60: 'var(--gray-12)', // caret
neutral80: 'var(--gray-12)', // input text
},
}
}

export function getStylesConfig<Option>(): StylesConfig<Option> {
return {
control: (provided, state) => ({
...provided,
height: 'var(--space-6)',
minHeight: 'auto',
paddingLeft: 'var(--space-3)',
paddingRight: 'var(--space-3)',
boxShadow: state.menuIsOpen ? 'none' : provided.boxShadow,
borderColor: state.menuIsOpen ? 'var(--gray-a8)' : provided.borderColor,
'&:hover': {
borderColor: 'var(--gray-a8)',
},
}),
valueContainer: (provided) => ({
...provided,
padding: 0,
}),
singleValue: (provided) => ({
...provided,
margin: 0,
}),
menu: (provided) => ({
...provided,
backgroundColor: 'var(--color-panel-solid)',
}),
menuPortal: (provided) => ({
...provided,
zIndex: 40,
}),
menuList: (provided) => ({
...provided,
padding: 'var(--space-2)',
paddingRight: 'var(--space-3)',
}),
option: (provided, state) => ({
...provided,
background: state.isFocused ? 'var(--accent-9)' : 'transparent',
color: state.isFocused ? 'var(--accent-contrast)' : undefined,
display: 'flex',
alignItems: 'center',
position: 'relative',
paddingLeft: 'var(--space-5)',
paddingRight: 'var(--space-6)',
borderRadius: 'var(--radius-2)',
}),
}
}
52 changes: 52 additions & 0 deletions src/components/StyledReactSelect/StyledReactSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { ComponentProps, useMemo } from 'react'
import Select, { components, OptionProps } from 'react-select'
import { ChevronDownIcon, ThickCheckIcon } from '@radix-ui/themes'
import { getStylesConfig, getThemeConfig } from './StyledReactSelect.styles'

export function StyledReactSelect<Option>(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Far from ideal integration of ReactSelect into Radix, it doesn't support all the props Radix's select does and has static size, but this iteration I've focused on blending it into Radix styles.

props: ComponentProps<typeof Select<Option>>
) {
const styles = useMemo(() => getStylesConfig<Option>(), [])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be moved to just before the component function? useMemo won't be needed then.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only reason is passing that generic type, was getting TS errors without it :(


return (
<div css={{ fontSize: 'var(--font-size-2)' }}>
<Select
menuPlacement="auto"
menuPosition="fixed"
styles={styles}
components={{
IndicatorSeparator: null,
DropdownIndicator: DropdownIndicator,
Option: OptionComponent<Option>,
}}
theme={getThemeConfig}
{...props}
/>
</div>
)
}

function OptionComponent<Option>({ children, ...props }: OptionProps<Option>) {
return (
<components.Option {...props}>
<div
css={{
position: 'absolute',
left: 0,
width: 'var(--space-5)',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{props.isSelected && <ThickCheckIcon />}
</div>

{children}
</components.Option>
)
}

function DropdownIndicator() {
return <ChevronDownIcon className="rt-SelectIcon" />
}
1 change: 1 addition & 0 deletions src/components/StyledReactSelect/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './StyledReactSelect'
Loading
Loading