forked from TrueCloudLab/frostfs-node
[#1223] lens/tui: Add app help
Signed-off-by: Aleksey Savchuk <a.savchuk@yadro.com>
This commit is contained in:
parent
ed396448ac
commit
e655336390
4 changed files with 179 additions and 1 deletions
38
cmd/frostfs-lens/internal/tui/help-pages/hotkeys.txt
Normal file
38
cmd/frostfs-lens/internal/tui/help-pages/hotkeys.txt
Normal file
|
@ -0,0 +1,38 @@
|
|||
[green::b]HOTKEYS[-::-]
|
||||
|
||||
[green::b]Navigation[-::-]
|
||||
|
||||
[yellow::b]Down Arrow[-::-] / [yellow::b]j[-::-]
|
||||
Scroll down.
|
||||
|
||||
[yellow::b]Up Arrow[-::-] / [yellow::b]k[-::-]
|
||||
Scroll up.
|
||||
|
||||
[yellow::b]Page Down[-::-] / [yellow::b]Ctrl-f[-::-]
|
||||
Scroll down by a full page.
|
||||
|
||||
[yellow::b]Page Up[-::-] / [yellow::b]Ctrl-b[-::-]
|
||||
Scroll up by a full page.
|
||||
|
||||
[green::b]Actions[-::-]
|
||||
|
||||
[yellow::b]Enter[-::-]
|
||||
Perform actions based on the current context:
|
||||
- In Buckets View:
|
||||
- Expand/collapse the selected bucket to show/hide its nested buckets.
|
||||
- If no nested buckets exist, navigate to the selected bucket's records.
|
||||
- In Records View: Open the detailed view of the selected record.
|
||||
|
||||
[yellow::b]Escape[-::-]
|
||||
Return to the previous page, opposite of [yellow::b]Enter[-::-].
|
||||
|
||||
Refer to the [green::b]SEARCHING[-::-] section for more specific actions.
|
||||
|
||||
|
||||
[green::b]Alternative Action Hotkeys[-::-]
|
||||
|
||||
[yellow::b]Ctrl-r[-::-]
|
||||
Directly navigate to the selected bucket's records.
|
||||
|
||||
[yellow::b]Ctrl-d[-::-]
|
||||
Access the detailed view of the selected bucket.
|
26
cmd/frostfs-lens/internal/tui/help-pages/searching.txt
Normal file
26
cmd/frostfs-lens/internal/tui/help-pages/searching.txt
Normal file
|
@ -0,0 +1,26 @@
|
|||
[green::b]SEARCHING[-::-]
|
||||
|
||||
[green::b]Hotkeys[-::-]
|
||||
|
||||
[yellow::b]/[-::-]
|
||||
Initiate the search prompt.
|
||||
- The prompt follows this syntax: [yellow::b]tag:value [+ tag:value]...[-::-]
|
||||
- Multiple filter can be combined with [yellow::b]+[-::-], the result is an intersection of those filters' result sets.
|
||||
- Any leading and trailing whitespace will be ignored.
|
||||
- An empty prompt will return all results with no filters applied.
|
||||
- Refer to the [green::b]Available Search Filters[-::-] section below for a list of valid filter tags.
|
||||
|
||||
[yellow::b]Enter[-::-]
|
||||
Execute the search based on the entered prompt.
|
||||
- If the prompt is invalid, an error message will be displayed.
|
||||
|
||||
[yellow::b]Escape[-::-]
|
||||
Exit the search prompt without performing a search.
|
||||
|
||||
[yellow::b]Down Arrow[-::-], [yellow::b]Up Arrow[-::-]
|
||||
Scroll through the search history.
|
||||
|
||||
|
||||
[green::b]Available Search Filters[-::-]
|
||||
|
||||
%s
|
101
cmd/frostfs-lens/internal/tui/help.go
Normal file
101
cmd/frostfs-lens/internal/tui/help.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package tui
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed help-pages/hotkeys.txt
|
||||
hotkeysHelpText string
|
||||
|
||||
//go:embed help-pages/searching.txt
|
||||
searchingHelpText string
|
||||
)
|
||||
|
||||
type HelpPage struct {
|
||||
*tview.Box
|
||||
pages []*tview.TextView
|
||||
currentPage int
|
||||
|
||||
filters []string
|
||||
filterHints map[string]string
|
||||
}
|
||||
|
||||
func NewHelpPage(filters []string, hints map[string]string) *HelpPage {
|
||||
hp := &HelpPage{
|
||||
Box: tview.NewBox(),
|
||||
filters: filters,
|
||||
filterHints: hints,
|
||||
}
|
||||
|
||||
page := tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetText(hotkeysHelpText)
|
||||
hp.addPage(page)
|
||||
|
||||
page = tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetText(fmt.Sprintf(searchingHelpText, hp.getFiltersText()))
|
||||
hp.addPage(page)
|
||||
|
||||
return hp
|
||||
}
|
||||
|
||||
func (hp *HelpPage) addPage(page *tview.TextView) {
|
||||
hp.pages = append(hp.pages, page)
|
||||
}
|
||||
|
||||
func (hp *HelpPage) getFiltersText() string {
|
||||
if len(hp.filters) == 0 {
|
||||
return "\t\tNo filters defined.\n"
|
||||
}
|
||||
|
||||
filtersText := strings.Builder{}
|
||||
gapSize := 4
|
||||
|
||||
tagMaxWidth := 3
|
||||
for _, filter := range hp.filters {
|
||||
tagMaxWidth = max(tagMaxWidth, len(filter))
|
||||
}
|
||||
filtersText.WriteString("\t\t[yellow::b]Tag")
|
||||
filtersText.WriteString(strings.Repeat(" ", gapSize))
|
||||
filtersText.WriteString("\tValue[-::-]\n\n")
|
||||
|
||||
for _, filter := range hp.filters {
|
||||
filtersText.WriteString("\t\t")
|
||||
filtersText.WriteString(filter)
|
||||
filtersText.WriteString(strings.Repeat(" ", tagMaxWidth-len(filter)+gapSize))
|
||||
filtersText.WriteString(hp.filterHints[filter])
|
||||
filtersText.WriteRune('\n')
|
||||
}
|
||||
|
||||
return filtersText.String()
|
||||
}
|
||||
|
||||
func (hp *HelpPage) Draw(screen tcell.Screen) {
|
||||
x, y, width, height := hp.GetInnerRect()
|
||||
hp.pages[hp.currentPage].SetRect(x+1, y+1, width-2, height-2)
|
||||
hp.pages[hp.currentPage].Draw(screen)
|
||||
}
|
||||
|
||||
func (hp *HelpPage) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
||||
return hp.WrapInputHandler(func(event *tcell.EventKey, _ func(tview.Primitive)) {
|
||||
if event.Key() == tcell.KeyEnter {
|
||||
hp.currentPage++
|
||||
hp.currentPage %= len(hp.pages)
|
||||
return
|
||||
}
|
||||
hp.pages[hp.currentPage].InputHandler()(event, func(tview.Primitive) {})
|
||||
})
|
||||
}
|
||||
|
||||
func (hp *HelpPage) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
|
||||
return hp.WrapMouseHandler(func(action tview.MouseAction, event *tcell.EventMouse, _ func(tview.Primitive)) (consumed bool, capture tview.Primitive) {
|
||||
return hp.pages[hp.currentPage].MouseHandler()(action, event, func(tview.Primitive) {})
|
||||
})
|
||||
}
|
|
@ -60,6 +60,8 @@ type UI struct {
|
|||
loadingBar *LoadingBar
|
||||
helpBar *tview.TextView
|
||||
|
||||
helpPage *HelpPage
|
||||
|
||||
searchErrorBar *tview.TextView
|
||||
|
||||
isSearching bool
|
||||
|
@ -275,7 +277,17 @@ func (ui *UI) draw(screen tcell.Screen) {
|
|||
|
||||
switch {
|
||||
case ui.isShowingHelp:
|
||||
pageToDraw = ui.pageStub
|
||||
if ui.helpPage == nil {
|
||||
var filters []string
|
||||
for f := range ui.filters {
|
||||
filters = append(filters, f)
|
||||
}
|
||||
for f := range ui.compositeFilters {
|
||||
filters = append(filters, f)
|
||||
}
|
||||
ui.helpPage = NewHelpPage(filters, ui.filterHints)
|
||||
}
|
||||
pageToDraw = ui.helpPage
|
||||
case ui.mountedPage != nil:
|
||||
pageToDraw = ui.mountedPage
|
||||
default:
|
||||
|
@ -429,6 +441,7 @@ func (ui *UI) handleInputOnShowingHelp(event *tcell.EventKey) {
|
|||
case k == tcell.KeyRune && r == 'q':
|
||||
ui.stop()
|
||||
default:
|
||||
ui.helpPage.InputHandler()(event, func(tview.Primitive) {})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue