Skip to content

Commit 2eceb3c

Browse files
committed
improve styles and add empty states
1 parent 4c48e21 commit 2eceb3c

File tree

4 files changed

+145
-145
lines changed

4 files changed

+145
-145
lines changed

src/components/AutoScrollArea.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { css } from '@emotion/react'
12
import { ScrollArea, ScrollAreaProps } from '@radix-ui/themes'
23
import { ReactNode, UIEvent, useRef } from 'react'
34

@@ -50,7 +51,14 @@ export function AutoScrollArea({
5051

5152
return (
5253
<ScrollArea {...props} onScroll={handleScroll}>
53-
<div ref={handleMount}>{children}</div>
54+
<div
55+
ref={handleMount}
56+
css={css`
57+
height: 100%;
58+
`}
59+
>
60+
{children}
61+
</div>
5462
</ScrollArea>
5563
)
5664
}

src/components/Validator/LogsSection.tsx

Lines changed: 133 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { css } from '@emotion/react'
22
import * as ToggleGroup from '@radix-ui/react-toggle-group'
33
import { Flex, Separator, Text, VisuallyHidden } from '@radix-ui/themes'
4-
import { useCallback, useMemo, useState } from 'react'
4+
import { ReactNode, useMemo, useState } from 'react'
55

6-
import { useAutoScroll } from '@/hooks/useAutoScroll'
76
import { LogEntry } from '@/schemas/k6'
87

98
import { AutoScrollArea } from '../AutoScrollArea'
@@ -31,20 +30,6 @@ function formatTime(time: string) {
3130
})
3231
}
3332

