Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace rainbow bar with moving hill for no-color mode #5262

Merged
merged 5 commits into from
Jan 26, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Simplify calculations using truncation
  • Loading branch information
amcaplan committed Jan 23, 2025
commit 549c88459956d19d4d2336c6ceef3b7687e48e55
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ describe('Tasks', () => {

// Then
expect(unstyled(renderInstance.lastFrame()!)).toMatchInlineSnapshot(`
"▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁
"▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆
task 1 ..."
`)
expect(firstTaskFunction).toHaveBeenCalled()
})

test('shrinks the no-color display for narrow screens', async () => {
test('truncates the no-color display correctly for narrow screens', async () => {
// Given
vi.mocked(useStdout).mockReturnValue({
stdout: new Stdout({
Expand All @@ -95,7 +95,7 @@ describe('Tasks', () => {

// Then
expect(unstyled(renderInstance.lastFrame()!)).toMatchInlineSnapshot(`
"▁▁▂▃▄▅▆▇█▇▆▅▄▃▂
"▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆
task 1 ..."
`)
expect(firstTaskFunction).toHaveBeenCalled()
Expand Down
16 changes: 3 additions & 13 deletions packages/cli-kit/src/private/node/ui/components/Tasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,7 @@ import {Box, Text, useStdin, useInput} from 'ink'
import React, {useRef, useState} from 'react'

const loadingBarChar = '▀'
// Chars that can be arranged to form a colorless horizontal figure that displays progress as it moves.
// The string is 15 chars long so it can always be displayed even within a box inside a 20-char-wide terminal.
const hillString = '▁▂▃▄▅▆▇█▇▆▅▄▃▂▁'
// Like the hill string, but forming a gentler slope for motion that is less jarring
const gradualHillString = hillString
.split('')
.map((char) => char.repeat(2).split(''))
.flat()
.join('')
const hillString = '▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁▁'

export interface Task<TContext = unknown> {
title: string
Expand Down Expand Up @@ -81,9 +73,7 @@ function Tasks<TContext>({
const {twoThirds} = useLayout()
let loadingBar = new Array(twoThirds).fill(loadingBarChar).join('')
if (noColor ?? !shouldDisplayColors()) {
const fittingPattern = gradualHillString.length <= twoThirds ? gradualHillString : hillString
const fullyFittingRepeats = Math.floor(twoThirds / fittingPattern.length)
loadingBar = fittingPattern.repeat(Math.max(1, fullyFittingRepeats))
loadingBar = hillString.repeat(Math.ceil(twoThirds / hillString.length))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rounding up here so we have a bit of extra insurance at the end (which TextAnimation will truncate), rather than overtruncating immediately.

}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const [currentTask, setCurrentTask] = useState<Task<TContext>>(tasks[0]!)
Expand Down Expand Up @@ -138,7 +128,7 @@ function Tasks<TContext>({

return state === TasksState.Loading && !isAborted ? (
<Box flexDirection="column">
<TextAnimation text={loadingBar} />
<TextAnimation text={loadingBar} maxWidth={twoThirds} />
<Text>{currentTask.title} ...</Text>
</Box>
) : null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import gradient from 'gradient-string'

interface TextAnimationProps {
text: string
maxWidth?: number
}

function rainbow(text: string, frame: number) {
Expand All @@ -21,10 +22,14 @@ function rotated(text: string, steps: number) {
return start + end
}

function truncated(text: string, maxWidth: number | undefined): string {
return maxWidth ? text.slice(0, maxWidth) : text
}

/**
* `TextAnimation` applies a rainbow animation to text.
*/
const TextAnimation = memo(({text}: TextAnimationProps): JSX.Element => {
const TextAnimation = memo(({text, maxWidth}: TextAnimationProps): JSX.Element => {
const frame = useRef(0)
const [renderedFrame, setRenderedFrame] = useState(text)
const timeout = useRef<NodeJS.Timeout>()
Expand All @@ -33,12 +38,12 @@ const TextAnimation = memo(({text}: TextAnimationProps): JSX.Element => {
const newFrame = frame.current + 1
frame.current = newFrame

setRenderedFrame(rainbow(rotated(text, frame.current), frame.current))
setRenderedFrame(rainbow(truncated(rotated(text, frame.current), maxWidth), frame.current))

timeout.current = setTimeout(() => {
renderAnimation()
}, 35)
}, [text])
}, [text, maxWidth])

useLayoutEffect(() => {
renderAnimation()
Expand Down
Loading