| @ -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 | |||||
| } | |||||