fix: open native directory picker for new instances

This commit is contained in:
QiuSW
2026-07-22 16:06:39 +08:00
parent 6f08c9c9d0
commit dbbb5c0e98
12 changed files with 289 additions and 14 deletions
+93 -10
View File
@@ -31,6 +31,8 @@ const (
type PathSearcher func(context.Context, PathField, string) (string, error)
type DirectoryChooser func(context.Context) (string, error)
type pathSearchState struct {
request uint64
cancel context.CancelFunc
@@ -44,6 +46,18 @@ type pathSearchResult struct {
err error
}
type directoryPickState struct {
request uint64
cancel context.CancelFunc
running bool
}
type directoryPickResult struct {
request uint64
path string
err error
}
// InstanceRow is the read-only view model used by the first Gio shell.
// Runtime data will replace these fixtures when the application service is wired.
type InstanceRow struct {
@@ -97,13 +111,16 @@ type Shell struct {
pathFeedback string
formFeedback string
list widget.List
rows []InstanceRow
onSave func(SettingsState)
pathSearcher PathSearcher
invalidate func()
searches map[PathField]*pathSearchState
searchResults chan pathSearchResult
list widget.List
rows []InstanceRow
onSave func(SettingsState)
pathSearcher PathSearcher
invalidate func()
searches map[PathField]*pathSearchState
searchResults chan pathSearchResult
directoryChooser DirectoryChooser
directoryPick directoryPickState
directoryResults chan directoryPickResult
}
func NewShell(theme *material.Theme) *Shell {
@@ -117,7 +134,7 @@ func NewShell(theme *material.Theme) *Shell {
PathEdgeExecutable: {},
PathDefaultUserData: {},
PathLogDirectory: {},
}, searchResults: make(chan pathSearchResult, 8)}
}, searchResults: make(chan pathSearchResult, 8), directoryResults: make(chan directoryPickResult, 1)}
s.chromePath.SetText(`C:\Program Files\Google\Chrome\Application\chrome.exe`)
s.edgePath.SetText(`C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe`)
s.dataDir.SetText(`C:\Users\Public\chub\profiles`)
@@ -149,8 +166,14 @@ func (s *Shell) OnPathSearch(searcher PathSearcher, invalidate func()) {
s.invalidate = invalidate
}
func (s *Shell) OnChooseDirectory(chooser DirectoryChooser, invalidate func()) {
s.directoryChooser = chooser
s.invalidate = invalidate
}
func (s *Shell) Layout(gtx layout.Context) layout.Dimensions {
s.consumeSearchResults()
s.consumeDirectoryResults()
for s.instancesClick.Clicked(gtx) {
s.page = pageInstances
}
@@ -170,7 +193,7 @@ func (s *Shell) Layout(gtx layout.Context) layout.Dimensions {
}
}
for s.instanceDirPick.Clicked(gtx) {
s.formFeedback = "请选择实例 User Data Dir(原生文件夹选择器待接入)。"
s.chooseInstanceDirectory()
}
for s.backClick.Clicked(gtx) {
s.page = pageInstances
@@ -379,7 +402,7 @@ func (s *Shell) instanceDirField(gtx layout.Context) layout.Dimensions {
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
layout.Flexed(1, material.Editor(s.theme, &s.instanceDir, "请输入或粘贴绝对路径").Layout),
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
layout.Rigid(material.Button(s.theme, &s.instanceDirPick, "选择路径").Layout),
layout.Rigid(material.Button(s.theme, &s.instanceDirPick, s.instanceDirButtonLabel()).Layout),
)
}),
)
@@ -488,6 +511,66 @@ func browserDisplay(value string) string {
return "Chrome"
}
func (s *Shell) chooseInstanceDirectory() {
if s.directoryPick.running {
s.formFeedback = "目录选择器已打开,请在系统窗口中选择或取消。"
return
}
if s.directoryChooser == nil {
s.formFeedback = "目录选择器尚未准备好。"
return
}
s.directoryPick.request++
request := s.directoryPick.request
ctx, cancel := context.WithCancel(context.Background())
s.directoryPick.cancel = cancel
s.directoryPick.running = true
s.formFeedback = "正在打开目录选择器…"
go func() {
path, err := s.directoryChooser(ctx)
s.directoryResults <- directoryPickResult{request: request, path: path, err: err}
if s.invalidate != nil {
s.invalidate()
}
}()
}
func (s *Shell) consumeDirectoryResults() {
for {
select {
case result := <-s.directoryResults:
if !s.directoryPick.running || result.request != s.directoryPick.request {
continue
}
s.directoryPick.running = false
s.directoryPick.cancel = nil
if result.err != nil {
if errors.Is(result.err, context.Canceled) {
s.formFeedback = "已取消选择 User Data Dir。"
} else {
s.formFeedback = fmt.Sprintf("选择 User Data Dir 失败:%v", result.err)
}
continue
}
if result.path == "" {
s.formFeedback = "未选择 User Data Dir。"
continue
}
s.instanceDir.SetText(result.path)
s.formFeedback = "已更新 User Data Dir。"
default:
return
}
}
}
func (s *Shell) instanceDirButtonLabel() string {
if s.directoryPick.running {
return "选择中…"
}
return "选择路径"
}
func (s *Shell) resetSettings() {
s.chromePath.SetText(`C:\Program Files\Google\Chrome\Application\chrome.exe`)
s.edgePath.SetText(`C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe`)
+19
View File
@@ -115,3 +115,22 @@ func TestShellIgnoresCancelledPathSearchResult(t *testing.T) {
t.Fatal("cancelled result overwrote the editor")
}
}
func TestShellAppliesSelectedInstanceDirectory(t *testing.T) {
shell := NewShell(material.NewTheme())
shell.OnChooseDirectory(func(context.Context) (string, error) {
return `C:\profiles\new-instance`, nil
}, nil)
shell.chooseInstanceDirectory()
var result directoryPickResult
select {
case result = <-shell.directoryResults:
case <-time.After(time.Second):
t.Fatal("directory picker did not produce a result")
}
shell.directoryResults <- result
shell.consumeDirectoryResults()
if got := shell.instanceDir.Text(); got != `C:\profiles\new-instance` {
t.Fatalf("instance dir = %q", got)
}
}