dlform.go (4431B)
1 package tui 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 8 "github.com/charmbracelet/bubbles/textinput" 9 tea "github.com/charmbracelet/bubbletea" 10 "github.com/charmbracelet/lipgloss" 11 ) 12 13 type dlFormField int 14 15 const ( 16 fieldSelect dlFormField = iota 17 fieldWhere 18 fieldLimit 19 fieldOut 20 fieldFormat 21 fieldCount 22 ) 23 24 // DlForm is the download dialog overlay. 25 type DlForm struct { 26 schema string 27 table string 28 inputs [fieldCount]textinput.Model 29 focused dlFormField 30 err string 31 } 32 33 // DlSubmitMsg is sent when the user confirms the download form. 34 type DlSubmitMsg struct { 35 Schema string 36 Table string 37 Columns string 38 Where string 39 Limit int 40 Out string 41 Format string 42 } 43 44 // DlCancelMsg is sent when the user cancels. 45 type DlCancelMsg struct{} 46 47 func newDlForm(schema, table string, colNames []string) DlForm { 48 f := DlForm{schema: schema, table: table} 49 50 f.inputs[fieldSelect] = textinput.New() 51 placeholder := "e.g. gvkey, datadate, sale" 52 if len(colNames) > 0 { 53 hint := strings.Join(colNames, ", ") 54 if len(hint) > 60 { 55 hint = hint[:57] + "..." 56 } 57 placeholder = "e.g. " + hint 58 } 59 f.inputs[fieldSelect].Placeholder = placeholder 60 f.inputs[fieldSelect].CharLimit = 1024 61 f.inputs[fieldSelect].SetValue("*") 62 63 f.inputs[fieldWhere] = textinput.New() 64 f.inputs[fieldWhere].Placeholder = "e.g. date >= '2020-01-01'" 65 f.inputs[fieldWhere].CharLimit = 512 66 67 f.inputs[fieldLimit] = textinput.New() 68 f.inputs[fieldLimit].Placeholder = "no limit" 69 f.inputs[fieldLimit].CharLimit = 12 70 71 f.inputs[fieldOut] = textinput.New() 72 f.inputs[fieldOut].Placeholder = fmt.Sprintf("./%s_%s.parquet", schema, table) 73 f.inputs[fieldOut].CharLimit = 256 74 f.inputs[fieldOut].SetValue(fmt.Sprintf("./%s_%s.parquet", schema, table)) 75 76 f.inputs[fieldFormat] = textinput.New() 77 f.inputs[fieldFormat].Placeholder = "parquet" 78 f.inputs[fieldFormat].CharLimit = 10 79 f.inputs[fieldFormat].SetValue("parquet") 80 81 f.inputs[fieldSelect].Focus() 82 return f 83 } 84 85 func (f DlForm) Update(msg tea.Msg) (DlForm, tea.Cmd) { 86 switch msg := msg.(type) { 87 case tea.KeyMsg: 88 switch msg.String() { 89 case "esc": 90 return f, func() tea.Msg { return DlCancelMsg{} } 91 case "enter": 92 if f.focused < fieldCount-1 { 93 f.inputs[f.focused].Blur() 94 f.focused++ 95 f.inputs[f.focused].Focus() 96 return f, textinput.Blink 97 } 98 // Submit 99 out := f.inputs[fieldOut].Value() 100 if out == "" { 101 out = fmt.Sprintf("./%s_%s.parquet", f.schema, f.table) 102 } 103 format := strings.ToLower(f.inputs[fieldFormat].Value()) 104 if format == "" { 105 format = "parquet" 106 } 107 columns := strings.TrimSpace(f.inputs[fieldSelect].Value()) 108 if columns == "" { 109 columns = "*" 110 } 111 var limit int 112 if v := strings.TrimSpace(f.inputs[fieldLimit].Value()); v != "" { 113 limit, _ = strconv.Atoi(v) 114 } 115 return f, func() tea.Msg { 116 return DlSubmitMsg{ 117 Schema: f.schema, 118 Table: f.table, 119 Columns: columns, 120 Where: f.inputs[fieldWhere].Value(), 121 Limit: limit, 122 Out: out, 123 Format: format, 124 } 125 } 126 case "tab", "down": 127 f.inputs[f.focused].Blur() 128 f.focused = (f.focused + 1) % fieldCount 129 f.inputs[f.focused].Focus() 130 return f, textinput.Blink 131 case "shift+tab", "up": 132 f.inputs[f.focused].Blur() 133 f.focused = (f.focused + fieldCount - 1) % fieldCount 134 f.inputs[f.focused].Focus() 135 return f, textinput.Blink 136 } 137 } 138 139 var cmd tea.Cmd 140 f.inputs[f.focused], cmd = f.inputs[f.focused].Update(msg) 141 return f, cmd 142 } 143 144 func (f DlForm) View(width int) string { 145 var sb strings.Builder 146 147 title := stylePanelHeader.Render(fmt.Sprintf("Download %s.%s", f.schema, f.table)) 148 sb.WriteString(title + "\n\n") 149 150 labels := []string{"SELECT columns", "WHERE clause", "LIMIT rows", "Output path", "Format (parquet/csv)"} 151 for i, label := range labels { 152 style := lipgloss.NewStyle().Foreground(colorMuted) 153 if dlFormField(i) == f.focused { 154 style = lipgloss.NewStyle().Foreground(colorFocus) 155 } 156 sb.WriteString(style.Render(label+" ") + "\n") 157 sb.WriteString(f.inputs[i].View() + "\n\n") 158 } 159 160 hint := styleStatusBar.Render("[tab] next field [enter] confirm [esc] cancel") 161 sb.WriteString(hint) 162 163 content := sb.String() 164 boxWidth := width - 8 165 if boxWidth < 40 { 166 boxWidth = 40 167 } 168 169 box := lipgloss.NewStyle(). 170 Border(lipgloss.RoundedBorder()). 171 BorderForeground(colorFocus). 172 Padding(1, 2). 173 Width(boxWidth). 174 Render(content) 175 176 return lipgloss.Place(width, 20, lipgloss.Center, lipgloss.Center, box) 177 }