130 lines
2.9 KiB
TypeScript
130 lines
2.9 KiB
TypeScript
import { Box, Text } from 'ink'
|
||
import * as React from 'react'
|
||
import { useEffect, useRef, useState } from 'react'
|
||
import { getTheme } from '@utils/theme'
|
||
import { sample } from 'lodash-es'
|
||
import { getSessionState } from '@utils/sessionState'
|
||
// NB: The third character in this string is an emoji that
|
||
// renders on Windows consoles with a green background
|
||
const CHARACTERS =
|
||
process.platform === 'darwin'
|
||
? ['·', '✢', '✳', '∗', '✻', '✽']
|
||
: ['·', '✢', '*', '∗', '✻', '✽']
|
||
|
||
const MESSAGES = [
|
||
'Accomplishing',
|
||
'Actioning',
|
||
'Actualizing',
|
||
'Baking',
|
||
'Brewing',
|
||
'Calculating',
|
||
'Cerebrating',
|
||
'Churning',
|
||
'Coding',
|
||
'Coalescing',
|
||
'Cogitating',
|
||
'Computing',
|
||
'Conjuring',
|
||
'Considering',
|
||
'Cooking',
|
||
'Crafting',
|
||
'Creating',
|
||
'Crunching',
|
||
'Deliberating',
|
||
'Determining',
|
||
'Doing',
|
||
'Effecting',
|
||
'Finagling',
|
||
'Forging',
|
||
'Forming',
|
||
'Generating',
|
||
'Hatching',
|
||
'Herding',
|
||
'Honking',
|
||
'Hustling',
|
||
'Ideating',
|
||
'Inferring',
|
||
'Manifesting',
|
||
'Marinating',
|
||
'Moseying',
|
||
'Mulling',
|
||
'Mustering',
|
||
'Musing',
|
||
'Noodling',
|
||
'Percolating',
|
||
'Pondering',
|
||
'Processing',
|
||
'Puttering',
|
||
'Reticulating',
|
||
'Ruminating',
|
||
'Schlepping',
|
||
'Shucking',
|
||
'Simmering',
|
||
'Smooshing',
|
||
'Spinning',
|
||
'Stewing',
|
||
'Synthesizing',
|
||
'Thinking',
|
||
'Transmuting',
|
||
'Vibing',
|
||
'Working',
|
||
]
|
||
|
||
export function Spinner(): React.ReactNode {
|
||
const frames = [...CHARACTERS, ...[...CHARACTERS].reverse()]
|
||
const [frame, setFrame] = useState(0)
|
||
const [elapsedTime, setElapsedTime] = useState(0)
|
||
const message = useRef(sample(MESSAGES))
|
||
const startTime = useRef(Date.now())
|
||
|
||
useEffect(() => {
|
||
const timer = setInterval(() => {
|
||
setFrame(f => (f + 1) % frames.length)
|
||
}, 120)
|
||
|
||
return () => clearInterval(timer)
|
||
}, [frames.length])
|
||
|
||
useEffect(() => {
|
||
const timer = setInterval(() => {
|
||
setElapsedTime(Math.floor((Date.now() - startTime.current) / 1000))
|
||
}, 1000)
|
||
|
||
return () => clearInterval(timer)
|
||
}, [])
|
||
|
||
return (
|
||
<Box flexDirection="row" marginTop={1}>
|
||
<Box flexWrap="nowrap" height={1} width={2}>
|
||
<Text color={getTheme().kode}>{frames[frame]}</Text>
|
||
</Box>
|
||
<Text color={getTheme().kode}>{message.current}… </Text>
|
||
<Text color={getTheme().secondaryText}>
|
||
({elapsedTime}s · <Text bold>esc</Text> to interrupt)
|
||
</Text>
|
||
<Text color={getTheme().secondaryText}>
|
||
· {getSessionState('currentError')}
|
||
</Text>
|
||
</Box>
|
||
)
|
||
}
|
||
|
||
export function SimpleSpinner(): React.ReactNode {
|
||
const frames = [...CHARACTERS, ...[...CHARACTERS].reverse()]
|
||
const [frame, setFrame] = useState(0)
|
||
|
||
useEffect(() => {
|
||
const timer = setInterval(() => {
|
||
setFrame(f => (f + 1) % frames.length)
|
||
}, 120)
|
||
|
||
return () => clearInterval(timer)
|
||
}, [frames.length])
|
||
|
||
return (
|
||
<Box flexWrap="nowrap" height={1} width={2}>
|
||
<Text color={getTheme().kode}>{frames[frame]}</Text>
|
||
</Box>
|
||
)
|
||
}
|