| @ -0,0 +1,156 @@ | |||
| package shelves | |||
| import ( | |||
| "fmt" | |||
| "git.brettb.xyz/goinv/client/internal/ui/dialogs" | |||
| "git.brettb.xyz/goinv/client/internal/ui/style" | |||
| ) | |||
| func (a *Shelves) runCommand(cmd string) { | |||
| switch cmd { | |||
| case "create shelf": | |||
| a.createDialog.Display() | |||
| case "edit shelf": | |||
| a.cedit() | |||
| case "delete shelf": | |||
| a.cdelete() | |||
| case "refresh": | |||
| a.crefresh() | |||
| } | |||
| } | |||
| func (a *Shelves) cNotImplemented() { | |||
| a.displayError("not implemented", fmt.Errorf("this command has not been implemented")) | |||
| } | |||
| // Confirm deletion | |||
| func (a *Shelves) cdelete() { | |||
| selectedItem := a.getSelectedItem() | |||
| // Empty table | |||
| if selectedItem == nil { | |||
| a.displayError("DELETE SHELF ERROR", fmt.Errorf("no shelfs to delete")) | |||
| return | |||
| } | |||
| title := "delete shelf" | |||
| a.confirmDialog.SetTitle(title) | |||
| a.confirmData = status_CONFIRM_DELETE_SHELF | |||
| bgColor := style.GetColorHex(style.DialogSubBoxBorderColor) | |||
| fgColor := style.GetColorHex(style.DialogFgColor) | |||
| shelfName := fmt.Sprintf("[%s:%s:b]SHELF NAME:[:-:-] %s", fgColor, bgColor, selectedItem.name) | |||
| confirmMsg := fmt.Sprintf("%s\n%s\nAre you sure you want to delete the selected shelf ?", shelfName) | |||
| a.confirmDialog.SetText(confirmMsg) | |||
| a.confirmDialog.Display() | |||
| } | |||
| func (a *Shelves) cedit() { | |||
| selectedItem := a.getSelectedItem() | |||
| if selectedItem == nil { | |||
| a.displayError("DELETE SHELF ERROR", fmt.Errorf("no shelves to edit")) | |||
| return | |||
| } | |||
| shelf, err := a.client.RetrieveShelfByID(selectedItem.id) | |||
| if err != nil { | |||
| a.displayError("DELETE SHELF ERROR", fmt.Errorf("unable to retrieve shelf from server")) | |||
| return | |||
| } | |||
| a.editDialog.SetShelf(shelf) | |||
| a.editDialog.Display() | |||
| } | |||
| func (a *Shelves) edit() { | |||
| selectedItem := a.getSelectedItem() | |||
| createReq := a.editDialog.EditShelfOptions() | |||
| if createReq.Name == "" { | |||
| a.displayError("SHELF EDIT ERROR", fmt.Errorf("shelf name cannot be empty")) | |||
| return | |||
| } | |||
| _, err := a.client.UpdateShelf(selectedItem.id, createReq) | |||
| if err != nil { | |||
| a.displayError("SHELF EDIT ERROR", err) | |||
| return | |||
| } | |||
| a.crefresh() | |||
| } | |||
| func (a *Shelves) delete() { | |||
| selectedItem := a.getSelectedItem() | |||
| a.progressDialog.SetTitle(fmt.Sprintf("deleting shelf %s", selectedItem.id)) | |||
| a.progressDialog.Display() | |||
| del := func() { | |||
| _, err := a.client.DeleteShelfByID(selectedItem.id) | |||
| a.progressDialog.Hide() | |||
| if err != nil { | |||
| a.displayError("DELETE SHELF ERROR", err) | |||
| return | |||
| } | |||
| // display success message | |||
| a.messageDialog.SetTitle(fmt.Sprintf("deleting shelf %s", selectedItem.id)) | |||
| a.messageDialog.SetText(dialogs.MessageGeneric, "Success!", fmt.Sprintf("Shelf %s successfully deleted.", selectedItem.id)) | |||
| a.messageDialog.Display() | |||
| a.UpdateShelfData() | |||
| } | |||
| del() | |||
| } | |||
| func (a *Shelves) crefresh() { | |||
| a.progressDialog.SetTitle("refreshing shelfs") | |||
| a.progressDialog.Display() | |||
| ref := func() { | |||
| a.UpdateShelfData() | |||
| a.UpdateShelfData() | |||
| a.progressDialog.Hide() | |||
| if !a.errorDialog.IsDisplay() { | |||
| a.messageDialog.SetTitle("shelf refresh") | |||
| a.messageDialog.SetText(dialogs.MessageGeneric, "Refreshed!", "Successfully refreshed page.") | |||
| a.messageDialog.Display() | |||
| } | |||
| } | |||
| ref() | |||
| } | |||
| func (a *Shelves) create() { | |||
| createReq := a.createDialog.CreateShelfOptions() | |||
| if createReq.Name == "" { | |||
| a.displayError("SHELF CREATE ERROR", fmt.Errorf("shelf name cannot be empty")) | |||
| return | |||
| } | |||
| _, err := a.client.CreateShelf(createReq) | |||
| if err != nil { | |||
| a.displayError("SHELF CREATE ERROR", err) | |||
| return | |||
| } | |||
| a.crefresh() | |||
| a.shelfTable.ScrollToEnd() | |||
| a.shelfTable.Select(a.shelfTable.GetRowCount(), 0) | |||
| } | |||
| func (a *Shelves) displayError(title string, err error) { | |||
| a.errorDialog.SetTitle(title) | |||
| a.errorDialog.SetText(fmt.Sprintf("%v", err)) | |||
| a.errorDialog.Display() | |||
| } | |||
| @ -0,0 +1,48 @@ | |||
| package shelves | |||
| import "git.brettb.xyz/goinv/client/internal/types" | |||
| func (a *Shelves) UpdateData() { | |||
| a.UpdateShelfData() | |||
| a.UpdateBuildingData() | |||
| } | |||
| func (a *Shelves) UpdateShelfData() { | |||
| shelves, err := a.shelfListFunc() | |||
| a.shelfList.mu.Lock() | |||
| defer a.shelfList.mu.Unlock() | |||
| if err != nil { | |||
| a.displayError("could not retrieve shelves", err) | |||
| a.shelfList.dirty = true | |||
| return | |||
| } | |||
| a.shelfList.dirty = false | |||
| a.shelfList.report = shelves | |||
| } | |||
| func (a *Shelves) UpdateBuildingData() { | |||
| buildings, err := a.buildingListFunc() | |||
| if err != nil { | |||
| a.displayError("could not retrieve buildings", err) | |||
| return | |||
| } | |||
| a.buildingCache.mu.Lock() | |||
| a.buildingCache.report = buildings | |||
| a.buildingCache.mu.Unlock() | |||
| } | |||
| func (a *Shelves) getShelfData() []types.ShelfLocation { | |||
| a.shelfList.mu.Lock() | |||
| shelfReport := a.shelfList.report | |||
| defer a.shelfList.mu.Unlock() | |||
| return shelfReport | |||
| } | |||
| func (a *Shelves) getBuildingData() map[uint64]types.Building { | |||
| a.buildingCache.mu.Lock() | |||
| buildingReport := a.buildingCache.report | |||
| defer a.buildingCache.mu.Unlock() | |||
| return buildingReport | |||
| } | |||
| @ -0,0 +1,24 @@ | |||
| package shelves | |||
| import "github.com/gdamore/tcell/v2" | |||
| func (a *Shelves) Draw(screen tcell.Screen) { | |||
| a.refresh() | |||
| a.Box.DrawForSubclass(screen, a) | |||
| x, y, width, height := a.GetInnerRect() | |||
| a.shelfTable.SetRect(x, y, width, height) | |||
| a.shelfTable.Draw(screen) | |||
| x, y, width, height = a.shelfTable.GetInnerRect() | |||
| for _, diag := range a.allDialogs { | |||
| if diag.IsDisplay() { | |||
| diag.SetRect(x, y, width, height) | |||
| diag.Draw(screen) | |||
| return | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,66 @@ | |||
| package shelves | |||
| import ( | |||
| "git.brettb.xyz/goinv/client/internal/ui/utils" | |||
| "github.com/gdamore/tcell/v2" | |||
| "github.com/rivo/tview" | |||
| ) | |||
| func (a *Shelves) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) { | |||
| return a.WrapInputHandler(func(event *tcell.EventKey, setFocus func(tview.Primitive)) { | |||
| a.logger.Sugar().Debugf("assets event %v received", event) | |||
| if a.progressDialog.IsDisplay() { | |||
| setFocus(a.progressDialog) | |||
| return | |||
| } | |||
| if a.errorDialog.HasFocus() { | |||
| if errorHandler := a.errorDialog.InputHandler(); errorHandler != nil { | |||
| errorHandler(event, setFocus) | |||
| } | |||
| } | |||
| if a.messageDialog.HasFocus() { | |||
| if messageHandler := a.messageDialog.InputHandler(); messageHandler != nil { | |||
| messageHandler(event, setFocus) | |||
| } | |||
| } | |||
| if a.confirmDialog.HasFocus() { | |||
| if confirmHandler := a.confirmDialog.InputHandler(); confirmHandler != nil { | |||
| confirmHandler(event, setFocus) | |||
| } | |||
| } | |||
| if a.createDialog.HasFocus() { | |||
| if createHandler := a.createDialog.InputHandler(); createHandler != nil { | |||
| createHandler(event, setFocus) | |||
| } | |||
| } | |||
| if a.editDialog.HasFocus() { | |||
| if editHandler := a.editDialog.InputHandler(); editHandler != nil { | |||
| editHandler(event, setFocus) | |||
| } | |||
| } | |||
| if a.cmdDialog.HasFocus() { | |||
| if cmdHandler := a.cmdDialog.InputHandler(); cmdHandler != nil { | |||
| cmdHandler(event, setFocus) | |||
| } | |||
| } | |||
| if a.shelfTable.HasFocus() { | |||
| if event.Rune() == utils.CommandMenuKey.Rune() { | |||
| a.cmdDialog.Display() | |||
| } else { | |||
| if tableHandler := a.shelfTable.InputHandler(); tableHandler != nil { | |||
| tableHandler(event, setFocus) | |||
| } | |||
| } | |||
| } | |||
| setFocus(a) | |||
| }) | |||
| } | |||
| @ -0,0 +1,72 @@ | |||
| package shelves | |||
| import ( | |||
| "fmt" | |||
| "strings" | |||
| "git.brettb.xyz/goinv/client/internal/ui/style" | |||
| "github.com/rivo/tview" | |||
| ) | |||
| const tableHeaderOffset = 1 | |||
| func (a *Shelves) refresh() { | |||
| shelfs := a.getShelfData() | |||
| a.shelfTable.Clear() | |||
| a.updateShelfTableTitle(len(shelfs)) | |||
| a.writeTableHeaders() | |||
| for i, shelf := range shelfs { | |||
| row := i + tableHeaderOffset | |||
| // ID, Name, Building, Room | |||
| a.shelfTable.SetCell(row, 0, | |||
| tview.NewTableCell(fmt.Sprintf("%d", shelf.ID)). | |||
| SetExpansion(a.shelfTableExpansions[0]). | |||
| SetAlign(tview.AlignLeft)) | |||
| a.shelfTable.SetCell(row, 1, | |||
| tview.NewTableCell(shelf.Name). | |||
| SetExpansion(a.shelfTableExpansions[1]). | |||
| SetAlign(tview.AlignLeft)) | |||
| building := "" | |||
| if shelf.Building != nil { | |||
| building = shelf.Building.Name | |||
| } | |||
| a.shelfTable.SetCell(row, 2, | |||
| tview.NewTableCell(building). | |||
| SetExpansion(a.shelfTableExpansions[2]). | |||
| SetAlign(tview.AlignLeft)) | |||
| a.shelfTable.SetCell(row, 2, | |||
| tview.NewTableCell(shelf.RoomNumber). | |||
| SetExpansion(a.shelfTableExpansions[3]). | |||
| SetAlign(tview.AlignLeft)) | |||
| } | |||
| } | |||
| func (a *Shelves) updateShelfTableTitle(count int) { | |||
| dirtyFlag := "" | |||
| if a.shelfList.dirty { | |||
| dirtyFlag = "*" | |||
| } | |||
| title := fmt.Sprintf("[::b]SHELVES [%s%d]", dirtyFlag, count) | |||
| a.shelfTable.SetTitle(title) | |||
| } | |||
| func (a *Shelves) writeTableHeaders() { | |||
| for i, headerText := range a.shelfTableHeaders { | |||
| header := fmt.Sprintf("[::b]%s", strings.ToUpper(headerText)) | |||
| a.shelfTable.SetCell(0, i, | |||
| tview.NewTableCell(header). | |||
| SetExpansion(a.shelfTableExpansions[i]). | |||
| SetBackgroundColor(style.TableHeaderBgColor). | |||
| SetTextColor(style.TableHeaderFgColor). | |||
| SetAlign(tview.AlignLeft). | |||
| SetSelectable(false)) | |||
| } | |||
| } | |||
| @ -0,0 +1,218 @@ | |||
| package shelves | |||
| import ( | |||
| "git.brettb.xyz/goinv/client/internal/ui/shelves/shlvdialogs" | |||
| "sync" | |||
| "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 ( | |||
| status_CONFIRM_DELETE_SHELF = "delete_shelf" | |||
| ) | |||
| type Shelves struct { | |||
| *tview.Box | |||
| client *api.APIClient | |||
| title string | |||
| logger *zap.Logger | |||
| shelfTable *tview.Table | |||
| shelfList shelfListReport | |||
| buildingCache buildingListReport | |||
| shelfTableHeaders []string | |||
| shelfTableExpansions []int | |||
| cmdDialog *dialogs.CommandDialog | |||
| confirmDialog *dialogs.ConfirmDialog | |||
| errorDialog *dialogs.ErrorDialog | |||
| progressDialog *dialogs.ProgressDialog | |||
| messageDialog *dialogs.MessageDialog | |||
| createDialog *shlvdialogs.ShelfCreateDialog | |||
| editDialog *shlvdialogs.ShelfEditDialog | |||
| allDialogs []dialogs.Dialog | |||
| confirmData string | |||
| shelfListFunc func() ([]types.ShelfLocation, error) | |||
| buildingListFunc func() (map[uint64]types.Building, error) | |||
| } | |||
| type shelfSelectedItem struct { | |||
| id string | |||
| name string | |||
| building string | |||
| roomNumber string | |||
| } | |||
| type shelfListReport struct { | |||
| mu sync.Mutex | |||
| report []types.ShelfLocation | |||
| dirty bool | |||
| } | |||
| type buildingListReport struct { | |||
| mu sync.Mutex | |||
| report map[uint64]types.Building | |||
| dirty bool | |||
| } | |||
| func NewShelves(logger *zap.Logger, client *api.APIClient) *Shelves { | |||
| shelves := &Shelves{ | |||
| Box: tview.NewBox(), | |||
| client: client, | |||
| title: "shelves", | |||
| logger: logger, | |||
| shelfTable: tview.NewTable(), | |||
| shelfTableHeaders: []string{"id", "name", "building", "room number"}, | |||
| shelfTableExpansions: []int{1, 4, 2, 2}, | |||
| confirmDialog: dialogs.NewConfirmDialog(logger), | |||
| errorDialog: dialogs.NewErrorDialog(logger), | |||
| progressDialog: dialogs.NewProgressDialog(logger), | |||
| messageDialog: dialogs.NewMessageDialog(logger, ""), | |||
| createDialog: shlvdialogs.NewShelfCreateDialog(logger, client), | |||
| editDialog: shlvdialogs.NewShelfEditDialog(logger, client), | |||
| } | |||
| shelves.shelfTable.SetBackgroundColor(style.BgColor) | |||
| shelves.shelfTable.SetBorder(true) | |||
| shelves.updateShelfTableTitle(0) | |||
| shelves.shelfTable.SetTitleColor(style.FgColor) | |||
| shelves.shelfTable.SetBorderColor(style.BorderColor) | |||
| shelves.shelfTable.SetFixed(1, 1) | |||
| shelves.shelfTable.SetSelectable(true, false) | |||
| shelves.writeTableHeaders() | |||
| shelves.shelfTable.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { | |||
| if shelves.shelfTable.GetRowCount() <= 1 { | |||
| return nil | |||
| } | |||
| return event | |||
| }) | |||
| shelves.cmdDialog = dialogs.NewCommandDialog(logger, [][]string{ | |||
| {"create shelf", "create a new shelf"}, | |||
| {"edit shelf", "edit the selected shelf"}, | |||
| {"delete shelf", "delete the selected shelf"}, | |||
| {"refresh", "refresh the page"}, | |||
| }) | |||
| shelves.cmdDialog.SetSelectedFunc(func() { | |||
| shelves.cmdDialog.Hide() | |||
| shelves.runCommand(shelves.cmdDialog.GetSelectedItem()) | |||
| }).SetCancelFunc(func() { | |||
| shelves.cmdDialog.Hide() | |||
| }) | |||
| shelves.confirmDialog.SetSelectedFunc(func() { | |||
| shelves.confirmDialog.Hide() | |||
| switch shelves.confirmData { | |||
| case status_CONFIRM_DELETE_SHELF: | |||
| shelves.delete() | |||
| } | |||
| }).SetCancelFunc(func() { | |||
| shelves.confirmDialog.Hide() | |||
| }) | |||
| shelves.messageDialog.SetCancelFunc(func() { | |||
| shelves.messageDialog.Hide() | |||
| }) | |||
| shelves.createDialog.SetCancelFunc(func() { | |||
| shelves.createDialog.Hide() | |||
| }).SetCreateFunc(func() { | |||
| shelves.createDialog.Hide() | |||
| shelves.create() | |||
| }) | |||
| shelves.editDialog.SetCancelFunc(func() { | |||
| shelves.editDialog.Hide() | |||
| }).SetEditFunc(func() { | |||
| shelves.editDialog.Hide() | |||
| shelves.edit() | |||
| }) | |||
| shelves.SetBuildingListFunc(func() (map[uint64]types.Building, error) { | |||
| if resp, err := shelves.client.RetrieveAllBuildings(); err != nil { | |||
| return nil, err | |||
| } else { | |||
| buildings := map[uint64]types.Building{} | |||
| for _, a := range resp { | |||
| buildings[a.ID] = *a | |||
| } | |||
| return buildings, nil | |||
| } | |||
| }) | |||
| shelves.allDialogs = []dialogs.Dialog{ | |||
| shelves.errorDialog, | |||
| shelves.messageDialog, | |||
| shelves.progressDialog, | |||
| shelves.confirmDialog, | |||
| shelves.createDialog, | |||
| shelves.editDialog, | |||
| shelves.cmdDialog, | |||
| } | |||
| return shelves | |||
| } | |||
| func (a *Shelves) GetTitle() string { | |||
| return a.title | |||
| } | |||
| func (a *Shelves) HasFocus() bool { | |||
| return dialogs.CheckDialogFocus(a.allDialogs...) || utils.CheckFocus(a.shelfTable, a.Box) | |||
| } | |||
| func (a *Shelves) SubDialogHasFocus() bool { | |||
| return dialogs.CheckDialogFocus(a.allDialogs...) | |||
| } | |||
| func (a *Shelves) Focus(delegate func(tview.Primitive)) { | |||
| for _, dialog := range a.allDialogs { | |||
| if dialog.IsDisplay() { | |||
| delegate(dialog) | |||
| return | |||
| } | |||
| } | |||
| delegate(a.shelfTable) | |||
| } | |||
| func (a *Shelves) SetBuildingListFunc(list func() (map[uint64]types.Building, error)) { | |||
| a.buildingListFunc = list | |||
| } | |||
| func (a *Shelves) SetShelfListFunc(list func() ([]types.ShelfLocation, error)) { | |||
| a.shelfListFunc = list | |||
| } | |||
| func (a *Shelves) hideAllDialogs() { | |||
| for _, dialog := range a.allDialogs { | |||
| dialog.Hide() | |||
| } | |||
| } | |||
| func (a *Shelves) getSelectedItem() *shelfSelectedItem { | |||
| selectedItem := shelfSelectedItem{} | |||
| if a.shelfTable.GetRowCount() <= 1 { | |||
| return nil | |||
| } | |||
| row, _ := a.shelfTable.GetSelection() | |||
| selectedItem.id = a.shelfTable.GetCell(row, 0).Text | |||
| selectedItem.name = a.shelfTable.GetCell(row, 1).Text | |||
| selectedItem.building = a.shelfTable.GetCell(row, 2).Text | |||
| selectedItem.roomNumber = a.shelfTable.GetCell(row, 3).Text | |||
| return &selectedItem | |||
| } | |||
| @ -0,0 +1,605 @@ | |||
| package shlvdialogs | |||
| import ( | |||
| "fmt" | |||
| "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 ( | |||
| shelfCreateDialogMaxWidth = 100 | |||
| shelfCreateDialogHeight = 17 | |||
| ) | |||
| const ( | |||
| createShelfFormFocus = 0 + iota | |||
| createCategoriesFocus | |||
| createCategoryPagesFocus | |||
| createShelfNameFieldFocus | |||
| createShelfRoomFieldFocus | |||
| createShelfBuildingFieldFocus | |||
| createShelfDescriptionFieldFocus | |||
| ) | |||
| const ( | |||
| generalPageIndex = 0 + iota | |||
| locationPageIndex | |||
| commentPageIndex | |||
| ) | |||
| type ShelfCreateDialog struct { | |||
| *tview.Box | |||
| layout *tview.Flex | |||
| createCategoryLabels []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() | |||
| createHandler func() | |||
| } | |||
| func NewShelfCreateDialog(logger *zap.Logger, client *api.APIClient) *ShelfCreateDialog { | |||
| createDialog := ShelfCreateDialog{ | |||
| Box: tview.NewBox(), | |||
| layout: tview.NewFlex(), | |||
| createCategoryLabels: []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(), | |||
| } | |||
| createDialog.focusMap = map[int]tview.Primitive{ | |||
| createShelfNameFieldFocus: createDialog.shelfNameField, | |||
| createShelfRoomFieldFocus: createDialog.shelfRoomField, | |||
| createShelfBuildingFieldFocus: createDialog.shelfBuildingField, | |||
| createShelfDescriptionFieldFocus: createDialog.shelfDescriptionArea, | |||
| } | |||
| createDialog.setupLayout() | |||
| createDialog.setActiveCategory(0) | |||
| createDialog.initCustomInputHandlers() | |||
| return &createDialog | |||
| } | |||
| func (d *ShelfCreateDialog) Display() { | |||
| d.display = true | |||
| d.initData() | |||
| d.focusElement = createCategoryPagesFocus | |||
| } | |||
| func (d *ShelfCreateDialog) IsDisplay() bool { | |||
| return d.display | |||
| } | |||
| func (d *ShelfCreateDialog) Hide() { | |||
| d.display = false | |||
| } | |||
| func (d *ShelfCreateDialog) HasFocus() bool { | |||
| return utils.CheckFocus(d.categories, d.categoryPages, d.Box, d.form) | |||
| } | |||
| func (d *ShelfCreateDialog) Focus(delegate func(tview.Primitive)) { | |||
| switch d.focusElement { | |||
| case createShelfFormFocus: | |||
| button := d.form.GetButton(d.form.GetButtonCount() - 1) | |||
| button.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { | |||
| if event.Key() == utils.SwitchFocusKey.EventKey() { | |||
| d.focusElement = createCategoriesFocus | |||
| d.Focus(delegate) | |||
| d.form.SetFocus(0) | |||
| return nil | |||
| } | |||
| if event.Key() == tcell.KeyEnter { | |||
| return nil | |||
| } | |||
| return event | |||
| }) | |||
| delegate(d.form) | |||
| case createCategoriesFocus: | |||
| d.categories.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { | |||
| if event.Key() == utils.SwitchFocusKey.EventKey() { | |||
| d.focusElement = createCategoryPagesFocus | |||
| d.Focus(delegate) | |||
| return nil | |||
| } | |||
| if event.Key() == tcell.KeyBacktab { | |||
| d.focusElement = createShelfFormFocus | |||
| 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 createCategoryPagesFocus: | |||
| delegate(d.categoryPages) | |||
| default: | |||
| delegate(d.focusMap[d.focusElement]) | |||
| } | |||
| } | |||
| func (d *ShelfCreateDialog) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) { | |||
| return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(tview.Primitive)) { | |||
| d.logger.Sugar().Debugf("shelf create 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.createHandler() | |||
| } | |||
| } | |||
| formHandler(event, setFocus) | |||
| return | |||
| } | |||
| } | |||
| }) | |||
| } | |||
| func (d *ShelfCreateDialog) SetRect(x, y, width, height int) { | |||
| if width > shelfCreateDialogMaxWidth { | |||
| emptySpace := (width - shelfCreateDialogMaxWidth) / 2 | |||
| x += emptySpace | |||
| width = shelfCreateDialogMaxWidth | |||
| } | |||
| if height > shelfCreateDialogHeight { | |||
| emptySpace := (height - shelfCreateDialogHeight) / 2 | |||
| y += emptySpace | |||
| height = shelfCreateDialogHeight | |||
| } | |||
| d.Box.SetRect(x, y, width, height) | |||
| } | |||
| func (d *ShelfCreateDialog) 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 *ShelfCreateDialog) SetCreateFunc(f func()) *ShelfCreateDialog { | |||
| d.createHandler = f | |||
| enterButton := d.form.GetButton(d.form.GetButtonCount() - 1) | |||
| enterButton.SetSelectedFunc(f) | |||
| return d | |||
| } | |||
| func (d *ShelfCreateDialog) SetCancelFunc(f func()) *ShelfCreateDialog { | |||
| d.cancelHandler = f | |||
| cancelButton := d.form.GetButton(d.form.GetButtonCount() - 2) | |||
| cancelButton.SetSelectedFunc(f) | |||
| return d | |||
| } | |||
| func (d *ShelfCreateDialog) dropdownHasFocus() bool { | |||
| return utils.CheckFocus(d.shelfBuildingField) | |||
| } | |||
| func (d *ShelfCreateDialog) SetShelf(a *types.ShelfLocation) { | |||
| d.shelf = a | |||
| } | |||
| func (d *ShelfCreateDialog) 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 (d *ShelfCreateDialog) 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("Create", nil) | |||
| d.form.SetButtonsAlign(tview.AlignRight) | |||
| d.form.SetButtonBackgroundColor(style.ButtonBgColor) | |||
| d.form.SetButtonTextColor(style.ButtonFgColor) | |||
| d.form.SetButtonActivatedStyle(activatedStyle) | |||
| d.categoryPages.AddPage(d.createCategoryLabels[generalPageIndex], d.generalInfoPage, true, true) | |||
| d.categoryPages.AddPage(d.createCategoryLabels[locationPageIndex], d.locationPage, true, true) | |||
| d.categoryPages.AddPage(d.createCategoryLabels[commentPageIndex], d.commentsPage, true, true) | |||
| d.layout.SetBackgroundColor(bgColor) | |||
| d.layout.SetBorder(true) | |||
| d.layout.SetBorderColor(style.DialogBorderColor) | |||
| d.layout.SetTitle("SHELF CREATE") | |||
| d.layout.SetTitleColor(style.DialogFgColor) | |||
| _, layoutWidth := utils.AlignStringListWidth(d.createCategoryLabels) | |||
| 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 *ShelfCreateDialog) 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 *ShelfCreateDialog) 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 *ShelfCreateDialog) 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 *ShelfCreateDialog) 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.createCategoryLabels) | |||
| 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.createCategoryLabels[idx]) | |||
| } | |||
| func (d *ShelfCreateDialog) nextCategory() { | |||
| activePage := d.activePageIndex | |||
| if d.activePageIndex < len(d.createCategoryLabels)-1 { | |||
| activePage++ | |||
| d.setActiveCategory(activePage) | |||
| return | |||
| } | |||
| d.setActiveCategory(0) | |||
| } | |||
| func (d *ShelfCreateDialog) previousCategory() { | |||
| activePage := d.activePageIndex | |||
| if d.activePageIndex > 0 { | |||
| activePage-- | |||
| d.setActiveCategory(activePage) | |||
| return | |||
| } | |||
| d.setActiveCategory(len(d.createCategoryLabels) - 1) | |||
| } | |||
| func (d *ShelfCreateDialog) initCustomInputHandlers() { | |||
| d.shelfBuildingField.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { | |||
| event = utils.ParseKeyEventKey(d.logger, event) | |||
| return event | |||
| }) | |||
| } | |||
| func (d *ShelfCreateDialog) setGeneralInfoNextFocus() { | |||
| // Name -> Quantity -> Length -> Manufacturer -> Model -> Price | |||
| if d.shelfNameField.HasFocus() { | |||
| d.focusElement = createShelfFormFocus | |||
| return | |||
| } | |||
| d.focusElement = createShelfFormFocus | |||
| } | |||
| func (d *ShelfCreateDialog) setGeneralInfoPrevFocus() { | |||
| if d.shelfNameField.HasFocus() { | |||
| d.focusElement = createCategoriesFocus | |||
| return | |||
| } | |||
| d.focusElement = createShelfNameFieldFocus | |||
| } | |||
| func (d *ShelfCreateDialog) setLocationNextFocus() { | |||
| // Category -> Shelf | |||
| if d.shelfRoomField.HasFocus() { | |||
| d.focusElement = createShelfBuildingFieldFocus | |||
| return | |||
| } | |||
| d.focusElement = createShelfFormFocus | |||
| } | |||
| func (d *ShelfCreateDialog) setLocationPrevFocus() { | |||
| if d.shelfRoomField.HasFocus() { | |||
| d.focusElement = createCategoriesFocus | |||
| return | |||
| } | |||
| if d.shelfBuildingField.HasFocus() { | |||
| d.focusElement = createShelfRoomFieldFocus | |||
| return | |||
| } | |||
| d.focusElement = createShelfBuildingFieldFocus | |||
| } | |||
| func (d *ShelfCreateDialog) setCommentsNextFocus() { | |||
| d.focusElement = createShelfFormFocus | |||
| } | |||
| func (d *ShelfCreateDialog) setCommentsPrevFocus() { | |||
| d.focusElement = createCategoriesFocus | |||
| } | |||
| func (d *ShelfCreateDialog) CreateShelfOptions() 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 | |||
| } | |||
| @ -0,0 +1,604 @@ | |||
| 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 | |||
| } | |||