okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
479178e
main
1package tui 2 3import ( 4"slices" 5 6"charm.land/bubbles/v2/textinput" 7 tea"charm.land/bubbletea/v2" 8) 9 10// loginPick is a list a login step chooses from: its items, filtered and 11// drawn by the shared listView like every other list screen. 12type loginPick struct { 13items []PickerItem 14listView 15} 16 17// newLoginPick lists items under title, with the row keyed initial highlighted. 18func newLoginPick (title string ,items []PickerItem ,initial string ,width ,height int )loginPick { 19p := loginPick {items :items ,listView :newListView ("Search" )} 20p .setSize (width ,height ) 21rows := make ([]listEntry ,len (items )) 22for i ,item := range items { 23rows [i ]= listEntry { 24key :item .ID , 25section :title , 26name :item .Label , 27desc :item .Detail , 28detail :func (w int ) []string {return loginPane (item ,w ) }, 29 } 30 } 31p .setRows (rows ) 32if i := slices .IndexFunc (p .shown ,func (s listShown )bool {return p .rows [s .row ].key == initial });i >= 0 { 33p .Cursor = i 34 } 35return p 36} 37 38// Highlighted is the item under the cursor. 39func (p loginPick )Highlighted () (PickerItem ,bool ) { 40row ,ok := p .highlighted () 41if ! ok { 42return PickerItem {},false 43 } 44i := slices .IndexFunc (p .items ,func (item PickerItem )bool {return item .ID == row .key }) 45if i < 0 { 46return PickerItem {},false 47 } 48return p .items [i ],true 49} 50 51// Init starts the cursor blinking in the search line. 52func (p loginPick )Init () tea.Cmd {return textinput .Blink } 53 54// loginPane is an item's detail: what it is, what it holds, and what enter 55// or a key would do with it. 56func loginPane (item PickerItem ,width int ) []string { 57lines := paneTitle (item .Label ,"" ,width ) 58if item .Detail != "" { 59lines = append (lines ,factRows ("details" ,item .Detail ,width )... ) 60 } 61if item .Note != "" { 62lines = append (lines ,"" ) 63lines = append (lines ,paneNote (item .Note ,width )... ) 64 } 65return lines 66} 67 68// pickerFor returns the list the current step shows. 69func (m * LoginModel )pickerFor ()* loginPick { 70switch m .Step { 71case StepProtocol : 72return & m .ProtocolPicker 73case StepModels ,StepOAuthModels : 74return & m .ModelPicker 75 } 76return & m .ChoosePicker 77} 78 79// listing reports whether the step shows a list rather than a question. A 80// catalog still loading is a question until its models arrive. 81func (m LoginModel )listing ()bool { 82switch m .Step { 83case StepChoose ,StepAccountProfile ,StepOAuthFlow ,StepProtocol : 84return true 85case StepModels ,StepOAuthModels : 86return m .Catalog != nil 87case StepOAuthFields : 88return m .choiceField () 89 } 90return false 91} 92 93// choiceField reports the sign-in field being asked is a list of choices. 94func (m LoginModel )choiceField ()bool { 95if m .Step != StepOAuthFields || m .LoginFieldIndex >= len (m .LoginFields ) { 96return false 97 } 98field := m .LoginFields [m .LoginFieldIndex ] 99return field .Type == "choice" || field .Type == "boolean" 100} 101 102// pickUpdate routes a message to the list the step shows. Enter and esc 103// answer with the messages the step already handles; every other key moves 104// or types in the search. 105func (m * LoginModel )pickUpdate (msg tea.Msg ) tea.Cmd { 106p := m .pickerFor () 107key ,ok := msg .(tea.KeyPressMsg ) 108if ! ok { 109return p .update (msg ) 110 } 111switch key .String () { 112case "enter" : 113item ,found := p .Highlighted () 114if ! found || ! m .listing () { 115return nil 116 } 117return func () tea.Msg {return PickerSelectMsg {ID :item .ID } } 118case "esc" ,"ctrl+c" : 119return func () tea.Msg {return PickerCancelMsg {} } 120 } 121return p .update (msg ) 122}