-
Notifications
You must be signed in to change notification settings - Fork 14
/
cli-loading.ts
119 lines (115 loc) · 3.62 KB
/
cli-loading.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import Spinners from 'cli-spinners'
import logFigures from 'figures'
import { formatDuration, Is } from './utils'
import chalk from 'chalk'
import { DepsTree, TaskState } from './task-manager'
import wcwidth from 'wcwidth'
import stripAnsi from 'strip-ansi'
export interface Props {
depsTree: DepsTree
indent?: number
symbolMap?: { [k: string]: string }
grayState?: TaskState[] | null
stream?: NodeJS.WriteStream
}
export class CliLoading {
props: Required<Props>
_loadingFrameMap = new Map<string, number>()
id?: NodeJS.Timer
linesToClear = 0
constructor(props: Props) {
this.props = {
indent: 3,
symbolMap: {},
grayState: null,
stream: process.stderr as any,
...props,
}
}
count(uid: string) {
let count = this._loadingFrameMap.get(uid) || 0
this._loadingFrameMap.set(uid, count + 1)
return count
}
renderDepsTree(depsTree: DepsTree, output: string[] = []) {
let indent = Array(depsTree.depth * this.props.indent)
.fill(' ')
.join('')
let frames = Spinners.dots.frames
let symbol = {
[TaskState.waiting]: chalk.gray(logFigures.ellipsis),
[TaskState.pending]: chalk.blueBright(logFigures.arrowRight),
[TaskState.loading]: chalk.cyan(frames[this.count(depsTree.uid) % frames.length]),
[TaskState.succeeded]: chalk.green(logFigures.tick),
[TaskState.failed]: chalk.red(logFigures.cross),
[TaskState.skipped]: chalk.yellow(logFigures.info),
...this.props.symbolMap,
}[depsTree.state]
let color =
(this.props.grayState || [TaskState.waiting]).indexOf(depsTree.state) >= 0
? f => chalk.gray(f)
: f => f
let skipped = depsTree.state === TaskState.skipped
output.push(
`${indent}${symbol} ${color(depsTree.task.name)}${skipped ? chalk.gray(` [skipped]`) : ''}${depsTree.priority ? chalk.gray(` [priority: ${depsTree.priority}]`) : ''}${(depsTree.duration>=0) ? chalk.gray(` [duration: ${formatDuration(depsTree.duration)}]`) : ''}`,
)
let childDeps = ([] as DepsTree[])
.concat(...depsTree.asyncDeps)
.concat(depsTree.syncDeps)
for (const child of childDeps) {
this.renderDepsTree(child, output)
}
return output
}
render() {
let columns = this.props.stream.columns || 80
let rows = this.props.stream.rows || Infinity
let output = this.renderDepsTree(this.props.depsTree)
let isFirstRendering = this.linesToClear === 0
output.push('')
let outputLineCounts = output.map(
line => Math.max(1, Math.ceil(wcwidth(stripAnsi(line)) / columns))
)
this.clear()
this.linesToClear = 0
for (let i = outputLineCounts.length - 1; i >= 0; i--) {
const count = outputLineCounts[i]
this.linesToClear += count
if (this.linesToClear > rows) {
this.linesToClear -= count
if (!isFirstRendering) {
output = output.slice(i + 1)
}
break
}
}
this.props.stream.write(output.join('\n'))
}
// Adopted from https://github.com/sindresorhus/ora/blob/bbc82a44884b23f1787d91f95b2d3f93afe653e3/index.js#L74
clear() {
if (!this.props.stream.isTTY) {
return this
}
for (let i = 0; i < this.linesToClear; i++) {
if (i > 0) {
this.props.stream.moveCursor(0, -1)
}
// @ts-ignore
this.props.stream.clearLine && (this.props.stream as any).clearLine()
this.props.stream.cursorTo(0)
}
this.linesToClear = 0
return this
}
start() {
if (this.id) return
this.id = setInterval(() => {
this.render()
}, 100)
}
stop() {
this.id && clearInterval(this.id)
this.id = undefined
this.render()
}
}