feat: add instance list columns and start action
This commit is contained in:
+72
-18
@@ -61,10 +61,11 @@ type directoryPickResult struct {
|
||||
// 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 {
|
||||
Name string
|
||||
Browser string
|
||||
Profile string
|
||||
Status string
|
||||
ID string
|
||||
Name string
|
||||
Browser string
|
||||
UserDataDir string
|
||||
Status string
|
||||
}
|
||||
|
||||
type SettingsState struct {
|
||||
@@ -113,6 +114,9 @@ type Shell struct {
|
||||
|
||||
list widget.List
|
||||
rows []InstanceRow
|
||||
startClicks map[string]*widget.Clickable
|
||||
nextInstance uint64
|
||||
instanceFeedback string
|
||||
onSave func(SettingsState)
|
||||
pathSearcher PathSearcher
|
||||
invalidate func()
|
||||
@@ -125,16 +129,16 @@ type Shell struct {
|
||||
|
||||
func NewShell(theme *material.Theme) *Shell {
|
||||
s := &Shell{theme: theme, rows: []InstanceRow{
|
||||
{Name: "运营主账号", Browser: "Chrome", Profile: "Default", Status: "运行中"},
|
||||
{Name: "广告投放", Browser: "Edge", Profile: "Profile 2", Status: "启动中"},
|
||||
{Name: "素材采集", Browser: "Chrome", Profile: "Default", Status: "外部占用"},
|
||||
{Name: "备用环境", Browser: "Edge", Profile: "Profile 1", Status: "已退出"},
|
||||
{ID: "demo-operations", Name: "运营主账号", Browser: "Chrome", UserDataDir: `C:\Users\Public\chub\profiles\operations`, Status: "运行中"},
|
||||
{ID: "demo-ads", Name: "广告投放", Browser: "Edge", UserDataDir: `C:\Users\Public\chub\profiles\ads`, Status: "启动中"},
|
||||
{ID: "demo-assets", Name: "素材采集", Browser: "Chrome", UserDataDir: `C:\Users\Public\chub\profiles\assets`, Status: "外部占用"},
|
||||
{ID: "demo-backup", Name: "备用环境", Browser: "Edge", UserDataDir: `C:\Users\Public\chub\profiles\backup`, Status: "已退出"},
|
||||
}, searches: map[PathField]*pathSearchState{
|
||||
PathChromeExecutable: {},
|
||||
PathEdgeExecutable: {},
|
||||
PathDefaultUserData: {},
|
||||
PathLogDirectory: {},
|
||||
}, searchResults: make(chan pathSearchResult, 8), directoryResults: make(chan directoryPickResult, 1)}
|
||||
}, startClicks: make(map[string]*widget.Clickable), nextInstance: 4, 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,6 +153,12 @@ func (s *Shell) SetInstances(rows []InstanceRow) {
|
||||
return
|
||||
}
|
||||
s.rows = append([]InstanceRow(nil), rows...)
|
||||
for i := range s.rows {
|
||||
if s.rows[i].ID == "" {
|
||||
s.nextInstance++
|
||||
s.rows[i].ID = fmt.Sprintf("restored-%d", s.nextInstance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Shell) SetSettings(value SettingsState) {
|
||||
@@ -185,7 +195,8 @@ func (s *Shell) Layout(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
for s.createClick.Clicked(gtx) {
|
||||
if name := s.instanceName.Text(); name != "" {
|
||||
s.rows = append(s.rows, InstanceRow{Name: name, Browser: browserDisplay(s.browserKind.Value), Profile: "Default", Status: "已退出"})
|
||||
s.nextInstance++
|
||||
s.rows = append(s.rows, InstanceRow{ID: fmt.Sprintf("instance-%d", s.nextInstance), Name: name, Browser: browserDisplay(s.browserKind.Value), UserDataDir: s.instanceDir.Text(), Status: "已退出"})
|
||||
s.page = pageInstances
|
||||
s.formFeedback = ""
|
||||
} else {
|
||||
@@ -198,6 +209,11 @@ func (s *Shell) Layout(gtx layout.Context) layout.Dimensions {
|
||||
for s.backClick.Clicked(gtx) {
|
||||
s.page = pageInstances
|
||||
}
|
||||
for _, row := range s.rows {
|
||||
for s.startClickFor(row.ID).Clicked(gtx) {
|
||||
s.requestStart(row.ID)
|
||||
}
|
||||
}
|
||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||
layout.Rigid(s.sidebar),
|
||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||
@@ -244,27 +260,45 @@ func (s *Shell) instances(gtx layout.Context) layout.Dimensions {
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(18)}.Layout),
|
||||
layout.Rigid(material.Button(s.theme, &s.newClick, "新建实例").Layout),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(16)}.Layout),
|
||||
layout.Rigid(s.instanceHeader),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||
return material.List(s.theme, &s.list).Layout(gtx, len(s.rows), func(gtx layout.Context, i int) layout.Dimensions {
|
||||
row := s.rows[i]
|
||||
return layout.Inset{Bottom: unit.Dp(10)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(material.Subtitle1(s.theme, row.Name).Layout),
|
||||
layout.Rigid(material.Caption(s.theme, row.Browser+" · "+row.Profile).Layout),
|
||||
)
|
||||
layout.Flexed(30, material.Body1(s.theme, row.Name).Layout),
|
||||
layout.Flexed(14, material.Body2(s.theme, row.Browser).Layout),
|
||||
layout.Flexed(26, pathCell(s.theme, row.UserDataDir)),
|
||||
layout.Flexed(16, statusLabel(s.theme, row.Status)),
|
||||
layout.Flexed(14, func(gtx layout.Context) layout.Dimensions {
|
||||
return material.Button(s.theme, s.startClickFor(row.ID), "启动").Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(statusLabel(s.theme, row.Status)),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(16)}.Layout),
|
||||
layout.Rigid(material.Button(s.theme, &s.startClick, "启动").Layout),
|
||||
)
|
||||
})
|
||||
})
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(material.Caption(s.theme, s.instanceFeedback).Layout),
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Shell) instanceHeader(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
||||
layout.Flexed(30, material.Caption(s.theme, "实例名称").Layout),
|
||||
layout.Flexed(14, material.Caption(s.theme, "浏览器类型").Layout),
|
||||
layout.Flexed(26, material.Caption(s.theme, "用户数据目录").Layout),
|
||||
layout.Flexed(16, material.Caption(s.theme, "状态").Layout),
|
||||
layout.Flexed(14, material.Caption(s.theme, "启动").Layout),
|
||||
)
|
||||
}
|
||||
|
||||
func pathCell(theme *material.Theme, path string) layout.Widget {
|
||||
style := material.Body2(theme, path)
|
||||
style.MaxLines = 2
|
||||
return style.Layout
|
||||
}
|
||||
|
||||
func statusLabel(theme *material.Theme, status string) layout.Widget {
|
||||
style := material.Label(theme, unit.Sp(13), status)
|
||||
style.Color = map[string]color.NRGBA{
|
||||
@@ -511,6 +545,26 @@ func browserDisplay(value string) string {
|
||||
return "Chrome"
|
||||
}
|
||||
|
||||
func (s *Shell) startClickFor(id string) *widget.Clickable {
|
||||
if click := s.startClicks[id]; click != nil {
|
||||
return click
|
||||
}
|
||||
click := new(widget.Clickable)
|
||||
s.startClicks[id] = click
|
||||
return click
|
||||
}
|
||||
|
||||
func (s *Shell) requestStart(id string) {
|
||||
for _, row := range s.rows {
|
||||
if row.ID != id {
|
||||
continue
|
||||
}
|
||||
s.instanceFeedback = fmt.Sprintf("%s 的启动命令等待 BrowserManager 接入。", row.Name)
|
||||
return
|
||||
}
|
||||
s.instanceFeedback = "找不到要启动的实例。"
|
||||
}
|
||||
|
||||
func (s *Shell) chooseInstanceDirectory() {
|
||||
if s.directoryPick.running {
|
||||
s.formFeedback = "目录选择器已打开,请在系统窗口中选择或取消。"
|
||||
|
||||
Reference in New Issue
Block a user