DEV Community

Cover image for ASCII progress indicators
Frank Fiegel
Frank Fiegel

Posted on • Originally published at glama.ai

ASCII progress indicators

I have always been fascinated by old terminal interfaces and wanted to bring some of their charm to the Glama chat UI. To indicate the progress of AI responses, I use ASCII progress indicators.

The animations are inspired by these two articles:

I transformed the concept into a React component.

Below is a collection of progress indicators:

ASCII progress indicators

Each animation is a sequence of characters that rotate at a set interval. For example, the characters ⣾, ⣽, ⣻, ⢿, ⡿, ⣟, ⣯, ⣷ become
Image description.

Here is the component code:

import { useEffect, useRef, useState } from 'react'; type ProgressIndicatorStyle = { frames: string[]; interval: number; }; const styles = { arrow: { frames: ['', '', '', '', '', '', '', ''], interval: 100, }, ball_wave: { frames: ['𓃉𓃉𓃉', '𓃉𓃉∘', '𓃉∘°', '∘°∘', '°∘𓃉', '∘𓃉𓃉'], interval: 100, }, blocks1: { frames: ['', '', '', ''], interval: 100, }, blocks2: { frames: ['','','',''], interval: 100, }, cym: { frames: ['', '', '', ''], interval: 100, }, dots1: { frames: ['', '', '', '', '', '', '', ''], interval: 50, }, dots2: { frames: ['', '', '', '', '', '', '', '', '', ''], interval: 50, }, dots3: { frames: ['', '', '', '', '', '', '', '', '', ''], interval: 50, }, dots4: { frames: [ '', '', '', '', '', '', '', '', '', '', '', '', '', '', ], interval: 50, }, dots5: { frames: [ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ], interval: 50, }, dots6: { frames: [ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ], interval: 50, }, dots7: { frames: [ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ], interval: 50, }, dots8: { frames: [ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ], interval: 50, }, dots9: { frames: ['', '', '', '', '', '', '', ''], interval: 50, }, dots10: { frames: ['', '', '', '', '', '', ''], interval: 50, }, dots11: { frames: ['', '', '', '', '', '', '', ''], interval: 50, }, emoji_blink: { frames: ['😐', '😐', '😐', '😐', '😐', '😐', '😐', '😐', '😐', '😐', '😑'], interval: 100, }, emoji_bomb: { frames: [ '💣 ', ' 💣 ', ' 💣 ', ' 💣', ' 💣', ' 💣', ' 💣', ' 💣', ' 💥', ' ', ' ', ], interval: 100, }, emoji_earth: { frames: ['🌍', '🌎', '🌏'], interval: 200, }, emoji_hour: { frames: [ '🕛', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚', ], interval: 100, }, emoji_moon: { frames: ['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘'], interval: 200, }, line: { frames: ['', '', '', '', '', ''], interval: 100, }, old: { frames: ['', '\\', '|', '/'], interval: 100, }, x_plus: { frames: ['×', '+'], interval: 100, }, } satisfies Record<string, ProgressIndicatorStyle>; const useInterval = (callback: () => void, delay: null | number) => { const savedCallback = useRef(callback); useEffect(() => { savedCallback.current = callback; }, [callback]); useEffect(() => { if (delay === null) { return undefined; } const id = setInterval(() => savedCallback.current(), delay); return () => { clearInterval(id); }; }, [delay]); }; export const ProgressIndicator = ({ style, }: { style: keyof typeof styles; }) => { const { frames, interval } = styles[style]; if (!style) { throw new Error('Invalid style index'); } const [index, setIndex] = useState<number>(0); useInterval(() => { setIndex((index + 1) % frames.length); }, interval); return ( <div style={{ color: '#00d992', fontFamily: 'monospace', pointerEvents: 'none', textAlign: 'center', userSelect: 'none', whiteSpace: 'pre', width: '24px', }} > {frames[index]} </div>  ); }; 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)