Skip to content

Commit

Permalink
feat: Update WebLogView to handle common mime types (#41)
Browse files Browse the repository at this point in the history
* feat: Update WebLogView components

- Update Remove padding from Drawer component
- Update WebLogView/Row/SideBar layout
- Add RequestDetails components
- Add ResponseDetails components
- Add a Tabs component to fill the height of the Allotment component

* feat: Add padding to main window

* feat: Add response cookies tab

* feat: Handle form data

- Consolidate Cookie component
- Consolidate Read-only editor component
- Update raw/preview switch to segmented controls

* refactor: Add comments for custom Tabs component

* fix: Update form data handling

* fix: Handle audio, video, css, js files

- Disable raw content for media files

* feat: Add font rendering
  • Loading branch information
2Steaks authored Jul 19, 2024
1 parent 20501e4 commit 6326a18
Show file tree
Hide file tree
Showing 24 changed files with 606 additions and 116 deletions.
13 changes: 9 additions & 4 deletions src/components/Drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,19 @@ export function Drawer({
}) {
return (
<>
<Box p="2" position="absolute" right="0" top="0">
<Box
p="2"
position="absolute"
right="0"
top="0"
pt="9px"
style={{ zIndex: 1 }}
>
<IconButton size="1" variant="ghost" onClick={close}>
<Cross2Icon />
</IconButton>
</Box>
<Box height="100%" pt="9px">
{children}
</Box>
<Box height="100%">{children}</Box>
</>
)
}
26 changes: 0 additions & 26 deletions src/components/RequestDetails/RequestDetails.tsx

This file was deleted.

40 changes: 0 additions & 40 deletions src/components/RequestDetails/RequestDetails.utils.ts

This file was deleted.

23 changes: 0 additions & 23 deletions src/components/RequestDetails/Response.tsx

This file was deleted.

33 changes: 33 additions & 0 deletions src/components/WebLogView/Cookies.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Flex, Table } from '@radix-ui/themes'

import { Cookie } from '@/types'

