179 lines
4.3 KiB
TypeScript
179 lines
4.3 KiB
TypeScript
import { useRef, useLayoutEffect, ReactNode, FC, useState } from 'react'
|
|
import { IconProps, Notepad, Prohibit } from 'phosphor-react'
|
|
import useStayScrolled from 'react-stay-scrolled'
|
|
import NextLink from 'next/link'
|
|
|
|
import Container from './Container'
|
|
import LogText from './LogText'
|
|
import { ILog } from '../state'
|
|
import { Pre, Link, Heading, Button, Text, Flex, Box } from '.'
|
|
|
|
interface ILogBox {
|
|
title: string
|
|
clearLog?: () => void
|
|
logs: ILog[]
|
|
renderNav?: () => ReactNode
|
|
enhanced?: boolean
|
|
Icon?: FC<IconProps>
|
|
}
|
|
|
|
const LogBox: FC<ILogBox> = ({
|
|
title,
|
|
clearLog,
|
|
logs,
|
|
children,
|
|
renderNav,
|
|
enhanced,
|
|
Icon = Notepad
|
|
}) => {
|
|
const logRef = useRef<HTMLPreElement>(null)
|
|
const { stayScrolled /*, scrollBottom*/ } = useStayScrolled(logRef)
|
|
|
|
useLayoutEffect(() => {
|
|
stayScrolled()
|
|
}, [stayScrolled, logs])
|
|
|
|
return (
|
|
<Flex
|
|
as="div"
|
|
css={{
|
|
display: 'flex',
|
|
borderTop: '1px solid $mauve6',
|
|
background: '$mauve1',
|
|
position: 'relative',
|
|
flex: 1,
|
|
height: '100%'
|
|
}}
|
|
>
|
|
<Container
|
|
css={{
|
|
px: 0,
|
|
height: '100%'
|
|
}}
|
|
>
|
|
<Flex
|
|
fluid
|
|
css={{
|
|
height: '48px',
|
|
alignItems: 'center',
|
|
fontSize: '$sm',
|
|
fontWeight: 300
|
|
}}
|
|
>
|
|
<Heading
|
|
as="h3"
|
|
css={{
|
|
fontWeight: 300,
|
|
m: 0,
|
|
fontSize: '11px',
|
|
color: '$mauve12',
|
|
px: '$3',
|
|
textTransform: 'uppercase',
|
|
alignItems: 'center',
|
|
display: 'inline-flex',
|
|
gap: '$3'
|
|
}}
|
|
>
|
|
<Icon size="15px" /> <Text css={{ lineHeight: 1 }}>{title}</Text>
|
|
</Heading>
|
|
<Flex
|
|
row
|
|
align="center"
|
|
// css={{
|
|
// maxWidth: "100%", // TODO make it max without breaking layout!
|
|
// }}
|
|
>
|
|
{renderNav?.()}
|
|
</Flex>
|
|
<Flex css={{ ml: 'auto', gap: '$3', marginRight: '$3' }}>
|
|
{clearLog && (
|
|
<Button ghost size="xs" onClick={clearLog}>
|
|
<Prohibit size="14px" />
|
|
</Button>
|
|
)}
|
|
</Flex>
|
|
</Flex>
|
|
|
|
<Box
|
|
as="pre"
|
|
ref={logRef}
|
|
css={{
|
|
margin: 0,
|
|
// display: "inline-block",
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
width: '100%',
|
|
height: 'calc(100% - 48px)', // 100% minus the logbox header height
|
|
overflowY: 'auto',
|
|
fontSize: '13px',
|
|
fontWeight: '$body',
|
|
fontFamily: '$monospace',
|
|
px: '$3',
|
|
pb: '$2',
|
|
whiteSpace: 'normal'
|
|
}}
|
|
>
|
|
{logs?.map((log, index) => (
|
|
<Box
|
|
as="span"
|
|
key={log.type + index}
|
|
css={{
|
|
'@hover': {
|
|
'&:hover': {
|
|
backgroundColor: enhanced ? '$backgroundAlt' : undefined
|
|
}
|
|
},
|
|
p: enhanced ? '$1' : undefined,
|
|
my: enhanced ? '$1' : undefined
|
|
}}
|
|
>
|
|
<Log {...log} />
|
|
</Box>
|
|
))}
|
|
{children}
|
|
</Box>
|
|
</Container>
|
|
</Flex>
|
|
)
|
|
}
|
|
|
|
export const Log: FC<ILog> = ({
|
|
type,
|
|
timestring,
|
|
message,
|
|
link,
|
|
linkText,
|
|
defaultCollapsed,
|
|
jsonData
|
|
}) => {
|
|
const [expanded, setExpanded] = useState(!defaultCollapsed)
|
|
|
|
if (message === undefined) message = <Text muted>{'undefined'}</Text>
|
|
else if (message === '') message = <Text muted>{'""'}</Text>
|
|
return (
|
|
<>
|
|
<LogText variant={type}>
|
|
{timestring && (
|
|
<Text muted monospace>
|
|
{timestring}{' '}
|
|
</Text>
|
|
)}
|
|
<Pre>{message}</Pre>
|
|
{link && (
|
|
<NextLink legacyBehavior href={link} shallow passHref>
|
|
<Link as="a">{linkText}</Link>
|
|
</NextLink>
|
|
)}
|
|
{jsonData && (
|
|
<Link onClick={() => setExpanded(!expanded)} as="a">
|
|
{expanded ? 'Collapse' : 'Expand'}
|
|
</Link>
|
|
)}
|
|
{expanded && jsonData && <Pre block>{jsonData}</Pre>}
|
|
</LogText>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default LogBox
|