Skip to content

Commit c201b5c

Browse files
committed
sandbox-ui: add a file picker component
1 parent 640f8a9 commit c201b5c

File tree

2 files changed

+576
-10
lines changed

2 files changed

+576
-10
lines changed

internal/tui/debug/debug.go

Lines changed: 115 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ func Launch() {
2929
}
3030

3131
type testModel struct {
32-
vp viewport.Model
33-
width int
34-
height int
35-
menu *frame.ListView
36-
dialog *frame.Dialog
37-
showDialog bool
32+
vp viewport.Model
33+
width int
34+
height int
35+
menu *frame.ListView
36+
dialog *frame.Dialog
37+
showDialog bool
38+
filePicker *frame.FilePicker
39+
showFilePicker bool
3840
}
3941

4042
func newTestModel() testModel {
@@ -61,6 +63,12 @@ func newTestModel() testModel {
6163
"Ok",
6264
)
6365
m.showDialog = false
66+
67+
// Create a file picker starting from home directory
68+
home, _ := os.UserHomeDir()
69+
m.filePicker = frame.NewFilePicker(home)
70+
m.showFilePicker = false
71+
6472
return m
6573
}
6674

@@ -74,13 +82,78 @@ func (m testModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
7482
return m, tea.Quit
7583
case "d":
7684
m.showDialog = !m.showDialog
85+
case "f":
86+
m.showFilePicker = !m.showFilePicker
87+
// Toggle between load and save mode for testing
88+
if m.showFilePicker {
89+
m.filePicker.SetSaveMode(false) // Load mode by default, press 's' for save
90+
}
91+
case "s":
92+
if m.showFilePicker {
93+
// Toggle save mode
94+
m.filePicker.SetSaveMode(!m.filePicker.IsSaveMode())
95+
}
7796
case "tab", "right":
7897
if m.showDialog {
7998
m.dialog.FocusRight()
99+
} else if m.showFilePicker {
100+
// Tab cycles through: file list -> [filename input] -> OK -> Cancel -> file list
101+
if m.filePicker.IsSaveMode() {
102+
if m.filePicker.Focused == 0 {
103+
m.filePicker.FocusFilename()
104+
} else if m.filePicker.Focused == 1 {
105+
m.filePicker.FocusOk()
106+
} else if m.filePicker.Focused == 2 {
107+
m.filePicker.FocusCancel()
108+
} else {
109+
m.filePicker.FocusFileList()
110+
}
111+
} else {
112+
if m.filePicker.Focused == 0 {
113+
m.filePicker.FocusOk()
114+
} else if m.filePicker.Focused == 1 {
115+
m.filePicker.FocusCancel()
116+
} else {
117+
m.filePicker.FocusFileList()
118+
}
119+
}
80120
}
81121
case "shift+tab", "left":
82122
if m.showDialog {
83123
m.dialog.FocusLeft()
124+
} else if m.showFilePicker {
125+
// Shift+Tab cycles backward
126+
if m.filePicker.IsSaveMode() {
127+
if m.filePicker.Focused == 0 {
128+
m.filePicker.FocusCancel()
129+
} else if m.filePicker.Focused == 1 {
130+
m.filePicker.FocusFileList()
131+
} else if m.filePicker.Focused == 2 {
132+
m.filePicker.FocusFilename()
133+
} else {
134+
m.filePicker.FocusOk()
135+
}
136+
} else {
137+
if m.filePicker.Focused == 0 {
138+
m.filePicker.FocusCancel()
139+
} else if m.filePicker.Focused == 1 {
140+
m.filePicker.FocusFileList()
141+
} else {
142+
m.filePicker.FocusOk()
143+
}
144+
}
145+
}
146+
if m.showDialog {
147+
m.dialog.FocusLeft()
148+
} else if m.showFilePicker {
149+
// Shift+Tab moves through file picker buttons backward
150+
if m.filePicker.Focused == 0 {
151+
m.filePicker.FocusCancel()
152+
} else if m.filePicker.Focused == 1 {
153+
m.filePicker.FocusFileList()
154+
} else {
155+
m.filePicker.FocusOk()
156+
}
84157
}
85158
case "enter", " ":
86159
if m.showDialog {
@@ -91,23 +164,47 @@ func (m testModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
91164
// Left button (Cancel) pressed
92165
m.showDialog = false
93166
}
167+
} else if m.showFilePicker && m.filePicker.Focused == 0 {
168+
// Enter on file list: enter directory or select file
169+
selected := m.filePicker.SelectCurrent()
170+
if selected != "" {
171+
// File selected - close picker
172+
m.showFilePicker = false
173+
}
94174
}
95175
case "j", "down":
96-
if !m.showDialog {
176+
if !m.showDialog && !m.showFilePicker {
97177
m.vp.LineDown(1)
178+
} else if m.showFilePicker {
179+
m.filePicker.MoveDown()
98180
}
99181
case "k", "up":
100-
if !m.showDialog {
182+
if !m.showDialog && !m.showFilePicker {
101183
m.vp.LineUp(1)
184+
} else if m.showFilePicker {
185+
m.filePicker.MoveUp()
186+
}
187+
case "u":
188+
if m.showFilePicker {
189+
m.filePicker.GoUp()
102190
}
103191
case "J":
104-
if !m.showDialog {
192+
if !m.showDialog && !m.showFilePicker {
105193
m.menu.MoveDown()
106194
}
107195
case "K":
108-
if !m.showDialog {
196+
if !m.showDialog && !m.showFilePicker {
109197
m.menu.MoveUp()
110198
}
199+
case "backspace":
200+
if m.showFilePicker && m.filePicker.Focused == 1 {
201+
m.filePicker.Backspace()
202+
}
203+
default:
204+
// Handle typing in filename input
205+
if m.showFilePicker && m.filePicker.Focused == 1 && len(msg.String()) == 1 {
206+
m.filePicker.TypeChar(rune(msg.String()[0]))
207+
}
111208
}
112209
case tea.WindowSizeMsg:
113210
// Reserve 3 lines for header/footer and padding
@@ -181,6 +278,14 @@ func (m testModel) View() string {
181278
return m.overlayDialog(dialogOutput, final)
182279
}
183280

281+
// Step 9: Render file picker if shown (overlay on top of background).
282+
if m.showFilePicker {
283+
m.filePicker.Width = 70
284+
m.filePicker.Height = frameH - 4
285+
pickerOutput := m.filePicker.Render()
286+
return m.overlayDialog(pickerOutput, final)
287+
}
288+
184289
return final
185290
}
186291

0 commit comments

Comments
 (0)