diff --git a/cmd/frostfs-lens/internal/tui/help-pages/hotkeys.txt b/cmd/frostfs-lens/internal/tui/help-pages/hotkeys.txt new file mode 100644 index 000000000..c371b34e9 --- /dev/null +++ b/cmd/frostfs-lens/internal/tui/help-pages/hotkeys.txt @@ -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. diff --git a/cmd/frostfs-lens/internal/tui/help-pages/searching.txt b/cmd/frostfs-lens/internal/tui/help-pages/searching.txt new file mode 100644 index 000000000..bc2be512b --- /dev/null +++ b/cmd/frostfs-lens/internal/tui/help-pages/searching.txt @@ -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 diff --git a/cmd/frostfs-lens/internal/tui/help.go b/cmd/frostfs-lens/internal/tui/help.go new file mode 100644 index 000000000..3ab8fede0 --- /dev/null +++ b/cmd/frostfs-lens/internal/tui/help.go @@ -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) {}) + }) +} diff --git a/cmd/frostfs-lens/internal/tui/ui.go b/cmd/frostfs-lens/internal/tui/ui.go index 701f2b331..bcc082821 100644 --- a/cmd/frostfs-lens/internal/tui/ui.go +++ b/cmd/frostfs-lens/internal/tui/ui.go @@ -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) {}) } }