Skip to content

Commit

Permalink
feat: Route-based nav (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
going-confetti authored Aug 1, 2024
1 parent 28abf73 commit 9a15f76
Show file tree
Hide file tree
Showing 40 changed files with 708 additions and 401 deletions.
9 changes: 7 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Home } from '@/views/Home'
import { Generator } from './views/Generator/Generator'
import { useTheme } from './hooks/useTheme'
import { globalStyles } from './globalStyles'
import { RecordingPreviewer } from './views/RecordingPreviewer'

export function App() {
const theme = useTheme()
Expand All @@ -21,8 +22,12 @@ export function App() {
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="recorder" element={<Recorder />} />
<Route path="validator" element={<Validator />} />
<Route path="generator/*" element={<Generator />} />
<Route
path="recording-previewer/:path"
element={<RecordingPreviewer />}
/>
<Route path="generator/:path/*" element={<Generator />} />
<Route path="validator/:path?" element={<Validator />} />
</Route>
</Routes>
</HashRouter>
Expand Down
1 change: 1 addition & 0 deletions src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const launchBrowser = async () => {
'--no-first-run',
'--disable-background-networking',
'--disable-component-update',
'--disable-search-engine-choice-screen',
`--proxy-server=http://localhost:${proxyPort}`,
`--ignore-certificate-errors-spki-list=${certificateSPKI}`,
disableChromeOptimizations,
Expand Down
33 changes: 19 additions & 14 deletions src/components/FileTree/File.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { css } from '@emotion/react'
import { Button, Tooltip } from '@radix-ui/themes'

import { getFileNameFromPath } from '@/utils/file'
import { Link } from 'react-router-dom'

interface FileProps {
path: string
isSelected?: boolean
onOpen?: (path: string) => void
viewPath: string
isSelected: boolean
}

export function File({ path, isSelected, onOpen }: FileProps) {
const fileName = path.split('/').pop()
export function File({ path, viewPath, isSelected }: FileProps) {
const fileName = getFileNameFromPath(path)

return (
<Tooltip content={path}>
<Button
variant="outline"
onClick={() => onOpen?.(path)}
color={isSelected ? 'violet' : 'gray'}
radius="full"
css={css`
Expand All @@ -24,16 +26,19 @@ export function File({ path, isSelected, onOpen }: FileProps) {
border: none;
box-shadow: none;
`}
asChild
>
<span
css={css`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`}
>
{fileName}
</span>
<Link to={`${viewPath}/${encodeURIComponent(path)}`}>
<span
css={css`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`}
>
{fileName}
</span>
</Link>
</Button>
</Tooltip>
)
Expand Down
20 changes: 5 additions & 15 deletions src/components/FileTree/FileList.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import { css } from '@emotion/react'
import { Button } from '@radix-ui/themes'
import { File } from './File'
import { useStudioUIStore } from '@/store/ui'
import { useParams } from 'react-router-dom'

interface FileListProps {
files: string[]
viewPath: string
noFilesMessage: string
onOpenFile?: (path: string) => void
}

export function FileList({ files, onOpenFile, noFilesMessage }: FileListProps) {
const selectedFile = useStudioUIStore((state) => state.selectedFile)
const setSelectedFile = useStudioUIStore((state) => state.setSelectedFile)

const handleOpenFile = (path: string) => {
if (!onOpenFile) return
onOpenFile(path)
setSelectedFile(path)
}
export function FileList({ files, noFilesMessage, viewPath }: FileListProps) {
const { path } = useParams()

if (files.length === 0) {
return (
Expand Down Expand Up @@ -50,11 +44,7 @@ export function FileList({ files, onOpenFile, noFilesMessage }: FileListProps) {
>
{files.map((file) => (
<li key={file}>
<File
path={file}
isSelected={selectedFile === file}
onOpen={handleOpenFile}
/>
<File path={file} isSelected={file === path} viewPath={viewPath} />
</li>
))}
</ul>
Expand Down
6 changes: 3 additions & 3 deletions src/components/FileTree/FileTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import { FileList } from './FileList'
interface FileTreeProps {
label: string
files: string[]
viewPath: string
noFilesMessage?: string
onOpenFile?: (path: string) => void
}

export function FileTree({
label,
files,
viewPath,
noFilesMessage = 'No files found',
onOpenFile,
}: FileTreeProps) {
const [open, setOpen] = useState(true)

Expand All @@ -37,7 +37,7 @@ export function FileTree({
<Collapsible.Content>
<FileList
files={files}
onOpenFile={onOpenFile}
viewPath={viewPath}
noFilesMessage={noFilesMessage}
/>
</Collapsible.Content>
Expand Down
13 changes: 3 additions & 10 deletions src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Box, Flex } from '@radix-ui/themes'
import { Box } from '@radix-ui/themes'
import { css } from '@emotion/react'
import { Allotment } from 'allotment'
import { Outlet } from 'react-router-dom'
Expand All @@ -22,21 +22,14 @@ export function Layout() {
`}
>
<Allotment>
<Allotment.Pane minSize={200} preferredSize={200} maxSize={320}>
<Allotment.Pane minSize={200} preferredSize={270} maxSize={320}>
<Sidebar />
</Allotment.Pane>

<Allotment.Pane>
<Allotment vertical>
<Allotment.Pane>
<Flex
direction="column"
overflow="hidden"
width="100%"
height="100%"
>
<Outlet />
</Flex>
<Outlet />
</Allotment.Pane>
{bottomDrawer.isOpen && (
<Allotment.Pane visible={bottomDrawer.isOpen}>
Expand Down
9 changes: 9 additions & 0 deletions src/components/Layout/Sidebar/Sidebar.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export function useFolderContent() {
const generators = useStudioUIStore((s) => s.generators)
const scripts = useStudioUIStore((s) => s.scripts)
const addFile = useStudioUIStore((s) => s.addFile)
const removeFile = useStudioUIStore((s) => s.removeFile)
const setFolderContent = useStudioUIStore((s) => s.setFolderContent)

useEffect(() => {
Expand All @@ -23,6 +24,14 @@ export function useFolderContent() {
[addFile]
)

useEffect(
() =>
window.studio.ui.onRemoveFile((path) => {
removeFile(path)
}),
[removeFile]
)

return {
recordings,
generators,
Expand Down
13 changes: 4 additions & 9 deletions src/components/Layout/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import { css } from '@emotion/react'
import { Box, Flex, Heading, IconButton, ScrollArea } from '@radix-ui/themes'
import { Link, useNavigate } from 'react-router-dom'
import { Link } from 'react-router-dom'

import { ThemeSwitcher } from '@/components/ThemeSwitcher'
import { FileTree } from '@/components/FileTree'
import { useFolderContent } from './Sidebar.hooks'
import { loadGenerator } from '@/views/Generator/Generator.utils'
import K6Logo from '@/assets/logo.svg'

export function Sidebar() {
const { recordings, generators, scripts } = useFolderContent()
const navigate = useNavigate()

const handleOpenGenerator = (path: string) => {
loadGenerator(path)
navigate('/generator')
}

return (
<Box
Expand Down Expand Up @@ -50,17 +43,19 @@ export function Sidebar() {
label="Recordings"
files={recordings}
noFilesMessage="No recordings found"
viewPath="/recording-previewer"
/>
<FileTree
label="Test generators"
files={generators}
onOpenFile={handleOpenGenerator}
noFilesMessage="No generators found"
viewPath="/generator"
/>
<FileTree
label="Scripts"
files={scripts}
noFilesMessage="No scripts found"
viewPath="/validator"
/>
</Flex>
</ScrollArea>
Expand Down
23 changes: 23 additions & 0 deletions src/components/Layout/View.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Box, Flex } from '@radix-ui/themes'
import { PropsWithChildren, ReactNode } from 'react'
import { PageHeading } from './PageHeading'

interface ViewProps {
title: string
actions: ReactNode
loading?: boolean
}

export function View({
title,
actions,
loading = false,
children,
}: PropsWithChildren<ViewProps>) {
return (
<Flex direction="column" overflow="hidden" width="100%" height="100%">
<PageHeading text={title}>{actions}</PageHeading>
{loading ? <Box p="2">Loading...</Box> : children}
</Flex>
)
}
27 changes: 19 additions & 8 deletions src/components/WebLogView/WebLogView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import { isGroupedProxyData } from './WebLogView.utils'
import { Row } from './Row'
import { Group } from './Group'

export function WebLogView({
requests,
}: {
interface WebLogViewProps {
requests: ProxyData[] | GroupedProxyData
}) {
noRequestsMessage?: string
}

export function WebLogView({ requests, noRequestsMessage }: WebLogViewProps) {
if (isEmpty(requests)) {
return <NoRequestsMessage />
return <NoRequestsMessage noRequestsMessage={noRequestsMessage} />
}

if (isGroupedProxyData(requests)) {
Expand All @@ -31,7 +32,11 @@ export function WebLogView({
return <RequestList requests={requests} />
}

function RequestList({ requests }: { requests: ProxyData[] }) {
interface RequestListProps {
requests: ProxyData[]
}

function RequestList({ requests }: RequestListProps) {
return (
<>
{requests.map((data) => (
Expand All @@ -41,13 +46,19 @@ function RequestList({ requests }: { requests: ProxyData[] }) {
)
}

function NoRequestsMessage() {
interface NoRequestsMessageProps {
noRequestsMessage?: string
}

function NoRequestsMessage({
noRequestsMessage = 'Your requests will appear here.',
}: NoRequestsMessageProps) {
return (
<Callout.Root>
<Callout.Icon>
<InfoCircledIcon />
</Callout.Icon>
<Callout.Text>Your requests will appear here.</Callout.Text>
<Callout.Text>{noRequestsMessage}</Callout.Text>
</Callout.Root>
)
}
6 changes: 3 additions & 3 deletions src/hooks/useAutoScroll.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useEffect, useRef } from 'react'

export function useAutoScroll(items: unknown) {
export function useAutoScroll(items: unknown, enabled = true) {
const bottomRef = useRef<HTMLDivElement>(null)

useEffect(() => {
if (!bottomRef.current) return
if (!bottomRef.current || !enabled) return

bottomRef.current.scrollIntoView({ behavior: 'smooth', block: 'end' })
}, [items])
}, [items, enabled])

return bottomRef
}
26 changes: 16 additions & 10 deletions src/hooks/useListenProxyData.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { useEffect, useRef } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'

import { useRecorderStore } from '@/store/recorder/useRecorderStore'
import { ProxyData } from '@/types'
import { mergeRequestsById } from '@/views/Recorder/Recorder.utils'

export function useListenProxyData(group?: string) {
const { resetProxyData, addRequest } = useRecorderStore()
const [proxyData, setProxyData] = useState<ProxyData[]>([])
const groupRef = useRef(group)

useEffect(() => {
return () => {
resetProxyData()
}
}, [resetProxyData])
const resetProxyData = useCallback(() => {
setProxyData([])
}, [])

useEffect(() => {
// Create ref to avoid creating multiple listeners
Expand All @@ -20,7 +19,14 @@ export function useListenProxyData(group?: string) {

useEffect(() => {
return window.studio.proxy.onProxyData((data) => {
addRequest(data, groupRef.current ?? 'default')
setProxyData((s) =>
mergeRequestsById(s, {
...data,
group: groupRef.current ?? 'default',
})
)
})
}, [addRequest])
}, [])

return { proxyData, resetProxyData }
}
Loading

0 comments on commit 9a15f76

Please sign in to comment.