@@ -65,6 +65,44 @@ const ROUTING_ERROR_NAMES: Record<number, string> = {
6565
6666type AppMode = "packets" | "nodes" | "chat" | "dm" | "config" | "log" | "meshview" ;
6767
68+ interface TabDef {
69+ mode : AppMode ;
70+ label : string ;
71+ short : string ;
72+ meshviewOnly ?: boolean ;
73+ onEnter ?: string ;
74+ }
75+
76+ const TABS : TabDef [ ] = [
77+ { mode : "packets" , label : "PACKETS" , short : "P" } ,
78+ { mode : "nodes" , label : "NODES" , short : "N" } ,
79+ { mode : "chat" , label : "CHAT" , short : "C" } ,
80+ { mode : "dm" , label : "DM" , short : "D" } ,
81+ { mode : "log" , label : "LOG" , short : "L" } ,
82+ { mode : "meshview" , label : "MESHVIEW" , short : "M" , meshviewOnly : true } ,
83+ { mode : "config" , label : "CONFIG" , short : "CFG" , onEnter : "startBatchEdit" } ,
84+ ] ;
85+
86+ const NavBar = React . memo ( function NavBar ( { mode, terminalWidth, hasMeshView } : { mode : AppMode ; terminalWidth : number ; hasMeshView : boolean } ) {
87+ const compact = terminalWidth <= 90 ;
88+ const visibleTabs = TABS . filter ( t => ! t . meshviewOnly || hasMeshView ) ;
89+ return (
90+ < Text >
91+ { visibleTabs . map ( ( tab , i ) => {
92+ const active = mode === tab . mode ;
93+ const key = String ( i + 1 ) ;
94+ const label = compact ? `${ key } :${ tab . short } ` : `${ key } :${ tab . label } ` ;
95+ return (
96+ < React . Fragment key = { tab . mode } >
97+ { i > 0 && < Text dimColor > | </ Text > }
98+ < Text bold = { active } { ...( active ? { color : theme . fg . accent } : { dimColor : true } ) } > { label } </ Text >
99+ </ React . Fragment >
100+ ) ;
101+ } ) }
102+ </ Text >
103+ ) ;
104+ } ) ;
105+
68106export interface ChannelInfo {
69107 index : number ;
70108 name : string ;
@@ -2060,17 +2098,17 @@ export function App({ address, packetStore, nodeStore, skipConfig = false, skipN
20602098
20612099 // Mode switching (allow only when input not focused)
20622100 if ( ! isInputFocused ) {
2063- if ( input === "1" ) { setMode ( "packets" ) ; setChatInputFocused ( false ) ; setDmInputFocused ( false ) ; return ; }
2064- if ( input === "2" ) { setMode ( "nodes" ) ; setChatInputFocused ( false ) ; setDmInputFocused ( false ) ; return ; }
2065- if ( input === "3" ) { setMode ( "chat" ) ; return ; }
2066- if ( input === "4" ) { setMode ( "dm" ) ; return ; }
2067- if ( input === "5" ) { setMode ( "log" ) ; setChatInputFocused ( false ) ; setDmInputFocused ( false ) ; return ; }
2068- if ( input === "6" && localMeshViewUrl ) { setMode ( "meshview" ) ; setChatInputFocused ( false ) ; setDmInputFocused ( false ) ; return ; }
2069- if ( input === ( localMeshViewUrl ? "7" : "6" ) ) { setMode ( "config" ) ; setChatInputFocused ( false ) ; setDmInputFocused ( false ) ; if ( ! batchEditMode ) startBatchEdit ( ) ; return ; }
2101+ const visibleTabs = TABS . filter ( t => ! t . meshviewOnly || localMeshViewUrl ) ;
2102+ const numKey = parseInt ( input ) ;
2103+ if ( numKey >= 1 && numKey <= visibleTabs . length ) {
2104+ const tab = visibleTabs [ numKey - 1 ] ;
2105+ setMode ( tab . mode ) ;
2106+ if ( tab . mode !== "chat" && tab . mode !== "dm" ) { setChatInputFocused ( false ) ; setDmInputFocused ( false ) ; }
2107+ if ( tab . onEnter === "startBatchEdit" && ! batchEditMode ) startBatchEdit ( ) ;
2108+ return ;
2109+ }
20702110 // Bracket keys for tab switching
2071- const modes : AppMode [ ] = localMeshViewUrl
2072- ? [ "packets" , "nodes" , "chat" , "dm" , "log" , "meshview" , "config" ]
2073- : [ "packets" , "nodes" , "chat" , "dm" , "log" , "config" ] ;
2111+ const modes : AppMode [ ] = visibleTabs . map ( t => t . mode ) ;
20742112 if ( input === "[" ) {
20752113 const idx = modes . indexOf ( mode ) ;
20762114 const newMode = modes [ ( idx - 1 + modes . length ) % modes . length ] ;
@@ -3140,37 +3178,7 @@ export function App({ address, packetStore, nodeStore, skipConfig = false, skipN
31403178
31413179 const selectedPacket = packets [ selectedPacketIndex ] ;
31423180
3143- const getModeLabel = ( ) => {
3144- const p = mode === "packets" ;
3145- const n = mode === "nodes" ;
3146- const c = mode === "chat" ;
3147- const d = mode === "dm" ;
3148- const cfg = mode === "config" ;
3149- const l = mode === "log" ;
3150- const mv = mode === "meshview" ;
3151- const compact = terminalWidth <= 90 ;
3152- return (
3153- < Text >
3154- < Text color = { p ? theme . fg . accent : theme . fg . muted } bold = { p } > { compact ? "[P]" : "[PACKETS]" } </ Text >
3155- { " " }
3156- < Text color = { n ? theme . fg . accent : theme . fg . muted } bold = { n } > { compact ? "[N]" : "[NODES]" } </ Text >
3157- { " " }
3158- < Text color = { c ? theme . fg . accent : theme . fg . muted } bold = { c } > { compact ? "[C]" : "[CHAT]" } </ Text >
3159- { " " }
3160- < Text color = { d ? theme . fg . accent : theme . fg . muted } bold = { d } > { compact ? "[D]" : "[DM]" } </ Text >
3161- { " " }
3162- < Text color = { l ? theme . fg . accent : theme . fg . muted } bold = { l } > { compact ? "[L]" : "[LOG]" } </ Text >
3163- { localMeshViewUrl && (
3164- < >
3165- { " " }
3166- < Text color = { mv ? theme . fg . accent : theme . fg . muted } bold = { mv } > { compact ? "[M]" : "[MESHVIEW]" } </ Text >
3167- </ >
3168- ) }
3169- { " " }
3170- < Text color = { cfg ? theme . fg . accent : theme . fg . muted } bold = { cfg } > { compact ? "[CFG]" : "[CONFIG]" } </ Text >
3171- </ Text >
3172- ) ;
3173- } ;
3181+ const hasMeshView = ! ! localMeshViewUrl ;
31743182
31753183 const statusColor = status === "connected" ? theme . status . online : theme . status . offline ;
31763184 const nodeCount = nodes . length ;
@@ -3226,7 +3234,7 @@ export function App({ address, packetStore, nodeStore, skipConfig = false, skipN
32263234 { truncatedNodeName } < Text color = { theme . fg . muted } > { formatNodeId ( myNodeNum ) } </ Text >
32273235 </ Text >
32283236 < Box flexShrink = { 0 } >
3229- { getModeLabel ( ) }
3237+ < NavBar mode = { mode } terminalWidth = { terminalWidth } hasMeshView = { hasMeshView } />
32303238 </ Box >
32313239 </ Box >
32323240
0 commit comments