export function Cookies({ cookies = [] }: { cookies?: Cookie[] }) {
if (!cookies.length) {
return (
<Flex height="200px" justify="center" align="center">
Cookies not available
</Flex>
)
}

return (
<Table.Root size="1" variant="surface">
<Table.Header>
<Table.Row>
<Table.ColumnHeaderCell>Name</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell>Value</Table.ColumnHeaderCell>
</Table.Row>
</Table.Header>

<Table.Body>
{cookies.map(([name, value], index) => (
<Table.Row key={index}>
<Table.Cell>{name}</Table.Cell>
<Table.Cell>{value}</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
)
}
42 changes: 42 additions & 0 deletions src/components/WebLogView/ReadOnlyEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { ComponentProps } from 'react'
import { Editor } from '@monaco-editor/react'
import { editor } from 'monaco-editor'

import { useTheme } from '@/hooks/useTheme'

const options: editor.IStandaloneEditorConstructionOptions = {
automaticLayout: true,
codeLens: false,
contextmenu: false,
domReadOnly: true,
fixedOverflowWidgets: true,
foldingMaximumRegions: 5,
lineNumbers: 'off',
minimap: {
enabled: false,
renderCharacters: false,
},
overviewRulerBorder: false,
readOnly: true,
scrollbar: {
alwaysConsumeMouseWheel: true,
horizontalScrollbarSize: 3,
verticalScrollbarSize: 3,
},
scrollBeyondLastLine: false,
tabSize: 1,
wordWrap: 'off',
}

export function ReadOnlyEditor(props: ComponentProps<typeof Editor>) {
const theme = useTheme()

return (
<Editor
theme={theme === 'dark' ? 'vs-dark' : 'vs'}
height="100%"
options={options}
{...props}
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,6 @@ export function Headers({ data }: { data: ProxyData }) {
<DataList.Value>{data.request.method}</DataList.Value>
</DataList.Item>

<DataList.Item>
<DataList.Label>Status Code</DataList.Label>
<DataList.Value>{data.response?.statusCode}</DataList.Value>
</DataList.Item>

{data.response && (
<>
<Strong>Response headers</Strong>
{data.response?.headers.map(([key, value], index) => (
<DataList.Item key={`${key}_${index}`}>
<DataList.Label>{key}</DataList.Label>
<DataList.Value>{value}</DataList.Value>
</DataList.Item>
))}
</>
)}

<Strong>Request headers</Strong>
{data.request.headers.map(([key, value], index) => (
<DataList.Item key={`${key}_${index}`}>
<DataList.Label>{key}</DataList.Label>
Expand Down
20 changes: 20 additions & 0 deletions src/components/WebLogView/RequestDetails/Payload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Flex } from '@radix-ui/themes'

import { ProxyData } from '@/types'

import { ReadOnlyEditor } from '../ReadOnlyEditor'
import { parseParams } from './utils'

export function Payload({ data }: { data: ProxyData }) {
const content = parseParams(data)

if (!content) {
return (
<Flex height="200px" justify="center" align="center">
Payload not available
</Flex>
)
}

return <ReadOnlyEditor language="javascript" value={content} />
}
47 changes: 47 additions & 0 deletions src/components/WebLogView/RequestDetails/RequestDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ComponentProps } from 'react'
import { Box, ScrollArea } from '@radix-ui/themes'

import { ProxyData } from '@/types'

import { Tabs } from '../Tabs'
import { Cookies } from '../Cookies'
import { Headers } from './Headers'
import { Payload } from './Payload'

export function RequestDetails({ data }: { data: ProxyData }) {
return (
<Tabs.Root defaultValue="headers">
<Tabs.List>
<Tabs.Trigger value="headers">Headers</Tabs.Trigger>
<Tabs.Trigger value="payload">Payload</Tabs.Trigger>
<Tabs.Trigger value="cookies">Cookies</Tabs.Trigger>
</Tabs.List>

<ScrollableTabsContent value="headers">
<Headers data={data} />
</ScrollableTabsContent>
<ScrollableTabsContent value="payload">
<Payload data={data} />
</ScrollableTabsContent>
<ScrollableTabsContent value="cookies">
<Cookies cookies={data.request?.cookies} />
</ScrollableTabsContent>
</Tabs.Root>
)
}

const ScrollableTabsContent = ({
children,
value,
...props
}: ComponentProps<typeof Tabs.Content>) => {
return (
<Tabs.Content value={value} {...props}>
<ScrollArea style={{ height: '100%' }}>
<Box p="4" height="100%">
{children}
</Box>
</ScrollArea>
</Tabs.Content>
)
}
File renamed without changes.
28 changes: 28 additions & 0 deletions src/components/WebLogView/RequestDetails/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ProxyData } from '@/types'
import { queryStringToJSON, safeAtob, stringify } from '@/utils/format'
import { getContentType } from '@/utils/headers'

export function parseParams(data: ProxyData) {
const hasParams = data.request.query.length || data.request.content

if (data.request.method === 'OPTIONS' || !data.response || !hasParams) {
return
}

try {
if (data.request.query.length) {
return stringify(data.request.query)
}

const contentType = getContentType(data.request?.headers ?? [])

if (contentType === 'application/x-www-form-urlencoded') {
return stringify(queryStringToJSON(safeAtob(data.request.content)))
}

return stringify(JSON.parse(safeAtob(data.request.content)))
} catch (e) {
console.error('Failed to parse query parameters', e)
return
}
}
63 changes: 63 additions & 0 deletions src/components/WebLogView/ResponseDetails/Content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useState } from 'react'
import { Box, Flex, ScrollArea, SegmentedControl } from '@radix-ui/themes'

import { ProxyData } from '@/types'
import { getContentType } from '@/utils/headers'
import { Preview } from './Preview'
import { Raw } from './Raw'
import { parseContent, toFormat } from './ResponseDetails.utils'

export function Content({ data }: { data: ProxyData }) {
const [isPreview, setIsPreview] = useState(true)

const contentType = getContentType(data.response?.headers ?? [])
const format = toFormat(contentType)
const content = parseContent(format, data)

if (!contentType || !content || !format) {
return (
<Flex height="200px" justify="center" align="center">
Content not available
</Flex>
)
}

const contentProps = {
content,
contentType,
format,
}

return (
<Flex direction="column" gap="4" height="100%" py="4">
{!isMedia(format) && (
<Flex gap="2" justify="end" px="4">
<SegmentedControl.Root
defaultValue="preview"
radius="small"
size="1"
variant="classic"
onValueChange={(value) => setIsPreview(value === 'preview')}
>
<SegmentedControl.Item value="raw">Raw</SegmentedControl.Item>
<SegmentedControl.Item value="preview">
Preview
</SegmentedControl.Item>
</SegmentedControl.Root>
</Flex>
)}
<ScrollArea style={{ height: '100%' }}>
<Box px="4" height="100%">
{isPreview ? (
<Preview {...contentProps} />
) : (
<Raw {...contentProps} />
)}
</Box>
</ScrollArea>
</Flex>
)
}

const isMedia = (format: string) =>
['audio', 'font', 'image', 'video'].includes(format)
Loading

0 comments on commit 6326a18

Please sign in to comment.