34-
function findTableElement(element: HTMLElement): HTMLTableElement | null {
35-
let current: HTMLElement | null = element
36-
37-
while (current !== null) {
38-
if (current instanceof HTMLTableElement) {
39-
return current
40-
}
41-
42-
current = current.parentElement
43-
}
44-
45-
return null
46-
}
47-
4833
/**
4934
* LogEntry has a source property but it's not really reliable and doesn't let us
5035
* distinguish between logs from the browser module and logs from the actual browser,
@@ -74,7 +59,7 @@ function withSource(entry: LogEntry) {
7459
const colors: Record<LogEntry['level'], string> = {
7560
info: 'green',
7661
debug: 'blue',
77-
warning: 'orange',
62+
warning: 'yellow',
7863
error: 'red',
7964
}
8065

@@ -99,15 +84,13 @@ const toggleGroupStyles = css`
9984
const toggleItemStyles = css`
10085
box-sizing: border-box;
10186
padding: var(--space-1) var(--space-2);
102-
font-size: 12px;
103-
font-weight: 500;
87+
font-size: var(--font-size-2);
10488
border: none;
10589
border-radius: var(--radius-2);
10690
cursor: pointer;
10791
color: var(--gray-11);
10892
background-color: transparent;
10993
110-
&:hover,
11194
&[data-state='on'] {
11295
background-color: var(--gray-a4);
11396
}
@@ -125,6 +108,125 @@ export function useConsoleFilter() {
125108
}
126109
}
127110

111+
function LogMessage({ children }: { children: ReactNode }) {
112+
return (
113+
<Text size="2" color="gray" asChild>
114+
<Flex align="center" justify="center" height="100%" flexGrow="1">
115+
{children}
116+
</Flex>
117+
</Text>
118+
)
119+
}
120+
121+
interface LogsContentProps {
122+
filter: ConsoleFilter
123+
logs: LogEntry[]
124+
}
125+
126+
function LogsContent({ filter, logs }: LogsContentProps) {
127+
const filteredLogs = useMemo(() => {
128+
return logs.map(withSource).filter((log) => {
129+
return (
130+
filter.levels.includes(log.entry.level) &&
131+
filter.sources.includes(log.source)
132+
)
133+
})
134+
}, [logs, filter])
135+
136+
if (logs.length === 0) {
137+
return <LogMessage>No logs available.</LogMessage>
138+
}
139+
140+
if (filteredLogs.length === 0) {
141+
return <LogMessage>No logs match the filter.</LogMessage>
142+
}
143+
144+
return (
145+
<table
146+
css={css`
147+
display: grid;
148+
align-items: center;
149+
grid-template-columns: 1fr auto auto;
150+
151+
thead,
152+
tbody,
153+
tr {
154+
display: grid;
155+
grid-column: 1 / -1;
156+
grid-template-columns: subgrid;
157+
}
158+
159+
tr {
160+
padding-right: var(--space-2);
161+
}
162+
163+
td {
164+
font-family: var(--code-font-family);
165+
font-size: 13px;
166+
padding: var(--space-2) var(--space-1);
167+
}
168+
`}
169+
>
170+
<thead>
171+
<tr
172+
css={css`
173+
height: 0;
174+
padding: 0;
175+
margin: 0;
176+
`}
177+
>
178+
<th>
179+
<VisuallyHidden>Message</VisuallyHidden>
180+
</th>
181+
<th>
182+
<VisuallyHidden>Source</VisuallyHidden>
183+
</th>
184+
<th>
185+
<VisuallyHidden>Time</VisuallyHidden>
186+
</th>
187+
</tr>
188+
</thead>
189+
<tbody>
190+
{filteredLogs.map(({ source, entry }, index) => (
191+
<tr key={index}>
192+
<td
193+
css={css`
194+
border-left: 4px solid var(--${colors[entry.level]}-9);
195+
&& {
196+
padding-left: var(--space-2);
197+
}
198+
`}
199+
>
200+
<pre
201+
css={css`
202+
margin: 0;
203+
`}
204+
>
205+
{entry.msg}
206+
</pre>
207+
</td>
208+
<Text
209+
asChild
210+
css={css`
211+
color: var(--gray-a9);
212+
`}
213+
>
214+
<td align="right">[{source}]</td>
215+
</Text>
216+
<Text
217+
asChild
218+
css={css`
219+
color: var(--gray-a9);
220+
`}
221+
>
222+
<td align="right">{formatTime(entry.time)}</td>
223+
</Text>
224+
</tr>
225+
))}
226+
</tbody>
227+
</table>
228+
)
229+
}
128230
interface ConsoleFilter {
129231
levels: Array<LogEntry['level']>
130232
sources: Array<ReturnType<typeof getSource>>
@@ -149,38 +251,6 @@ export function LogsSection({
149251
autoScroll,
150252
onFilterChange,
151253
}: LogsSectionProps) {
152-
const ref = useAutoScroll<HTMLTableElement>(logs, autoScroll)
153-
154-
const filteredLogs = useMemo(() => {
155-
return logs.map(withSource).filter((log) => {
156-
return (
157-
filter.levels.includes(log.entry.level) &&
158-
filter.sources.includes(log.source)
159-
)
160-
})
161-
}, [logs, filter])
162-
163-
// Radix UI's Table component wraps the table element in a ScrollArea but in
164-
// order to autoscroll we need to get a ref to the table element. Ideally Radix
165-
// would provide a way to do this, but instead we have to get the ref to an
166-
// inner element and find the table element from there.
167-
const setTableRef = useCallback(
168-
(element: HTMLTableSectionElement | null) => {
169-
if (element === null) {
170-
return
171-
}
172-
173-
const table = findTableElement(element)
174-
175-
if (table === null) {
176-
return
177-
}
178-
179-
ref.current = table
180-
},
181-
[ref]
182-
)
183-
184254
const handleLogLevelsChange = (values: string[]) => {
185255
onFilterChange({
186256
...filter,
@@ -205,10 +275,19 @@ export function LogsSection({
205275
gap="2"
206276
minHeight="40px"
207277
css={css`
278+
font-size: var(--font-size-2);
279+
line-height: 1em;
208280
flex-shrink: 0;
209281
border-bottom: 1px solid var(--gray-a6);
210282
`}
211283
>
284+
<div
285+
css={css`
286+
padding: var(--space-1) 0;
287+
`}
288+
>
289+
Filters:
290+
</div>
212291
<ToggleGroup.Root
213292
type="multiple"
214293
value={filter.levels}
@@ -282,95 +361,8 @@ export function LogsSection({
282361
)}
283362
</ToggleGroup.Root>
284363
</Flex>
285-
<AutoScrollArea tail items={filteredLogs.length}>
286-
<Flex height="100%">
287-
<table
288-
css={css`
289-
height: 100%;
290-
flex: 1;
291-
292-
display: grid;
293-
align-items: center;
294-
grid-template-columns: 1fr auto auto;
295-
296-
thead,
297-
tbody,
298-
tr {
299-
display: grid;
300-
grid-column: 1 / -1;
301-
grid-template-columns: subgrid;
302-
}
303-
304-
tr {
305-
padding-right: var(--space-2);
306-
}
307-
308-
td {
309-
font-family: var(--code-font-family);
310-
font-size: 13px;
311-
padding: var(--space-2) var(--space-1);
312-
}
313-
`}
314-
>
315-
<thead ref={setTableRef}>
316-
<tr
317-
css={css`
318-
height: 0;
319-
padding: 0;
320-
margin: 0;
321-
`}
322-
>
323-
<th>
324-
<VisuallyHidden>Message</VisuallyHidden>
325-
</th>
326-
<th>
327-
<VisuallyHidden>Source</VisuallyHidden>
328-
</th>
329-
<th>
330-
<VisuallyHidden>Time</VisuallyHidden>
331-
</th>
332-
</tr>
333-
</thead>
334-
<tbody>
335-
{filteredLogs.map(({ source, entry }, index) => (
336-
<tr key={index}>
337-
<td
338-
css={css`
339-
border-left: 3px solid var(--${colors[entry.level]}-9);
340-
&& {
341-
padding-left: var(--space-2);
342-
}
343-
`}
344-
>
345-
<pre
346-
css={css`
347-
margin: 0;
348-
`}
349-
>
350-
{entry.msg}
351-
</pre>
352-
</td>
353-
<Text
354-
asChild
355-
css={css`
356-
color: var(--gray-a9);
357-
`}
358-
>
359-
<td align="right">[{source}]</td>
360-
</Text>
361-
<Text
362-
asChild
363-
css={css`
364-
color: var(--gray-a9);
365-
`}
366-
>
367-
<td align="right">{formatTime(entry.time)}</td>
368-
</Text>
369-
</tr>
370-
))}
371-
</tbody>
372-
</table>
373-
</Flex>
364+
<AutoScrollArea tail={autoScroll} items={logs.length}>
365+
<LogsContent filter={filter} logs={logs} />
374366
</AutoScrollArea>
375367
</Flex>
376368
)

src/hooks/useAutoScroll.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { MutableRefObject, useEffect, useRef } from 'react'
1+
import { RefObject, useEffect, useRef } from 'react'
22

33
export function useAutoScroll<Element extends HTMLElement = HTMLDivElement>(
44
items: unknown,
55
enabled = true
6-
): MutableRefObject<Element | null> {
6+
): RefObject<Element | null> {
77
const bottomRef = useRef<Element>(null)
88

99
useEffect(() => {

src/views/Validator/Browser/BrowserDebugger.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export function BrowserDebugger({
117117
>
118118
<LogsSection
119119
{...consoleFilter}
120-
autoScroll={false}
120+
autoScroll={session.state === 'running'}
121121
logs={session.logs}
122122
/>
123123
</Tabs.Content>

0 commit comments

Comments
 (0)