From 8f5a9293aade7ea47a060a869c1574fe797eb27f Mon Sep 17 00:00:00 2001 From: adamstruck Date: Wed, 8 Nov 2017 16:40:10 -0800 Subject: [PATCH] termdash: paging --- cmd/termdash/config/switch.go | 2 +- cmd/termdash/cursor.go | 16 +++-- cmd/termdash/grid.go | 22 +++++-- cmd/termdash/keys.go | 12 ++-- cmd/termdash/menus.go | 2 +- cmd/termdash/tasksource.go | 109 ++++++++++++++++++++------------- cmd/termdash/termdash.go | 2 +- cmd/termdash/widgets/header.go | 89 +++++++++++++++++++-------- cmd/termdash/widgets/input.go | 2 +- 9 files changed, 170 insertions(+), 86 deletions(-) diff --git a/cmd/termdash/config/switch.go b/cmd/termdash/config/switch.go index 4719b945f..87ad942e5 100644 --- a/cmd/termdash/config/switch.go +++ b/cmd/termdash/config/switch.go @@ -13,7 +13,7 @@ var switches = []*Switch{ { Key: "allTasks", Val: false, - Label: "Show All Tasks", + Label: "Clear All Filters", }, { Key: "enableHeader", diff --git a/cmd/termdash/cursor.go b/cmd/termdash/cursor.go index 84cdf30d6..55569ef20 100644 --- a/cmd/termdash/cursor.go +++ b/cmd/termdash/cursor.go @@ -16,12 +16,20 @@ type GridCursor struct { func NewGridCursor(tesHTTPServerAddress string) *GridCursor { return &GridCursor{ - tSource: NewTaskSource(tesHTTPServerAddress), + tSource: NewTaskSource(tesHTTPServerAddress, 100), } } func (gc *GridCursor) Len() int { return len(gc.filtered) } +func (gc *GridCursor) NextPageExists() bool { + return gc.tSource.GetNextPage() != "" +} + +func (gc *GridCursor) PreviousPageExists() bool { + return gc.tSource.GetPreviousPage() != "" +} + func (gc *GridCursor) Selected() *TaskWidget { idx := gc.Idx() if idx < gc.Len() { @@ -36,13 +44,13 @@ func (gc *GridCursor) RefreshTask(id string) *TaskWidget { } // Refresh task list -func (gc *GridCursor) RefreshTaskList() (lenChanged bool) { +func (gc *GridCursor) RefreshTaskList(previous, next bool) (lenChanged bool) { oldLen := gc.Len() // Tasks filtered by display bool gc.filtered = TaskWidgets{} var cursorVisible bool - for _, t := range gc.tSource.All() { + for _, t := range gc.tSource.List(previous, next) { if t.display { if t.Task.Id == gc.selectedID { t.Widgets.ID.Highlight() @@ -69,7 +77,7 @@ func (gc *GridCursor) RefreshTaskList() (lenChanged bool) { // Set an initial cursor position, if possible func (gc *GridCursor) Reset() { - for _, t := range gc.tSource.All() { + for _, t := range gc.tSource.List(false, false) { t.Widgets.ID.UnHighlight() } if gc.Len() > 0 { diff --git a/cmd/termdash/grid.go b/cmd/termdash/grid.go index 9c7c39c40..cc40edf26 100644 --- a/cmd/termdash/grid.go +++ b/cmd/termdash/grid.go @@ -16,7 +16,8 @@ func RedrawRows(clr bool) { // build layout y := 1 if config.GetSwitchVal("enableHeader") { - header.SetCount(cursor.Len()) + header.SetPrevious(cursor.PreviousPageExists()) + header.SetNext(cursor.NextPageExists()) header.SetFilter(config.GetVal("filterStr")) y += header.Height() } @@ -65,10 +66,20 @@ func ExpandView(t *TaskWidget) { } func RefreshDisplay() { - needsClear := cursor.RefreshTaskList() + needsClear := cursor.RefreshTaskList(false, false) RedrawRows(needsClear) } +func NextPage() { + cursor.RefreshTaskList(false, true) + RedrawRows(true) +} + +func PreviousPage() { + cursor.RefreshTaskList(true, false) + RedrawRows(true) +} + func Display() bool { var menu func() var expand bool @@ -77,14 +88,13 @@ func Display() bool { // initial draw header.Align() - cursor.RefreshTaskList() + cursor.RefreshTaskList(false, false) RedrawRows(true) HandleKeys("up", cursor.Up) HandleKeys("down", cursor.Down) - - HandleKeys("pgup", cursor.PgUp) - HandleKeys("pgdown", cursor.PgDown) + HandleKeys("left", PreviousPage) + HandleKeys("right", NextPage) HandleKeys("exit", ui.StopLoop) HandleKeys("help", func() { diff --git a/cmd/termdash/keys.go b/cmd/termdash/keys.go index 5a3bb5e6a..349f94427 100644 --- a/cmd/termdash/keys.go +++ b/cmd/termdash/keys.go @@ -17,13 +17,13 @@ var keyMap = map[string][]string{ "/sys/kbd/", "/sys/kbd/j", }, - "pgup": { - "/sys/kbd/", - "/sys/kbd/C-", + "left": { + "/sys/kbd/", + "sys/kbd/h", }, - "pgdown": { - "/sys/kbd/", - "/sys/kbd/C-", + "right": { + "/sys/kbd/", + "sys/kbd/l", }, "exit": { "/sys/kbd/q", diff --git a/cmd/termdash/menus.go b/cmd/termdash/menus.go index babbbc21b..73c55f7e8 100644 --- a/cmd/termdash/menus.go +++ b/cmd/termdash/menus.go @@ -11,7 +11,7 @@ import ( ) var helpDialog = []menu.Item{ - {Label: "[a] - toggle display of all tasks", Val: ""}, + {Label: "[a] - toggle active filter", Val: ""}, {Label: "[f] - filter displayed tasks", Val: ""}, {Label: "[h] - open this help dialog", Val: ""}, {Label: "[H] - toggle dashboard header", Val: ""}, diff --git a/cmd/termdash/tasksource.go b/cmd/termdash/tasksource.go index fddea3072..81b3e1253 100644 --- a/cmd/termdash/tasksource.go +++ b/cmd/termdash/tasksource.go @@ -10,70 +10,84 @@ import ( ) type TesTaskSource interface { - All() TaskWidgets + List(bool, bool) TaskWidgets Get(string) *TaskWidget + GetNextPage() string + GetPreviousPage() string } type TaskSource struct { - client *client.Client - tasks TaskWidgets - lock sync.RWMutex + client *client.Client + pageSize uint32 + pPage []string + cPage string + nPage string + tasks TaskWidgets + lock sync.RWMutex } -func NewTaskSource(tesHTTPServerAddress string) *TaskSource { +func NewTaskSource(tesHTTPServerAddress string, pageSize uint32) *TaskSource { // init funnel http client cli := client.NewClient(tesHTTPServerAddress) - cm := &TaskSource{ - client: cli, - lock: sync.RWMutex{}, + ts := &TaskSource{ + client: cli, + pageSize: pageSize, + lock: sync.RWMutex{}, } - cm.tasks = cm.listTasks() - return cm + ts.tasks = ts.listTasks(false, false) + return ts } -func (cm *TaskSource) listTasks() TaskWidgets { +func (ts *TaskSource) listTasks(previous, next bool) TaskWidgets { var tasks TaskWidgets - var page string - defer func() { - if r := recover(); r != nil { - if header != nil { - header.SetError(fmt.Sprintf("%v", r)) + if next && !previous { + if ts.nPage != "" { + if ts.cPage != "" { + ts.pPage = append(ts.pPage, ts.cPage) } + ts.cPage = ts.nPage } - }() - - for { - resp, err := cm.client.ListTasks(context.Background(), &tes.ListTasksRequest{ - View: tes.TaskView_BASIC, - PageToken: page, - }) - if err != nil { - panic(err) + } else if previous && !next { + if len(ts.pPage) > 0 { + ts.cPage = ts.pPage[len(ts.pPage)-1] + ts.pPage = ts.pPage[:len(ts.pPage)-1] } else { - header.SetError("") - } - for _, t := range resp.Tasks { - tasks = append(tasks, NewTaskWidget(t)) - } - page = resp.NextPageToken - if page == "" { - break + ts.cPage = "" } } + + resp, err := ts.client.ListTasks(context.Background(), &tes.ListTasksRequest{ + View: tes.TaskView_BASIC, + PageSize: ts.pageSize, + PageToken: ts.cPage, + }) + if err != nil { + header.SetError(fmt.Sprintf("%v", err)) + return nil + } else { + // header.SetError(fmt.Sprintf("Previous: %s; Next: %s; Current: %s", ts.pPage, ts.nPage, ts.cPage)) + header.SetError("") + } + ts.nPage = resp.NextPageToken + + for _, t := range resp.Tasks { + tasks = append(tasks, NewTaskWidget(t)) + } + return tasks } -// Return array of all tasks, sorted by field -func (cm *TaskSource) All() TaskWidgets { - cm.tasks = cm.listTasks() +// Return array of tasks, sorted by field +func (ts *TaskSource) List(previous, next bool) TaskWidgets { + ts.tasks = ts.listTasks(previous, next) - cm.lock.Lock() + ts.lock.Lock() var tasks TaskWidgets - for _, t := range cm.tasks { + for _, t := range ts.tasks { tasks = append(tasks, t) } - cm.lock.Unlock() + ts.lock.Unlock() sort.Sort(tasks) tasks.Filter() @@ -81,7 +95,7 @@ func (cm *TaskSource) All() TaskWidgets { } // Get a single task, by ID -func (cm *TaskSource) Get(id string) *TaskWidget { +func (ts *TaskSource) Get(id string) *TaskWidget { defer func() { if r := recover(); r != nil { if header != nil { @@ -90,7 +104,7 @@ func (cm *TaskSource) Get(id string) *TaskWidget { } }() - task, err := cm.client.GetTask(context.Background(), &tes.GetTaskRequest{ + task, err := ts.client.GetTask(context.Background(), &tes.GetTaskRequest{ Id: id, View: tes.TaskView_FULL, }) @@ -99,3 +113,16 @@ func (cm *TaskSource) Get(id string) *TaskWidget { } return NewTaskWidget(task) } + +func (ts *TaskSource) GetNextPage() string { + return ts.nPage +} + +func (ts *TaskSource) GetPreviousPage() string { + if len(ts.pPage) > 0 { + return ts.pPage[len(ts.pPage)-1] + } else if ts.cPage != "" { + return ts.cPage + } + return "" +} diff --git a/cmd/termdash/termdash.go b/cmd/termdash/termdash.go index f224e76b2..c3506e311 100644 --- a/cmd/termdash/termdash.go +++ b/cmd/termdash/termdash.go @@ -48,9 +48,9 @@ func termdash(tesHTTPServerAddress string) { defer Shutdown() // init grid, cursor, header + header = widgets.NewTermDashHeader() cursor = NewGridCursor(tesHTTPServerAddress) cGrid = compact.NewGrid() - header = widgets.NewTermDashHeader() for { exit := Display() diff --git a/cmd/termdash/widgets/header.go b/cmd/termdash/widgets/header.go index fb231627f..63c85e772 100644 --- a/cmd/termdash/widgets/header.go +++ b/cmd/termdash/widgets/header.go @@ -11,22 +11,24 @@ import ( ) type TermDashHeader struct { - Error *ui.List - Time *ui.Par - Count *ui.Par - Filter *ui.Par - help *ui.Par - bg *ui.Par + Error *ui.List + Time *ui.Par + Previous *ui.Par + Next *ui.Par + Filter *ui.Par + help *ui.Par + bg *ui.Par } func NewTermDashHeader() *TermDashHeader { return &TermDashHeader{ - Error: headerError(""), - Time: headerPar(0, timeStr()), - Count: headerPar(33, "-"), - Filter: headerPar(50, ""), - help: headerPar(ui.TermWidth()-19, "press [h] for help"), - bg: headerBg(), + Error: headerError(""), + Time: headerPar(0, 0, timeStr()), + Previous: headerPar(ui.TermWidth()/2-12, 0, ""), + Next: headerPar(ui.TermWidth()/2+1, 0, ""), + Filter: headerPar(0, 1, ""), + help: headerPar(ui.TermWidth()-20, 0, "press [h] for help"), + bg: headerBg(), } } @@ -35,8 +37,11 @@ func (c *TermDashHeader) Buffer() ui.Buffer { buf.Merge(c.bg.Buffer()) c.Time.Text = timeStr() buf.Merge(c.Time.Buffer()) - buf.Merge(c.Count.Buffer()) - buf.Merge(c.Filter.Buffer()) + buf.Merge(c.Previous.Buffer()) + buf.Merge(c.Next.Buffer()) + if c.Filter.Text != "" { + buf.Merge(c.Filter.Buffer()) + } buf.Merge(c.help.Buffer()) if len(c.Error.Items) > 0 { buf.Merge(c.Error.Buffer()) @@ -45,7 +50,7 @@ func (c *TermDashHeader) Buffer() ui.Buffer { } func (c *TermDashHeader) Align() { - c.bg.SetWidth(ui.TermWidth() - 1) + c.bg.SetWidth(ui.TermWidth()) } func (c *TermDashHeader) Height() int { @@ -54,32 +59,65 @@ func (c *TermDashHeader) Height() int { func headerBg() *ui.Par { bg := ui.NewPar("") - bg.X = 1 + bg.X = 0 bg.Height = 1 + bg.Width = ui.TermWidth() bg.Border = false bg.Bg = ui.ThemeAttr("header.bg") return bg } -func (c *TermDashHeader) SetCount(val int) { - c.Count.Text = fmt.Sprintf("%d tasks", val) +func (c *TermDashHeader) SetPrevious(set bool) { + if set { + c.Previous.Text = "<- Previous" + } else { + c.Previous.Text = "" + } +} + +func (c *TermDashHeader) SetNext(set bool) { + if set { + c.Next.Text = "Next ->" + } else { + c.Next.Text = "" + } } func (c *TermDashHeader) SetFilter(val string) { - if val == "" { + switch { + case val == "" && len(c.Error.Items) == 0: + c.bg.Height = 1 c.Filter.Text = "" - } else { - c.Filter.Text = fmt.Sprintf("filter: %s", val) + case val == "" && len(c.Error.Items) > 0: + c.Filter.Text = "" + case val != "" && len(c.Error.Items) == 0: + c.bg.Height = 2 + val = fmt.Sprintf("filter: %s", val) + c.Filter.X = ui.TermWidth()/2 - len(val)/2 + c.Filter.Text = val + case val != "" && len(c.Error.Items) > 0: + c.bg.Height = 4 + val = fmt.Sprintf("filter: %s", val) + c.Filter.X = ui.TermWidth()/2 - len(val)/2 + c.Filter.Text = val } } func (c *TermDashHeader) SetError(val string) { - if val == "" { + switch { + case val == "" && c.Filter.Text == "": c.bg.Height = 1 c.Error.Items = []string{} - } else { + case val == "" && c.Filter.Text != "": + c.Error.Items = []string{} + case val != "" && c.Filter.Text == "": + c.Error.Y = 1 c.bg.Height = 3 c.Error.Items = []string{fmt.Sprintf("ERROR: %s", val)} + case val != "" && c.Filter.Text != "": + c.Error.Y = 2 + c.bg.Height = 4 + c.Error.Items = []string{fmt.Sprintf("ERROR: %s", val)} } } @@ -88,12 +126,13 @@ func timeStr() string { return fmt.Sprintf("funnel - %s", ts) } -func headerPar(x int, s string) *ui.Par { +func headerPar(x, y int, s string) *ui.Par { p := ui.NewPar(fmt.Sprintf(" %s", s)) p.X = x + p.Y = y p.Border = false p.Height = 1 - p.Width = 24 + p.Width = 50 p.Bg = ui.ThemeAttr("header.bg") p.TextFgColor = ui.ThemeAttr("header.fg") p.TextBgColor = ui.ThemeAttr("header.bg") diff --git a/cmd/termdash/widgets/input.go b/cmd/termdash/widgets/input.go index 375eb14de..e8377585e 100644 --- a/cmd/termdash/widgets/input.go +++ b/cmd/termdash/widgets/input.go @@ -30,7 +30,7 @@ func NewInput() *Input { i := &Input{ Block: *ui.NewBlock(), Label: "input", - MaxLen: 20, + MaxLen: 40, TextFgColor: ui.ThemeAttr("menu.text.fg"), TextBgColor: ui.ThemeAttr("menu.text.bg"), padding: Padding{4, 2},