11/** ====== About Iris Screen ======
22 * Read-only screen showing software metadata, version, and runtime info.
33 */
4- import {
5- BoxRenderable ,
6- TextRenderable ,
7- type KeyEvent ,
8- t ,
9- fg ,
10- link ,
11- underline ,
12- } from '@opentui/core' ;
4+ import { TextRenderable , t , fg , link , underline } from '@opentui/core' ;
135import type { RenderContext , Renderer } from '../types' ;
146import { theme , PALETTE } from '../../../assets/brand/theme' ;
157import type { Screen , ScreenResult , ScreenData } from '../utils/router' ;
16- import { DEFAULT_CONFIG } from '../../lib/types/configTypes' ;
8+ import { appShell , panel , type AppShell , type Panel } from '../components' ;
9+ import { Keymap } from '../utils/keymap' ;
1710
1811const CONTAINER_ID = 'about-root' ;
1912
@@ -23,59 +16,54 @@ import packageJson from '../../../package.json';
2316export class AboutScreen implements Screen {
2417 readonly name = 'about' ;
2518 private renderer : Renderer ;
26- private keyHandler ?: ( key : KeyEvent ) => void ;
19+ private shell ?: AppShell ;
20+ private infoPanel ?: Panel ;
21+ private keymap ?: Keymap ;
2722
2823 constructor ( ctx : RenderContext ) {
2924 this . renderer = ctx . renderer ;
3025 }
3126
3227 async render ( _data ?: ScreenData ) : Promise < ScreenResult > {
33- this . buildUI ( ) ;
34-
3528 return new Promise ( ( resolve ) => {
36- this . keyHandler = ( key : KeyEvent ) => {
37- if ( key . name === 'escape' || key . name === 'q' ) {
38- this . renderer . keyInput . off ( 'keypress' , this . keyHandler ! ) ;
39- resolve ( { action : 'pop' } ) ;
40- }
41- } ;
42- this . renderer . keyInput . on ( 'keypress' , this . keyHandler ) ;
29+ const keymap = this . buildUI ( resolve ) ;
30+ keymap . attach ( this . renderer ) ;
4331 } ) ;
4432 }
4533
4634 cleanup ( ) : void {
47- if ( this . keyHandler ) {
48- this . renderer . keyInput . off ( 'keypress' , this . keyHandler ) ;
49- }
35+ this . keymap ?. detach ( this . renderer ) ;
5036 this . renderer . root . remove ( CONTAINER_ID ) ;
5137 }
5238
53- private buildUI ( ) : void {
54- const container = new BoxRenderable ( this . renderer , {
39+ private buildUI ( resolve : ( result : ScreenResult ) => void ) : Keymap {
40+ const finish = ( ) => resolve ( { action : 'pop' } ) ;
41+ this . keymap = new Keymap ( {
42+ bindings : [ ] ,
43+ onBack : finish ,
44+ onQuit : finish ,
45+ } ) ;
46+ const keymap = this . keymap ;
47+
48+ this . shell = appShell ( this . renderer , {
5549 id : CONTAINER_ID ,
56- flexDirection : 'column' ,
57- width : '100%' ,
58- height : '100%' ,
59- backgroundColor : theme . background ,
50+ breadcrumb : 'About' ,
51+ footer : keymap . toKeybar ( ) ,
6052 } ) ;
6153
62- // Title
63- container . add ( new TextRenderable ( this . renderer , {
64- content : 'About Iris' ,
65- fg : theme . primary ,
66- } ) ) ;
54+ this . infoPanel = panel ( this . renderer , { title : 'About Iris' , flexGrow : 1 } ) ;
6755
68- // Description (before fields)
69- container . add ( new TextRenderable ( this . renderer , {
70- content : ' ILR toolkit for converting learner data from CSV' ,
56+ // Description
57+ this . infoPanel . add ( new TextRenderable ( this . renderer , {
58+ content : 'ILR toolkit for converting learner data from CSV' ,
7159 fg : theme . textMuted ,
7260 } ) ) ;
73- container . add ( new TextRenderable ( this . renderer , {
74- content : ' exports into ILR-compliant XML for ESFA submission.' ,
61+ this . infoPanel . add ( new TextRenderable ( this . renderer , {
62+ content : 'exports into ILR-compliant XML for ESFA submission.' ,
7563 fg : theme . textMuted ,
7664 } ) ) ;
7765
78- container . add ( new TextRenderable ( this . renderer , { content : '' } ) ) ;
66+ this . infoPanel . add ( new TextRenderable ( this . renderer , { content : '' } ) ) ;
7967
8068 // Software info
8169 const labelColour = theme . text ;
@@ -92,26 +80,20 @@ export class AboutScreen implements Screen {
9280 for ( const field of fields ) {
9381 const padding = ' ' . repeat ( Math . max ( 1 , 22 - field . label . length ) ) ;
9482 if ( field . url ) {
95- container . add ( new TextRenderable ( this . renderer , {
96- content : t ` ${ fg ( labelColour ) ( `${ field . label } ${ padding } ` ) } ${ link ( field . url ) ( underline ( fg ( linkColour ) ( field . value ) ) ) } ` ,
83+ this . infoPanel . add ( new TextRenderable ( this . renderer , {
84+ content : t `${ fg ( labelColour ) ( `${ field . label } ${ padding } ` ) } ${ link ( field . url ) ( underline ( fg ( linkColour ) ( field . value ) ) ) } ` ,
9785 } ) ) ;
9886 } else {
99- container . add ( new TextRenderable ( this . renderer , {
100- content : ` ${ field . label } ${ padding } ${ field . value } ` ,
87+ this . infoPanel . add ( new TextRenderable ( this . renderer , {
88+ content : `${ field . label } ${ padding } ${ field . value } ` ,
10189 fg : labelColour ,
10290 } ) ) ;
10391 }
10492 }
10593
106- // Fill remaining space (explicit background so it paints the theme colour, not the terminal default)
107- container . add ( new BoxRenderable ( this . renderer , { flexGrow : 1 , backgroundColor : theme . background } ) ) ;
108-
109- // Status bar
110- container . add ( new TextRenderable ( this . renderer , {
111- content : '[ESC] Back' ,
112- fg : theme . textMuted ,
113- } ) ) ;
94+ this . shell . content . add ( this . infoPanel . box ) ;
95+ this . renderer . root . add ( this . shell . root ) ;
11496
115- this . renderer . root . add ( container ) ;
97+ return keymap ;
11698 }
11799}
0 commit comments