From 639788d37b91b293aee55bd7d12752dda7dfc3d7 Mon Sep 17 00:00:00 2001 From: Brett Bender Date: Sat, 20 Jan 2024 21:58:49 -0600 Subject: [PATCH] feat: create dialog --- cmd/client/client.go | 6 +- internal/api/api.go | 76 +-- internal/types/api.go | 23 + internal/types/assets.go | 2 + internal/types/categories.go | 11 + internal/types/shelves.go | 2 + internal/ui/assets/assets.go | 8 + internal/ui/assets/astdialogs/create.go | 614 ++++++++++++++++++++++++ internal/ui/assets/command.go | 6 +- internal/ui/style/style.go | 3 + internal/ui/utils/utils.go | 26 + 11 files changed, 715 insertions(+), 62 deletions(-) create mode 100644 internal/types/api.go create mode 100644 internal/ui/assets/astdialogs/create.go diff --git a/cmd/client/client.go b/cmd/client/client.go index e49f5fb..953371e 100644 --- a/cmd/client/client.go +++ b/cmd/client/client.go @@ -14,9 +14,11 @@ import ( var envPrefix = "GOINV_CLI_" func main() { - logger, _ := zap.NewProduction() + logger, err := zap.NewProduction() defer logger.Sync() + logger.Info("loading configuration") + config.LoadConfig(file.Provider("config.json"), json.Parser()) config.LoadConfig(env.Provider(envPrefix, ".", func(s string) string { return strings.Replace(strings.ToLower( @@ -26,7 +28,7 @@ func main() { var cfg config.Config - err := config.Unmarshal(&cfg) + err = config.Unmarshal(&cfg) if err != nil { logger.Panic("could not load config", zap.Error(err)) } diff --git a/internal/api/api.go b/internal/api/api.go index ab69223..235fda3 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -23,7 +23,7 @@ func NewAPIClient(host string) *APIClient { } } -func (c *APIClient) retrieveSingleAsset(r *http.Request) (*types.AssetResponse, error) { +func makeRequest[T any](c *APIClient, r *http.Request) (*T, error) { r.Header.Set("User-Agent", userAgent) //r.Header.Set("Authorization", "Bearer "+c.Token) r.Header.Set("Accept", "application/json") @@ -41,57 +41,7 @@ func (c *APIClient) retrieveSingleAsset(r *http.Request) (*types.AssetResponse, return nil, err } - resp, err := unmarshal[types.AssetResponse](body) - if err != nil { - return nil, fmt.Errorf("%s", "An unexpected response was received") - } - return resp, nil -} - -func (c *APIClient) retrieveMultipleAssets(r *http.Request) (*types.MultipleAssetsResponse, error) { - r.Header.Set("User-Agent", userAgent) - //r.Header.Set("Authorization", "Bearer "+c.Token) - r.Header.Set("Accept", "application/json") - r.Header.Set("Content-Type", "application/json") - - httpCli := http.Client{} - httpResp, err := httpCli.Do(r) - if err != nil { - return nil, err - } - - defer httpResp.Body.Close() - body, err := io.ReadAll(httpResp.Body) - if err != nil { - return nil, err - } - - resp, err := unmarshal[types.MultipleAssetsResponse](body) - if err != nil { - return nil, fmt.Errorf("%s", "An unexpected response was received") - } - return resp, nil -} - -func (c *APIClient) retrieveMultipleShelves(r *http.Request) (*types.MultipleShelfResponse, error) { - r.Header.Set("User-Agent", userAgent) - //r.Header.Set("Authorization", "Bearer "+c.Token) - r.Header.Set("Accept", "application/json") - r.Header.Set("Content-Type", "application/json") - - httpCli := http.Client{} - httpResp, err := httpCli.Do(r) - if err != nil { - return nil, err - } - - defer httpResp.Body.Close() - body, err := io.ReadAll(httpResp.Body) - if err != nil { - return nil, err - } - - resp, err := unmarshal[types.MultipleShelfResponse](body) + resp, err := unmarshal[T](body) if err != nil { return nil, fmt.Errorf("%s", "An unexpected response was received") } @@ -102,7 +52,7 @@ func (c *APIClient) RetrieveAssetByID(idNumber string) (*types.Asset, error) { url := fmt.Sprintf("%s/assets/%s", c.Host, idNumber) req, _ := http.NewRequest("GET", url, bytes.NewBuffer([]byte{})) - resp, err := c.retrieveSingleAsset(req) + resp, err := makeRequest[types.AssetResponse](c, req) if err != nil { return nil, err } @@ -120,7 +70,7 @@ func (c *APIClient) CreateAsset(request types.CreateAssetRequest) (*types.Asset, req, _ := http.NewRequest("POST", url, bytes.NewBuffer(body)) - resp, err := c.retrieveSingleAsset(req) + resp, err := makeRequest[types.AssetResponse](c, req) if err != nil { return nil, err } @@ -132,7 +82,7 @@ func (c *APIClient) RetrieveAllAssets() ([]*types.Asset, error) { url := fmt.Sprintf("%s/assets", c.Host) req, _ := http.NewRequest("GET", url, bytes.NewBuffer([]byte{})) - resp, err := c.retrieveMultipleAssets(req) + resp, err := makeRequest[types.MultipleAssetsResponse](c, req) if err != nil { return nil, err } @@ -144,7 +94,7 @@ func (c *APIClient) DeleteAssetByID(idNumber string) (*types.Asset, error) { url := fmt.Sprintf("%s/assets/%s", c.Host, idNumber) req, _ := http.NewRequest("DELETE", url, bytes.NewBuffer([]byte{})) - resp, err := c.retrieveSingleAsset(req) + resp, err := makeRequest[types.AssetResponse](c, req) if err != nil { return nil, err } @@ -156,10 +106,22 @@ func (c *APIClient) RetrieveAllShelves() ([]*types.ShelfLocation, error) { url := fmt.Sprintf("%s/shelves", c.Host) req, _ := http.NewRequest("GET", url, bytes.NewBuffer([]byte{})) - resp, err := c.retrieveMultipleShelves(req) + resp, err := makeRequest[types.MultipleShelfResponse](c, req) if err != nil { return nil, err } return resp.ShelfLocations, nil } + +func (c *APIClient) RetrieveAllCategories() ([]*types.Category, error) { + url := fmt.Sprintf("%s/categories", c.Host) + req, _ := http.NewRequest("GET", url, bytes.NewBuffer([]byte{})) + + resp, err := makeRequest[types.MultipleCategoryResponse](c, req) + if err != nil { + return nil, err + } + + return resp.Categories, nil +} diff --git a/internal/types/api.go b/internal/types/api.go new file mode 100644 index 0000000..0f77121 --- /dev/null +++ b/internal/types/api.go @@ -0,0 +1,23 @@ +package types + +type Response struct { + HTTPStatusCode int `json:"status"` +} + +type APIVersion struct { + Major int `json:"major"` + Minor int `json:"minor"` + Patch int `json:"patch"` +} + +type IndexResponse struct { + *Response + Version APIVersion `json:"version"` +} + +type APIError struct { + *Response + Err error `json:"-"` + + Messages []string `json:"messages"` +} diff --git a/internal/types/assets.go b/internal/types/assets.go index c813dc6..ebec4ae 100644 --- a/internal/types/assets.go +++ b/internal/types/assets.go @@ -5,10 +5,12 @@ import ( ) type AssetResponse struct { + *Response Asset *Asset `json:"asset"` } type MultipleAssetsResponse struct { + *Response Assets []*Asset `json:"assets"` Total int `json:"total"` } diff --git a/internal/types/categories.go b/internal/types/categories.go index 1e908f0..9655574 100644 --- a/internal/types/categories.go +++ b/internal/types/categories.go @@ -9,3 +9,14 @@ type Category struct { UpdatedAt time.Time `json:"updated_at"` DeletedAt *time.Time `json:"deleted_at,omitempty"` } + +type CategoryResponse struct { + *Response + Category *Category `json:"category"` +} + +type MultipleCategoryResponse struct { + *Response + Categories []*Category `json:"categories"` + Total int64 `json:"total"` +} diff --git a/internal/types/shelves.go b/internal/types/shelves.go index c8f3df5..4a70a64 100644 --- a/internal/types/shelves.go +++ b/internal/types/shelves.go @@ -3,10 +3,12 @@ package types import "time" type ShelfResponse struct { + *Response ShelfLocation *ShelfLocation `json:"shelf"` } type MultipleShelfResponse struct { + *Response ShelfLocations []*ShelfLocation `json:"shelves"` Total int `json:"total"` } diff --git a/internal/ui/assets/assets.go b/internal/ui/assets/assets.go index 83438e2..178bdaa 100644 --- a/internal/ui/assets/assets.go +++ b/internal/ui/assets/assets.go @@ -1,6 +1,7 @@ package assets import ( + "git.brettb.xyz/goinv/client/internal/ui/assets/astdialogs" "sync" "git.brettb.xyz/goinv/client/internal/api" @@ -32,6 +33,7 @@ type Assets struct { errorDialog *dialogs.ErrorDialog progressDialog *dialogs.ProgressDialog messageDialog *dialogs.MessageDialog + createDialog *astdialogs.AssetCreateDialog allDialogs []dialogs.Dialog confirmData string assetListFunc func() ([]types.Asset, error) @@ -119,6 +121,12 @@ func NewAssets(logger *zap.Logger, client *api.APIClient) *Assets { assets.messageDialog.Hide() }) + assets.createDialog.SetCancelFunc(func() { + assets.createDialog.Hide() + }).SetCreateFunc(func() { + assets.createDialog.Hide() + }) + assets.SetAssetListFunc(func() ([]types.Asset, error) { if asp, err := assets.client.RetrieveAllAssets(); err != nil { return nil, err diff --git a/internal/ui/assets/astdialogs/create.go b/internal/ui/assets/astdialogs/create.go new file mode 100644 index 0000000..a1ad6e0 --- /dev/null +++ b/internal/ui/assets/astdialogs/create.go @@ -0,0 +1,614 @@ +package astdialogs + +import ( + "fmt" + "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" + "strings" +) + +const ( + assetCreateDialogMaxWidth = 100 + assetCreateDialogHeight = 10 +) + +const ( + createAssetFormFocus = 0 + iota + createCategoriesFocus + createCategoryPagesFocus + createAssetNameFieldFocus + createAssetQuantityFieldFocus + createAssetLengthFieldFocus + createAssetManufacturerFieldFocus + createAssetModelFieldFocus + createAssetPriceFieldFocus + createAssetCategoryFieldFocus + createAssetShelfFieldFocus + createAssetCommentFieldFocus +) + +const ( + generalPageIndex = 0 + iota + locationPageIndex + commentPageIndex +) + +type AssetCreateDialog 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 + + categoryList []*types.Category + shelfList []*types.ShelfLocation + + assetNameField *tview.InputField + assetQuantityField *tview.InputField + assetLengthField *tview.InputField + assetManufacturerField *tview.InputField + assetModelField *tview.InputField + assetPriceField *tview.InputField + assetCategoryField *tview.DropDown + assetShelfField *tview.DropDown + assetCommentsArea *tview.TextArea + + focusMap map[int]tview.Primitive + + cancelHandler func() + createHandler func() +} + +func NewAssetCreateDialog(logger *zap.Logger, client *api.APIClient) *AssetCreateDialog { + createDialog := AssetCreateDialog{ + 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, + assetNameField: tview.NewInputField(), + assetQuantityField: tview.NewInputField(), + assetLengthField: tview.NewInputField(), + assetManufacturerField: tview.NewInputField(), + assetModelField: tview.NewInputField(), + assetPriceField: tview.NewInputField(), + assetCategoryField: tview.NewDropDown(), + assetShelfField: tview.NewDropDown(), + assetCommentsArea: tview.NewTextArea(), + } + + createDialog.focusMap = map[int]tview.Primitive{ + createAssetNameFieldFocus: createDialog.assetNameField, + createAssetQuantityFieldFocus: createDialog.assetQuantityField, + createAssetLengthFieldFocus: createDialog.assetLengthField, + createAssetManufacturerFieldFocus: createDialog.assetManufacturerField, + createAssetModelFieldFocus: createDialog.assetModelField, + createAssetPriceFieldFocus: createDialog.assetPriceField, + createAssetCategoryFieldFocus: createDialog.assetCategoryField, + createAssetShelfFieldFocus: createDialog.assetShelfField, + createAssetCommentFieldFocus: createDialog.assetCommentsArea, + } + + createDialog.setupLayout() + createDialog.setActiveCategory(0) + createDialog.initCustomInputHandlers() + + return &createDialog +} + +func (d *AssetCreateDialog) Display() { + d.display = true + d.initData() + d.focusElement = createCategoryPagesFocus +} + +func (d *AssetCreateDialog) IsDisplay() bool { + return d.display +} + +func (d *AssetCreateDialog) Hide() { + d.display = false +} + +func (d *AssetCreateDialog) HasFocus() bool { + return utils.CheckFocus(d.categories, d.categoryPages, d.Box, d.form) +} + +func (d *AssetCreateDialog) Focus(delegate func(tview.Primitive)) { + switch d.focusElement { + case createAssetFormFocus: + 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 + } + + 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 *AssetCreateDialog) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) { + return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(tview.Primitive)) { + d.logger.Sugar().Debugf("asset 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() + } + + handler(event, setFocus) + + return + } + } + + if d.locationPage.HasFocus() { + if handler := d.locationPage.InputHandler(); handler != nil { + if event.Key() == utils.SwitchFocusKey.EventKey() { + d.setLocationNextFocus() + } + + handler(event, setFocus) + + return + } + } + + if d.commentsPage.HasFocus() { + if handler := d.commentsPage.InputHandler(); handler != nil { + if event.Key() == utils.SwitchFocusKey.EventKey() { + d.setCommentsNextFocus() + } + + 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 *AssetCreateDialog) SetRect(x, y, width, height int) { + if width > assetCreateDialogMaxWidth { + emptySpace := (width - assetCreateDialogMaxWidth) / 2 + x += emptySpace + width = assetCreateDialogMaxWidth + } + + if height > assetCreateDialogHeight { + emptySpace := (height - assetCreateDialogHeight) / 2 + y += emptySpace + height = assetCreateDialogHeight + } + + d.Box.SetRect(x, y, width, height) +} + +func (d *AssetCreateDialog) 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 *AssetCreateDialog) SetCreateFunc(f func()) *AssetCreateDialog { + d.createHandler = f + enterButton := d.form.GetButton(d.form.GetButtonCount() - 1) + + enterButton.SetSelectedFunc(f) + + return d +} + +func (d *AssetCreateDialog) SetCancelFunc(f func()) *AssetCreateDialog { + d.cancelHandler = f + return d +} + +func (d *AssetCreateDialog) dropdownHasFocus() bool { + return utils.CheckFocus(d.assetCategoryField, d.assetShelfField) +} + +func (d *AssetCreateDialog) initData() { + + // Get available shelves + shelfList, _ := d.client.RetrieveAllShelves() + d.shelfList = shelfList + + shelfOptions := []string{""} + for _, shelf := range d.shelfList { + shelfOptions = append(shelfOptions, fmt.Sprintf("%s", shelf.Name)) // In case we want to do custom formatting later + } + + // Get available categories + categoryList, _ := d.client.RetrieveAllCategories() + d.categoryList = categoryList + + categoryOptions := []string{""} + for _, category := range d.categoryList { + categoryOptions = append(shelfOptions, fmt.Sprintf("%s", category.Name)) + } + + // General category + d.assetNameField.SetText("") + d.assetQuantityField.SetText("") + d.assetLengthField.SetText("") + d.assetManufacturerField.SetText("") + d.assetModelField.SetText("") + d.assetPriceField.SetText("") + + // Location category + d.assetShelfField.SetOptions(shelfOptions, nil) + d.assetShelfField.SetCurrentOption(0) + d.assetCategoryField.SetOptions(categoryOptions, nil) + d.assetCategoryField.SetCurrentOption(0) + + // Comments category + d.assetCommentsArea.SetText("", true) +} + +func (d *AssetCreateDialog) setupLayout() { + bgColor := style.DialogBgColor + + d.categories.SetDynamicColors(true). + SetWrap(true). + SetTextAlign(tview.AlignLeft) + d.categories.SetBackgroundColor(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("ASSET CREATE") + + _, 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.AddItem(layout, 0, 1, true) + + d.layout.AddItem(d.form, dialogs.DialogFormHeight, 0, true) +} + +func (d *AssetCreateDialog) setupGeneralInfoPageUI() { + bgColor := style.DialogBgColor + inputFieldBgColor := style.InputFieldBgColor + pageLabelWidth := 12 + + d.assetNameField.SetLabel("name:") + d.assetNameField.SetLabelWidth(pageLabelWidth) + d.assetNameField.SetLabelColor(bgColor) + d.assetNameField.SetBackgroundColor(style.DialogFgColor) + d.assetNameField.SetFieldBackgroundColor(inputFieldBgColor) + + d.assetQuantityField.SetLabel("quantity:") + d.assetQuantityField.SetLabelWidth(pageLabelWidth) + d.assetQuantityField.SetLabelColor(bgColor) + d.assetQuantityField.SetBackgroundColor(style.DialogFgColor) + d.assetQuantityField.SetFieldBackgroundColor(inputFieldBgColor) + + d.assetLengthField.SetLabel("length:") + d.assetLengthField.SetLabelWidth(pageLabelWidth) + d.assetLengthField.SetLabelColor(bgColor) + d.assetLengthField.SetBackgroundColor(style.DialogFgColor) + d.assetLengthField.SetFieldBackgroundColor(inputFieldBgColor) + + d.assetManufacturerField.SetLabel("manufacturer:") + d.assetManufacturerField.SetLabelWidth(pageLabelWidth) + d.assetManufacturerField.SetLabelColor(bgColor) + d.assetManufacturerField.SetBackgroundColor(style.DialogFgColor) + d.assetManufacturerField.SetFieldBackgroundColor(inputFieldBgColor) + + d.assetModelField.SetLabel("model:") + d.assetModelField.SetLabelWidth(pageLabelWidth) + d.assetModelField.SetLabelColor(bgColor) + d.assetModelField.SetBackgroundColor(style.DialogFgColor) + d.assetModelField.SetFieldBackgroundColor(inputFieldBgColor) + + d.assetPriceField.SetLabel("price:") + d.assetPriceField.SetLabelWidth(pageLabelWidth) + d.assetPriceField.SetLabelColor(bgColor) + d.assetPriceField.SetBackgroundColor(style.DialogFgColor) + d.assetPriceField.SetFieldBackgroundColor(inputFieldBgColor) + + d.generalInfoPage.SetDirection(tview.FlexRow) + d.generalInfoPage.AddItem(d.assetNameField, 1, 0, true) + d.generalInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + d.generalInfoPage.AddItem(d.assetQuantityField, 1, 0, true) + d.generalInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + d.generalInfoPage.AddItem(d.assetLengthField, 1, 0, true) + d.generalInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + d.generalInfoPage.AddItem(d.assetManufacturerField, 1, 0, true) + d.generalInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + d.generalInfoPage.AddItem(d.assetModelField, 1, 0, true) + d.generalInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + d.generalInfoPage.AddItem(d.assetPriceField, 1, 0, true) + d.generalInfoPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) +} + +func (d *AssetCreateDialog) setupLocationPageUI() { + bgColor := style.DialogBgColor + inputFieldBgColor := style.InputFieldBgColor + pageLabelWidth := 12 + ddUnselectedStyle := style.DropdownUnselected + ddSelectedStyle := style.DropdownSelected + + d.assetCategoryField.SetLabel("category:") + d.assetCategoryField.SetLabelWidth(pageLabelWidth) + d.assetCategoryField.SetBackgroundColor(bgColor) + d.assetCategoryField.SetLabelColor(style.DialogFgColor) + d.assetCategoryField.SetListStyles(ddUnselectedStyle, ddSelectedStyle) + d.assetCategoryField.SetFieldBackgroundColor(inputFieldBgColor) + + d.assetShelfField.SetLabel("shelf:") + d.assetShelfField.SetLabelWidth(pageLabelWidth) + d.assetShelfField.SetBackgroundColor(bgColor) + d.assetShelfField.SetLabelColor(style.DialogFgColor) + d.assetShelfField.SetListStyles(ddUnselectedStyle, ddSelectedStyle) + d.assetShelfField.SetFieldBackgroundColor(inputFieldBgColor) + + d.locationPage.SetDirection(tview.FlexRow) + d.locationPage.AddItem(d.assetCategoryField, 1, 0, true) + d.locationPage.AddItem(utils.EmptyBoxSpace(bgColor), 1, 0, true) + d.locationPage.AddItem(d.assetShelfField, 1, 0, true) + d.locationPage.SetBackgroundColor(bgColor) +} + +func (d *AssetCreateDialog) setupCommentsPageUI() { + bgColor := style.DialogBgColor + inputFieldBgColor := style.InputFieldBgColor + pageLabelWidth := 12 + + d.assetCommentsArea.SetLabel("comments:") + d.assetCommentsArea.SetLabelWidth(pageLabelWidth) + d.assetCommentsArea.SetBackgroundColor(inputFieldBgColor) + d.assetCommentsArea.SetLabelStyle(tcell.StyleDefault.Foreground(style.DialogFgColor).Background(bgColor)) + d.assetCommentsArea.SetWrap(true) + + d.commentsPage.AddItem(d.assetCommentsArea, 0, 1, true) + d.commentsPage.SetBackgroundColor(bgColor) +} + +func (d *AssetCreateDialog) setActiveCategory(idx int) { + fgColor := style.DialogFgColor + bgBolor := style.ButtonBgColor + 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 *AssetCreateDialog) nextCategory() { + activePage := d.activePageIndex + if d.activePageIndex < len(d.createCategoryLabels)-1 { + activePage++ + + d.setActiveCategory(activePage) + + return + } + + d.setActiveCategory(0) +} + +func (d *AssetCreateDialog) previousCategory() { + activePage := d.activePageIndex + if d.activePageIndex > 0 { + activePage-- + + d.setActiveCategory(activePage) + + return + } + + d.setActiveCategory(len(d.createCategoryLabels) - 1) +} + +func (d *AssetCreateDialog) initCustomInputHandlers() { + d.assetShelfField.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + event = utils.ParseKeyEventKey(d.logger, event) + + return event + }) + + d.assetCategoryField.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + event = utils.ParseKeyEventKey(d.logger, event) + + return event + }) +} + +func (d *AssetCreateDialog) setGeneralInfoNextFocus() { + // Name -> Quantity -> Length -> Manufacturer -> Model -> Price + if d.assetNameField.HasFocus() { + d.focusElement = createAssetQuantityFieldFocus + + return + } + + if d.assetQuantityField.HasFocus() { + d.focusElement = createAssetLengthFieldFocus + + return + } + + if d.assetLengthField.HasFocus() { + d.focusElement = createAssetManufacturerFieldFocus + + return + } + + if d.assetManufacturerField.HasFocus() { + d.focusElement = createAssetModelFieldFocus + + return + } + + if d.assetModelField.HasFocus() { + d.focusElement = createAssetPriceFieldFocus + + return + } + + d.focusElement = createAssetFormFocus +} + +func (d *AssetCreateDialog) setLocationNextFocus() { + // Category -> Shelf + if d.assetCategoryField.HasFocus() { + d.focusElement = createAssetShelfFieldFocus + + return + } + + d.focusElement = createAssetFormFocus +} + +func (d *AssetCreateDialog) setCommentsNextFocus() { + d.focusElement = createAssetFormFocus +} \ No newline at end of file diff --git a/internal/ui/assets/command.go b/internal/ui/assets/command.go index 022df68..8e52913 100644 --- a/internal/ui/assets/command.go +++ b/internal/ui/assets/command.go @@ -9,12 +9,12 @@ import ( func (a *Assets) runCommand(cmd string) { switch cmd { - case "create asset", "view asset": + case "create asset": + a.createDialog.Display() + case "view asset": a.cNotImplemented() - return case "delete asset": a.cdelete() - return case "refresh": a.crefresh() } diff --git a/internal/ui/style/style.go b/internal/ui/style/style.go index 3636ed0..c078540 100644 --- a/internal/ui/style/style.go +++ b/internal/ui/style/style.go @@ -41,4 +41,7 @@ var ( ButtonFgColor = tcell.ColorLightBlue ButtonSelectedFgColor = tcell.ColorBlack ButtonSelectedBgColor = tcell.ColorWhite + InputFieldBgColor = tcell.ColorGray + DropdownUnselected = tcell.StyleDefault.Background(tcell.ColorWhiteSmoke).Foreground(tcell.ColorBlack) + DropdownSelected = tcell.StyleDefault.Background(tcell.ColorLightSlateGray).Foreground(tcell.ColorWhite) ) diff --git a/internal/ui/utils/utils.go b/internal/ui/utils/utils.go index eb02411..b52c02a 100644 --- a/internal/ui/utils/utils.go +++ b/internal/ui/utils/utils.go @@ -27,3 +27,29 @@ func CheckFocus(prims ...tview.Primitive) bool { } return false } + +func AlignStringListWidth(list []string) ([]string, int) { + var ( + m = 0 + alignedList = make([]string, 0) + ) + + for _, item := range list { + if len(item) > m { + m = len(item) + } + } + + for _, item := range list { + if len(item) < m { + need := m - len(item) + for i := 0; i < need; i++ { + item += " " + } + } + + alignedList = append(alignedList, item) + } + + return alignedList, m +}