UNPKG

4.09 kBJavaScriptView Raw
1import { cursor } from '@arpel/escape'
2import { Escape } from '../util/Escape.js'
3import { IO } from '../util/IO.js'
4import { Config } from './Config.js'
5import { Layout } from './Layout.js'
6import { State } from './State.js'
7
8// Baro constructor
9export class Baro {
10 config
11 format
12 states = []
13 escape = Escape.build({
14 fn: this.#renderStates,
15 ctx: this,
16 arg: this.states
17 })
18 #locker = null
19 offset = 0
20 /**
21 *
22 * @param {Config} config
23 * @param {Layout} layout
24 */
25 constructor(config, layout) {
26 this.config = config
27 this.layout = layout
28 this.io = IO.build(this.config)
29 }
30
31 static build(config, layout) {
32 config = Config.build(config)
33 layout = Layout.build(layout)
34 return new Baro(config, layout)
35 }
36
37 get active() { return this.escape.active }
38
39 get forceRedraw() {
40 return this.config.forceRedraw || ( this.config.noTTYOutput && !this.io.isTTY ) // force redraw in noTTY-mode!
41 }
42
43 async start() {
44 const { io } = this
45 io.input.resume()
46 if (this.config.hideCursor) io.showCursor(false) // hide the cursor ?
47 if (!this.config.lineWrap) io.setLineWrap(false) // disable line wrapping ?
48 // this.io.output.write('\f')
49 const height = this.io.height
50 this.io.output.write(cursor.nextLine(height) + cursor.prevLine(height))
51 // this.io.output.write(scroll.down(height))
52
53 // const [ x, y ] = await io.asyncCursorPos()
54 // console.log('x', x, 'y', y, 'offset', this.offset, 'states', this.states.length, 'height', io.height)
55 // if (x + this.states.length >= io.height) {
56 // io.output.write('\f')
57 // // io.nextPage()
58 // }
59
60 // WARNING: intentionally call loop without await
61 this.escape.loop(this.config.throttle) // initialize update timer
62 }
63
64 /**
65 * add a new bar to the stack
66 * @param {State|object} state // const state = new State(total, value, this.config.eta)
67 * @returns {State|object}
68 */
69 async append(state) {
70 if (this.#locker) await this.#locker // console.debug('>>', state.agent, 'waiting for occupy')
71 const taskPromise = Promise
72 .resolve()
73 .then(async () => {
74 state.last = Number.NEGATIVE_INFINITY
75 this.states.push(state)
76 if (!this.escape.active && this.states.length) await this.start()
77 })
78 this.#locker = taskPromise.then(() => this.#locker = null)
79 return state
80 }
81
82 // remove a bar from the stack
83 remove(state) {
84 const index = this.states.indexOf(state) // find element
85 if (index < 0) return false // element found ?
86 this.states.splice(index, 1) // remove element
87 this.io.nextLine()
88 this.io.clearDown()
89 return true
90 }
91
92 async stop() {
93 this.escape.stop() // stop timer
94 if (this.config.hideCursor) { this.io.showCursor(true) } // cursor hidden ?
95 if (!this.config.lineWrap) { this.io.setLineWrap(true) } // re-enable line wrapping ?
96 if (this.config.autoClear) {
97 this.io.resetCursor() // reset cursor
98 this.io.clearDown()
99 } // clear all bars or show final progress
100 else {
101 // for (let state of this.states) { state.stop() }
102 await this.#renderStates(this.states)
103 }
104
105 this.io.input.pause()
106 }
107
108 async #renderStates(states) {
109 const { io } = this
110 const height = io.height - 1
111 const [ x, y ] = await io.asyncCursorPos()
112 // if (!this.busy && ( x + states.length > height )) {
113 // io.nextPage()
114 // this.offset = 0
115 // }
116 // else {
117 // this.busy = true
118 //
119 // }
120
121 io.offsetLines(-Math.min(this.offset, height)) // reset cursor
122 this.offset = 0
123 if (height) {
124 for (const state of states.slice(-( height ))) {
125 if (this.forceRedraw || ( state.value !== state.last )) {
126 io.writeOff(`CURSOR (${x}, ${y}) OFFSET (${this.offset}) TERM (${io.size}) ` + this.layout.format(state))
127 state.last = state.value
128 }
129 io.nextLine()
130 this.offset++
131 }
132 }
133 if (this.config.autoStop && states.every(state => state.reachLimit)) {
134 await this.stop()
135 } // stop if autoStop and all bars stopped
136 }
137}