Spinner.tsx
1 import { Box, Text } from 'ink' 2 import * as React from 'react' 3 import { useEffect, useRef, useState } from 'react' 4 import { getTheme } from '../utils/theme.js' 5 import { sample } from 'lodash-es' 6 7 // NB: The third character in this string is an emoji that 8 // renders on Windows consoles with a green background 9 const CHARACTERS = 10 process.platform === 'darwin' 11 ? ['·', '✢', '✳', '∗', '✻', '✽'] 12 : ['·', '✢', '*', '∗', '✻', '✽'] 13 14 const MESSAGES = [ 15 'Accomplishing', 16 'Actioning', 17 'Actualizing', 18 'Baking', 19 'Brewing', 20 'Calculating', 21 'Cerebrating', 22 'Churning', 23 'Clauding', 24 'Coalescing', 25 'Cogitating', 26 'Computing', 27 'Conjuring', 28 'Considering', 29 'Cooking', 30 'Crafting', 31 'Creating', 32 'Crunching', 33 'Deliberating', 34 'Determining', 35 'Doing', 36 'Effecting', 37 'Finagling', 38 'Forging', 39 'Forming', 40 'Generating', 41 'Hatching', 42 'Herding', 43 'Honking', 44 'Hustling', 45 'Ideating', 46 'Inferring', 47 'Manifesting', 48 'Marinating', 49 'Moseying', 50 'Mulling', 51 'Mustering', 52 'Musing', 53 'Noodling', 54 'Percolating', 55 'Pondering', 56 'Processing', 57 'Puttering', 58 'Reticulating', 59 'Ruminating', 60 'Schlepping', 61 'Shucking', 62 'Simmering', 63 'Smooshing', 64 'Spinning', 65 'Stewing', 66 'Synthesizing', 67 'Thinking', 68 'Transmuting', 69 'Vibing', 70 'Working', 71 ] 72 73 export function Spinner(): React.ReactNode { 74 const frames = [...CHARACTERS, ...[...CHARACTERS].reverse()] 75 const [frame, setFrame] = useState(0) 76 const [elapsedTime, setElapsedTime] = useState(0) 77 const message = useRef(sample(MESSAGES)) 78 const startTime = useRef(Date.now()) 79 80 useEffect(() => { 81 const timer = setInterval(() => { 82 setFrame(f => (f + 1) % frames.length) 83 }, 120) 84 85 return () => clearInterval(timer) 86 }, [frames.length]) 87 88 useEffect(() => { 89 const timer = setInterval(() => { 90 setElapsedTime(Math.floor((Date.now() - startTime.current) / 1000)) 91 }, 1000) 92 93 return () => clearInterval(timer) 94 }, []) 95 96 return ( 97 <Box flexDirection="row" marginTop={1}> 98 <Box flexWrap="nowrap" height={1} width={2}> 99 <Text color={getTheme().claude}>{frames[frame]}</Text> 100 </Box> 101 <Text color={getTheme().claude}>{message.current}… </Text> 102 <Text color={getTheme().secondaryText}> 103 ({elapsedTime}s · <Text bold>esc</Text> to interrupt) 104 </Text> 105 </Box> 106 ) 107 } 108 109 export function SimpleSpinner(): React.ReactNode { 110 const frames = [...CHARACTERS, ...[...CHARACTERS].reverse()] 111 const [frame, setFrame] = useState(0) 112 113 useEffect(() => { 114 const timer = setInterval(() => { 115 setFrame(f => (f + 1) % frames.length) 116 }, 120) 117 118 return () => clearInterval(timer) 119 }, [frames.length]) 120 121 return ( 122 <Box flexWrap="nowrap" height={1} width={2}> 123 <Text color={getTheme().claude}>{frames[frame]}</Text> 124 </Box> 125 ) 126 }