152 lines
3.6 KiB
TypeScript
152 lines
3.6 KiB
TypeScript
/**
|
|
* RoadmapPanel Widget - Displays phase roadmap with completion status
|
|
*
|
|
* Features:
|
|
* - Phase tracking with visual indicators
|
|
* - Current phase highlighting
|
|
* - Completed phase markers
|
|
* - Auto-updating current phase
|
|
*/
|
|
|
|
import * as blessed from 'blessed'
|
|
|
|
// =============================================================================
|
|
// Types
|
|
// =============================================================================
|
|
|
|
export interface PhaseInfo {
|
|
index: number
|
|
name: string
|
|
completed: boolean
|
|
}
|
|
|
|
export interface RoadmapPanelOptions {
|
|
/** Label for the panel */
|
|
label?: string
|
|
/** Box options */
|
|
boxOptions?: blessed.Widgets.BoxOptions
|
|
}
|
|
|
|
// =============================================================================
|
|
// RoadmapPanel Widget
|
|
// =============================================================================
|
|
|
|
export class RoadmapPanel {
|
|
private box: blessed.Widgets.BoxElement
|
|
private phases: PhaseInfo[] = []
|
|
private currentPhase = 0
|
|
|
|
constructor(parent: blessed.Widgets.Screen | blessed.Widgets.Node, options: RoadmapPanelOptions = {}) {
|
|
this.box = blessed.box({
|
|
parent,
|
|
label: ` ${options.label ?? 'Roadmap'} `,
|
|
border: { type: 'line' },
|
|
style: {
|
|
border: { fg: 'cyan' },
|
|
label: { fg: 'white', bold: true } as unknown as string, // blessed supports object form
|
|
},
|
|
tags: true,
|
|
...options.boxOptions,
|
|
})
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Public API
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Set or update a phase
|
|
*/
|
|
setPhase(index: number, name: string, completed = false): void {
|
|
const existing = this.phases.find(p => p.index === index)
|
|
if (existing) {
|
|
existing.name = name
|
|
existing.completed = completed
|
|
} else {
|
|
this.phases.push({ index, name, completed })
|
|
this.phases.sort((a, b) => a.index - b.index)
|
|
}
|
|
this.render()
|
|
}
|
|
|
|
/**
|
|
* Set the current phase (also marks previous phases as completed)
|
|
*/
|
|
setCurrentPhase(index: number, name?: string): void {
|
|
this.currentPhase = index
|
|
|
|
// Mark previous phases as completed
|
|
for (const phase of this.phases) {
|
|
if (phase.index < index) {
|
|
phase.completed = true
|
|
}
|
|
}
|
|
|
|
// Update or add current phase
|
|
if (name) {
|
|
this.setPhase(index, name, false)
|
|
}
|
|
|
|
this.render()
|
|
}
|
|
|
|
/**
|
|
* Mark a phase as completed
|
|
*/
|
|
completePhase(index: number): void {
|
|
const phase = this.phases.find(p => p.index === index)
|
|
if (phase) {
|
|
phase.completed = true
|
|
this.render()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear all phases
|
|
*/
|
|
clear(): void {
|
|
this.phases = []
|
|
this.currentPhase = 0
|
|
this.box.setContent('')
|
|
this.box.screen.render()
|
|
}
|
|
|
|
/**
|
|
* Render the widget
|
|
*/
|
|
render(): void {
|
|
const lines: string[] = []
|
|
|
|
for (const phase of this.phases) {
|
|
const isCurrent = phase.index === this.currentPhase
|
|
let symbol: string
|
|
let color: string
|
|
|
|
if (phase.completed) {
|
|
symbol = '✓'
|
|
color = 'green'
|
|
} else if (isCurrent) {
|
|
symbol = '●'
|
|
color = 'yellow'
|
|
} else {
|
|
symbol = '○'
|
|
color = 'gray'
|
|
}
|
|
|
|
const nameStyle = isCurrent ? '{bold}' : ''
|
|
const nameStyleEnd = isCurrent ? '{/bold}' : ''
|
|
|
|
lines.push(`{${color}-fg}${symbol}{/} ${nameStyle}${phase.name}${nameStyleEnd}`)
|
|
}
|
|
|
|
this.box.setContent(lines.join('\n'))
|
|
this.box.screen.render()
|
|
}
|
|
|
|
/**
|
|
* Get the underlying blessed element
|
|
*/
|
|
get element(): blessed.Widgets.BoxElement {
|
|
return this.box
|
|
}
|
|
}
|