numux 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/ui/app.ts +4 -28
- package/src/ui/status-bar.ts +3 -64
package/package.json
CHANGED
package/src/ui/app.ts
CHANGED
|
@@ -20,7 +20,6 @@ export class App {
|
|
|
20
20
|
private sidebarWidth = 20
|
|
21
21
|
|
|
22
22
|
private config: ResolvedNumuxConfig
|
|
23
|
-
private processHexColors: Map<string, string>
|
|
24
23
|
|
|
25
24
|
private resizeTimer: ReturnType<typeof setTimeout> | null = null
|
|
26
25
|
|
|
@@ -34,7 +33,6 @@ export class App {
|
|
|
34
33
|
this.manager = manager
|
|
35
34
|
this.config = config
|
|
36
35
|
this.names = manager.getProcessNames()
|
|
37
|
-
this.processHexColors = buildProcessHexColorMap(this.names, config)
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
async start(): Promise<void> {
|
|
@@ -60,7 +58,8 @@ export class App {
|
|
|
60
58
|
})
|
|
61
59
|
|
|
62
60
|
// Tab bar (vertical sidebar)
|
|
63
|
-
|
|
61
|
+
const processHexColors = buildProcessHexColorMap(this.names, this.config)
|
|
62
|
+
this.tabBar = new TabBar(this.renderer, this.names, processHexColors)
|
|
64
63
|
|
|
65
64
|
// Content row: sidebar | pane
|
|
66
65
|
const contentRow = new BoxRenderable(this.renderer, {
|
|
@@ -91,15 +90,12 @@ export class App {
|
|
|
91
90
|
for (const name of this.names) {
|
|
92
91
|
const interactive = this.config.processes[name].interactive === true
|
|
93
92
|
const pane = new Pane(this.renderer, name, termCols, termRows, interactive)
|
|
94
|
-
pane.onScroll(() => {
|
|
95
|
-
if (name === this.activePane) this.updateScrollIndicator()
|
|
96
|
-
})
|
|
97
93
|
this.panes.set(name, pane)
|
|
98
94
|
paneContainer.add(pane.scrollBox)
|
|
99
95
|
}
|
|
100
96
|
|
|
101
|
-
// Status bar
|
|
102
|
-
this.statusBar = new StatusBar(this.renderer
|
|
97
|
+
// Status bar (only visible during search)
|
|
98
|
+
this.statusBar = new StatusBar(this.renderer)
|
|
103
99
|
|
|
104
100
|
// Assemble layout
|
|
105
101
|
contentRow.add(sidebar)
|
|
@@ -117,13 +113,9 @@ export class App {
|
|
|
117
113
|
if (this.destroyed) return
|
|
118
114
|
if (event.type === 'output') {
|
|
119
115
|
this.panes.get(event.name)?.feed(event.data)
|
|
120
|
-
if (event.name === this.activePane) {
|
|
121
|
-
this.updateScrollIndicator()
|
|
122
|
-
}
|
|
123
116
|
} else if (event.type === 'status') {
|
|
124
117
|
const state = this.manager.getState(event.name)
|
|
125
118
|
this.tabBar.updateStatus(event.name, event.status, state?.exitCode, state?.restartCount)
|
|
126
|
-
this.statusBar.updateStatus(event.name, event.status)
|
|
127
119
|
}
|
|
128
120
|
})
|
|
129
121
|
|
|
@@ -227,19 +219,16 @@ export class App {
|
|
|
227
219
|
const pane = this.panes.get(this.activePane)
|
|
228
220
|
const delta = this.termRows - 2
|
|
229
221
|
pane?.scrollBy(key.name === 'pageup' ? -delta : delta)
|
|
230
|
-
this.updateScrollIndicator()
|
|
231
222
|
return
|
|
232
223
|
}
|
|
233
224
|
|
|
234
225
|
// Alt+Home/End: scroll to top/bottom
|
|
235
226
|
if (this.activePane && key.name === 'home') {
|
|
236
227
|
this.panes.get(this.activePane)?.scrollToTop()
|
|
237
|
-
this.updateScrollIndicator()
|
|
238
228
|
return
|
|
239
229
|
}
|
|
240
230
|
if (this.activePane && key.name === 'end') {
|
|
241
231
|
this.panes.get(this.activePane)?.scrollToBottom()
|
|
242
|
-
this.updateScrollIndicator()
|
|
243
232
|
return
|
|
244
233
|
}
|
|
245
234
|
}
|
|
@@ -253,18 +242,14 @@ export class App {
|
|
|
253
242
|
if (key.name === 'up' || key.name === 'down') {
|
|
254
243
|
const pane = this.panes.get(this.activePane)
|
|
255
244
|
pane?.scrollBy(key.name === 'up' ? -1 : 1)
|
|
256
|
-
this.updateScrollIndicator()
|
|
257
245
|
} else if (key.name === 'pageup' || key.name === 'pagedown') {
|
|
258
246
|
const pane = this.panes.get(this.activePane)
|
|
259
247
|
const delta = this.termRows - 2
|
|
260
248
|
pane?.scrollBy(key.name === 'pageup' ? -delta : delta)
|
|
261
|
-
this.updateScrollIndicator()
|
|
262
249
|
} else if (key.name === 'home') {
|
|
263
250
|
this.panes.get(this.activePane)?.scrollToTop()
|
|
264
|
-
this.updateScrollIndicator()
|
|
265
251
|
} else if (key.name === 'end') {
|
|
266
252
|
this.panes.get(this.activePane)?.scrollToBottom()
|
|
267
|
-
this.updateScrollIndicator()
|
|
268
253
|
}
|
|
269
254
|
return
|
|
270
255
|
}
|
|
@@ -296,14 +281,6 @@ export class App {
|
|
|
296
281
|
}
|
|
297
282
|
this.activePane = name
|
|
298
283
|
this.panes.get(name)?.show()
|
|
299
|
-
this.updateScrollIndicator()
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
private updateScrollIndicator(): void {
|
|
303
|
-
if (!this.activePane) return
|
|
304
|
-
const pane = this.panes.get(this.activePane)
|
|
305
|
-
if (!pane) return
|
|
306
|
-
this.statusBar.setScrollIndicator(!pane.isAtBottom)
|
|
307
284
|
}
|
|
308
285
|
|
|
309
286
|
private enterSearch(): void {
|
|
@@ -398,7 +375,6 @@ export class App {
|
|
|
398
375
|
if (!pane) return
|
|
399
376
|
const match = this.searchMatches[this.searchIndex]
|
|
400
377
|
pane.scrollToLine(match.line)
|
|
401
|
-
this.updateScrollIndicator()
|
|
402
378
|
}
|
|
403
379
|
|
|
404
380
|
async shutdown(): Promise<void> {
|
package/src/ui/status-bar.ts
CHANGED
|
@@ -1,24 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type CliRenderer,
|
|
3
|
-
cyan,
|
|
4
|
-
fg,
|
|
5
|
-
green,
|
|
6
|
-
red,
|
|
7
|
-
reverse,
|
|
8
|
-
StyledText,
|
|
9
|
-
type TextChunk,
|
|
10
|
-
TextRenderable,
|
|
11
|
-
yellow
|
|
12
|
-
} from '@opentui/core'
|
|
13
|
-
import type { ProcessStatus } from '../types'
|
|
14
|
-
|
|
15
|
-
const STATUS_STYLE: Partial<Record<ProcessStatus, (input: string) => TextChunk>> = {
|
|
16
|
-
ready: green,
|
|
17
|
-
running: cyan,
|
|
18
|
-
failed: red,
|
|
19
|
-
stopped: fg('#888'),
|
|
20
|
-
skipped: fg('#888')
|
|
21
|
-
}
|
|
1
|
+
import { type CliRenderer, cyan, red, reverse, StyledText, type TextChunk, TextRenderable, yellow } from '@opentui/core'
|
|
22
2
|
|
|
23
3
|
function plain(text: string): TextChunk {
|
|
24
4
|
return { __isChunk: true, text } as TextChunk
|
|
@@ -26,20 +6,12 @@ function plain(text: string): TextChunk {
|
|
|
26
6
|
|
|
27
7
|
export class StatusBar {
|
|
28
8
|
readonly renderable: TextRenderable
|
|
29
|
-
private statuses = new Map<string, ProcessStatus>()
|
|
30
|
-
private colors: Map<string, string>
|
|
31
|
-
private scrolledUp = false
|
|
32
9
|
private _searchMode = false
|
|
33
10
|
private _searchQuery = ''
|
|
34
11
|
private _searchMatchCount = 0
|
|
35
12
|
private _searchCurrentIndex = -1
|
|
36
13
|
|
|
37
|
-
constructor(renderer: CliRenderer
|
|
38
|
-
this.colors = colors ?? new Map()
|
|
39
|
-
for (const name of names) {
|
|
40
|
-
this.statuses.set(name, 'pending')
|
|
41
|
-
}
|
|
42
|
-
|
|
14
|
+
constructor(renderer: CliRenderer) {
|
|
43
15
|
this.renderable = new TextRenderable(renderer, {
|
|
44
16
|
id: 'status-bar',
|
|
45
17
|
width: '100%',
|
|
@@ -50,17 +22,6 @@ export class StatusBar {
|
|
|
50
22
|
})
|
|
51
23
|
}
|
|
52
24
|
|
|
53
|
-
updateStatus(name: string, status: ProcessStatus): void {
|
|
54
|
-
this.statuses.set(name, status)
|
|
55
|
-
this.renderable.content = this.buildContent()
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
setScrollIndicator(scrolledUp: boolean): void {
|
|
59
|
-
if (this.scrolledUp === scrolledUp) return
|
|
60
|
-
this.scrolledUp = scrolledUp
|
|
61
|
-
this.renderable.content = this.buildContent()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
25
|
setSearchMode(active: boolean, query = '', matchCount = 0, currentIndex = -1): void {
|
|
65
26
|
this._searchMode = active
|
|
66
27
|
this._searchQuery = query
|
|
@@ -73,29 +34,7 @@ export class StatusBar {
|
|
|
73
34
|
if (this._searchMode) {
|
|
74
35
|
return this.buildSearchContent()
|
|
75
36
|
}
|
|
76
|
-
|
|
77
|
-
let first = true
|
|
78
|
-
for (const [name, status] of this.statuses) {
|
|
79
|
-
if (!first) chunks.push(plain(' '))
|
|
80
|
-
first = false
|
|
81
|
-
const styleFn = STATUS_STYLE[status]
|
|
82
|
-
const hexColor = this.colors.get(name)
|
|
83
|
-
if (styleFn) {
|
|
84
|
-
chunks.push(styleFn(`${name}:${status}`))
|
|
85
|
-
} else if (hexColor) {
|
|
86
|
-
chunks.push(fg(hexColor)(`${name}:${status}`))
|
|
87
|
-
} else {
|
|
88
|
-
chunks.push(plain(`${name}:${status}`))
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
if (this.scrolledUp) {
|
|
92
|
-
chunks.push(plain(' '))
|
|
93
|
-
chunks.push(yellow('[scrolled]'))
|
|
94
|
-
}
|
|
95
|
-
chunks.push(
|
|
96
|
-
plain(' Alt+\u2190\u2192/1-9: tabs Alt+PgUp/Dn: scroll Alt+R: restart Alt+S: stop/start Ctrl+C: quit')
|
|
97
|
-
)
|
|
98
|
-
return new StyledText(chunks)
|
|
37
|
+
return new StyledText([plain('')])
|
|
99
38
|
}
|
|
100
39
|
|
|
101
40
|
private buildSearchContent(): StyledText {
|