You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

605 lines
15 KiB

package shlvdialogs
import (
"fmt"
"slices"
"strings"
"git.brettb.xyz/goinv/client/internal/api"
"git.brettb.xyz/goinv/client/internal/types"
"git.brettb.xyz/goinv/client/internal/ui/dialogs"
"git.brettb.xyz/goinv/client/internal/ui/style"
"git.brettb.xyz/goinv/client/internal/ui/utils"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"go.uber.org/zap"
)
const (
shelfEditDialogMaxWidth = 100
shelfEditDialogHeight = 17
)
const (
editShelfFormFocus = 0 + iota
editCategoriesFocus
editCategoryPagesFocus
editShelfNameFieldFocus
editShelfRoomFieldFocus
editShelfBuildingFieldFocus
editShelfDescriptionFieldFocus
)
type ShelfEditDialog struct {
*tview.Box
layout *tview.Flex
editCategoryLabels []string
categories *tview.TextView
categoryPages *tview.Pages
generalInfoPage *tview.Flex
locationPage *tview.Flex
commentsPage *tview.Flex
form *tview.Form
display bool
activePageIndex int
focusElement int
logger *zap.Logger
client *api.APIClient
shelf *types.ShelfLocation
buildingList []*types.Building
shelfNameField *tview.InputField
shelfRoomField *tview.InputField
shelfDescriptionArea *tview.TextArea
shelfBuildingField *tview.DropDown
focusMap map[int]tview.Primitive
cancelHandler func()
editHandler func()
}
func NewShelfEditDialog(logger *zap.Logger, client *api.APIClient) *ShelfEditDialog {
editDialog := ShelfEditDialog{
Box: tview.NewBox(),
layout: tview.NewFlex(),
editCategoryLabels: []string{
"General",
"Location",
"Comments",
},
categories: tview.NewTextView(),
categoryPages: tview.NewPages(),
generalInfoPage: tview.NewFlex(),
locationPage: tview.NewFlex(),
commentsPage: tview.NewFlex(),
form: tview.NewForm(),
display: false,
activePageIndex: 0,
logger: logger,
client: client,
shelfNameField: tview.NewInputField(),
shelfRoomField: tview.NewInputField(),
shelfBuildingField: tview.NewDropDown(),
shelfDescriptionArea: tview.NewTextArea(),
}
editDialog.focusMap = map[int]tview.Primitive{
editShelfNameFieldFocus: editDialog.shelfNameField,
editShelfRoomFieldFocus: editDialog.shelfRoomField,
editShelfBuildingFieldFocus: editDialog.shelfBuildingField,
editShelfDescriptionFieldFocus: editDialog.shelfDescriptionArea,
}
editDialog.setupLayout()
editDialog.setActiveCategory(0)
editDialog.initCustomInputHandlers()
return &editDialog
}
func (d *ShelfEditDialog) Display() {
d.display = true
d.initData()
d.focusElement = editCategoryPagesFocus
}
func (d *ShelfEditDialog) IsDisplay() bool {
return d.display
}
func (d *ShelfEditDialog) Hide() {
d.display = false
}
func (d *ShelfEditDialog) HasFocus() bool {
return utils.CheckFocus(d.categories, d.categoryPages, d.Box, d.form)
}
func (d *ShelfEditDialog) Focus(delegate func(tview.Primitive)) {
switch d.focusElement {
case editShelfFormFocus:
button := d.form.GetButton(d.form.GetButtonCount() - 1)
button.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == utils.SwitchFocusKey.EventKey() {
d.focusElement = editCategoriesFocus
d.Focus(delegate)
d.form.SetFocus(0)
return nil
}
if event.Key() == tcell.KeyEnter {
return nil
}
return event
})
delegate(d.form)
case editCategoriesFocus:
d.categories.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == utils.SwitchFocusKey.EventKey() {
d.focusElement = editCategoryPagesFocus
d.Focus(delegate)
return nil
}
if event.Key() == tcell.KeyBacktab {
d.focusElement = editShelfFormFocus
d.Focus(delegate)
return nil
}
event = utils.ParseKeyEventKey(d.logger, event)
if event.Key() == tcell.KeyDown {
d.nextCategory()
}
if event.Key() == tcell.KeyUp {
d.previousCategory()
}
return event
})
delegate(d.categories)
case editCategoryPagesFocus:
delegate(d.categoryPages)
default:
delegate(d.focusMap[d.focusElement])
}
}
func (d *ShelfEditDialog) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) {
return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(tview.Primitive)) {
d.logger.Sugar().Debugf("shelf edit dialog event %v received", event)
if event.Key() == utils.CloseDialogKey.EventKey() && !d.dropdownHasFocus() {
d.cancelHandler()
return
}
if d.generalInfoPage.HasFocus() {
if handler := d.generalInfoPage.InputHandler(); handler != nil {
if event.Key() == utils.SwitchFocusKey.EventKey() {
d.setGeneralInfoNextFocus()
}
if event.Key() == tcell.KeyBacktab {
d.setGeneralInfoPrevFocus()
}
handler(event, setFocus)
return
}
}
if d.locationPage.HasFocus() {
if handler := d.locationPage.InputHandler(); handler != nil {
if event.Key() == utils.SwitchFocusKey.EventKey() {
d.setLocationNextFocus()
}
if event.Key() == tcell.KeyBacktab {
d.setLocationPrevFocus()
}
handler(event, setFocus)
return
}
}
if d.commentsPage.HasFocus() {
if handler := d.commentsPage.InputHandler(); handler != nil {
if event.Key() == utils.SwitchFocusKey.EventKey() {
d.setCommentsNextFocus()
}
if event.Key() == tcell.KeyBacktab {
d.setCommentsPrevFocus()
}
handler(event, setFocus)
return
}
}
if d.categories.HasFocus() {
if categoryHandler := d.categories.InputHandler(); categoryHandler != nil {
categoryHandler(event, setFocus)
return
}
}
if d.form.HasFocus() {
if formHandler := d.form.InputHandler(); formHandler != nil {
if event.Key() == tcell.KeyEnter {
enterButton := d.form.GetButton(d.form.GetButtonCount() - 1)
if enterButton.HasFocus() {
d.editHandler()
}
}
formHandler(event, setFocus)
return
}
}
})
}
func (d *ShelfEditDialog) SetRect(x, y, width, height int) {
if width > shelfEditDialogMaxWidth {
emptySpace := (width - shelfEditDialogMaxWidth) / 2
x += emptySpace
width = shelfEditDialogMaxWidth
}
if height > shelfEditDialogHeight {
emptySpace := (height - shelfEditDialogHeight) / 2
y += emptySpace
height = shelfEditDialogHeight
}
d.Box.SetRect(x, y, width, height)
}
func (d *ShelfEditDialog) Draw(screen tcell.Screen) {
if !d.display {
return
}
d.Box.DrawForSubclass(screen, d)
x, y, width, height := d.Box.GetInnerRect()
d.layout.SetRect(x, y, width, height)
d.layout.Draw(screen)
}
func (d *ShelfEditDialog) SetEditFunc(f func()) *ShelfEditDialog {
d.editHandler = f
enterButton := d.form.GetButton(d.form.GetButtonCount() - 1)
enterButton.SetSelectedFunc(f)
return d
}
func (d *ShelfEditDialog) SetCancelFunc(f func()) *ShelfEditDialog {
d.cancelHandler = f
cancelButton := d.form.GetButton(d.form.GetButtonCount() - 2)
cancelButton.SetSelectedFunc(f)
return d
}
func (d *ShelfEditDialog) dropdownHasFocus() bool {
return utils.CheckFocus(d.shelfBuildingField)
}
func (d *ShelfEditDialog) SetShelf(a *types.ShelfLocation) {
d.shelf = a
}
func (d *ShelfEditDialog) initData() {
// Get available buildings
buildingList, _ := d.client.RetrieveAllBuildings()
d.buildingList = buildingList
buildingOptions := []string{""}
for _, building := range d.buildingList {
buildingOptions = append(buildingOptions, building.Name) // In case we want to do custom formatting later
}
d.setActiveCategory(0)
// General category
if d.shelf == nil {
d.shelfNameField.SetText("")
// Location category
d.shelfBuildingField.SetOptions(buildingOptions, nil)
d.shelfRoomField.SetText("")
// Comments category
d.shelfDescriptionArea.SetText("", true)
} else {
d.shelfNameField.SetText(d.shelf.Name)
// Location category
d.shelfBuildingField.SetOptions(buildingOptions, nil)
if d.shelf.Building != nil {
d.shelfBuildingField.SetCurrentOption(findOption(buildingOptions, d.shelf.Building.Name))
}
d.shelfRoomField.SetText("")
// Comments category
d.shelfDescriptionArea.SetText("", true)
}
}
func findOption(options []string, search string) int {
return slices.Index(options, search)
}
func (d *ShelfEditDialog) setupLayout() {
bgColor := style.DialogBgColor
fgColor := style.DialogFgColor
d.categories.SetDynamicColors(true).
SetWrap(true).
SetTextAlign(tview.AlignLeft)
d.categories.SetBackgroundColor(fgColor)
d.categories.SetTextColor(bgColor)
d.categories.SetBorder(true)
d.categories.SetBorderColor(style.DialogSubBoxBorderColor)
d.categoryPages.SetBackgroundColor(bgColor)
d.categoryPages.SetBorder(true)
d.categoryPages.SetBorderColor(style.DialogSubBoxBorderColor)
d.setupGeneralInfoPageUI()
d.setupLocationPageUI()
d.setupCommentsPageUI()
activatedStyle := tcell.StyleDefault.
Background(style.ButtonSelectedBgColor).
Foreground(style.ButtonSelectedFgColor)
d.form.SetBackgroundColor(bgColor)
d.form.AddButton("Cancel", nil)
d.form.AddButton("Edit", nil)
d.form.SetButtonsAlign(tview.AlignRight)
d.form.SetButtonBackgroundColor(style.ButtonBgColor)
d.form.SetButtonTextColor(style.ButtonFgColor)
d.form.SetButtonActivatedStyle(activatedStyle)
d.categoryPages.AddPage(d.editCategoryLabels[generalPageIndex], d.generalInfoPage, true, true)
d.categoryPages.AddPage(d.editCategoryLabels[locationPageIndex], d.locationPage, true, true)
d.categoryPages.AddPage(d.editCategoryLabels[commentPageIndex], d.commentsPage, true, true)
d.layout.SetBackgroundColor(bgColor)
d.layout.SetBorder(true)
d.layout.SetBorderColor(style.DialogBorderColor)
d.layout.SetTitle("SHELF EDIT")
d.layout.SetTitleColor(style.DialogFgColor)
_, layoutWidth := utils.AlignStringListWidth(d.editCategoryLabels)
layout := tview.NewFlex().SetDirection(tview.FlexColumn)
layout.AddItem(d.categories, layoutWidth+6, 0, true)
layout.AddItem(d.categoryPages, 0, 1, true)
layout.SetBackgroundColor(bgColor)
d.layout.SetDirection(tview.FlexRow)
d.layout.AddItem(layout, 0, 1, true)
d.layout.AddItem(d.form, dialogs.DialogFormHeight, 0, true)
}
func (d *ShelfEditDialog) setupGeneralInfoPageUI() {
bgColor := style.DialogBgColor
inputFieldBgColor := style.InputFieldBgColor
pageLabelWidth := 14
d.shelfNameField.SetLabel("name:")
d.shelfNameField.SetLabelWidth(pageLabelWidth)
d.shelfNameField.SetLabelColor(bgColor)
d.shelfNameField.SetBackgroundColor(style.DialogFgColor)
d.shelfNameField.SetFieldBackgroundColor(inputFieldBgColor)
d.generalInfoPage.SetDirection(tview.FlexRow)
d.generalInfoPage.AddItem(d.shelfNameField, 1, 0, true)
d.generalInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
}
func (d *ShelfEditDialog) setupLocationPageUI() {
bgColor := style.DialogBgColor
inputFieldBgColor := style.InputFieldBgColor
pageLabelWidth := 12
ddUnselectedStyle := style.DropdownUnselected
ddSelectedStyle := style.DropdownSelected
d.shelfRoomField.SetLabel("room:")
d.shelfRoomField.SetLabelWidth(pageLabelWidth)
d.shelfRoomField.SetLabelColor(bgColor)
d.shelfRoomField.SetBackgroundColor(style.DialogFgColor)
d.shelfRoomField.SetFieldBackgroundColor(inputFieldBgColor)
d.shelfBuildingField.SetLabel("building:")
d.shelfBuildingField.SetLabelWidth(pageLabelWidth)
d.shelfBuildingField.SetBackgroundColor(bgColor)
d.shelfBuildingField.SetLabelColor(style.DialogFgColor)
d.shelfBuildingField.SetListStyles(ddUnselectedStyle, ddSelectedStyle)
d.shelfBuildingField.SetFieldBackgroundColor(inputFieldBgColor)
d.locationPage.SetDirection(tview.FlexRow)
d.locationPage.AddItem(d.shelfRoomField, 1, 0, true)
d.locationPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true)
d.locationPage.AddItem(d.shelfBuildingField, 1, 0, true)
d.locationPage.SetBackgroundColor(bgColor)
}
func (d *ShelfEditDialog) setupCommentsPageUI() {
bgColor := style.DialogBgColor
inputFieldBgColor := style.InputFieldBgColor
pageLabelWidth := 12
d.shelfDescriptionArea.SetLabel("comments:")
d.shelfDescriptionArea.SetLabelWidth(pageLabelWidth)
d.shelfDescriptionArea.SetBackgroundColor(inputFieldBgColor)
d.shelfDescriptionArea.SetLabelStyle(tcell.StyleDefault.Foreground(style.DialogFgColor).Background(bgColor))
d.shelfDescriptionArea.SetWrap(true)
d.commentsPage.AddItem(d.shelfDescriptionArea, 0, 1, true)
d.commentsPage.SetBackgroundColor(bgColor)
}
func (d *ShelfEditDialog) setActiveCategory(idx int) {
fgColor := style.ButtonSelectedFgColor
bgBolor := style.ButtonSelectedBgColor
ctgTextColor := style.GetColorHex(fgColor)
ctgBgColor := style.GetColorHex(bgBolor)
d.activePageIndex = idx
d.categories.Clear()
alignedList, _ := utils.AlignStringListWidth(d.editCategoryLabels)
var ctgList []string
for i, lbl := range alignedList {
if i == idx {
ctgList = append(ctgList, fmt.Sprintf("[%s:%s:b]-> %s ", ctgTextColor, ctgBgColor, lbl))
continue
}
ctgList = append(ctgList, fmt.Sprintf("[-:-:-] %s ", lbl))
}
d.categories.SetText(strings.Join(ctgList, "\n"))
d.categoryPages.SwitchToPage(d.editCategoryLabels[idx])
}
func (d *ShelfEditDialog) nextCategory() {
activePage := d.activePageIndex
if d.activePageIndex < len(d.editCategoryLabels)-1 {
activePage++
d.setActiveCategory(activePage)
return
}
d.setActiveCategory(0)
}
func (d *ShelfEditDialog) previousCategory() {
activePage := d.activePageIndex
if d.activePageIndex > 0 {
activePage--
d.setActiveCategory(activePage)
return
}
d.setActiveCategory(len(d.editCategoryLabels) - 1)
}
func (d *ShelfEditDialog) initCustomInputHandlers() {
d.shelfBuildingField.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
event = utils.ParseKeyEventKey(d.logger, event)
return event
})
}
func (d *ShelfEditDialog) setGeneralInfoNextFocus() {
// Name -> Quantity -> Length -> Manufacturer -> Model -> Price
if d.shelfNameField.HasFocus() {
d.focusElement = editShelfFormFocus
return
}
d.focusElement = editShelfFormFocus
}
func (d *ShelfEditDialog) setGeneralInfoPrevFocus() {
if d.shelfNameField.HasFocus() {
d.focusElement = createCategoriesFocus
return
}
d.focusElement = editShelfNameFieldFocus
}
func (d *ShelfEditDialog) setLocationNextFocus() {
// Category -> Shelf
if d.shelfRoomField.HasFocus() {
d.focusElement = editShelfBuildingFieldFocus
return
}
d.focusElement = editShelfFormFocus
}
func (d *ShelfEditDialog) setLocationPrevFocus() {
if d.shelfRoomField.HasFocus() {
d.focusElement = createCategoriesFocus
return
}
if d.shelfBuildingField.HasFocus() {
d.focusElement = editShelfRoomFieldFocus
return
}
d.focusElement = editShelfBuildingFieldFocus
}
func (d *ShelfEditDialog) setCommentsNextFocus() {
d.focusElement = editShelfFormFocus
}
func (d *ShelfEditDialog) setCommentsPrevFocus() {
d.focusElement = createCategoriesFocus
}
func (d *ShelfEditDialog) EditShelfOptions() types.CreateShelfRequest {
var (
buildingID *uint64
)
selectedBuildingIndex, _ := d.shelfBuildingField.GetCurrentOption()
if len(d.buildingList) > 0 && selectedBuildingIndex > 0 {
buildingID = &d.buildingList[selectedBuildingIndex-1].ID
}
req := types.CreateShelfRequest{
Name: d.shelfNameField.GetText(),
RoomNumber: d.shelfRoomField.GetText(),
Description: d.shelfDescriptionArea.GetText(),
BuildingID: buildingID,
}
return req
}