package ui import ( "fmt" "strconv" "codeflow.dananglin.me.uk/apollo/canal/internal/board" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" ) const ( shiftLeft int = iota shiftRight ) const ( mainPageName string = "main" quitPageName string = "quit" addPageName string = "add" movePageName string = "move" ) type UI struct { *tview.Application columns []column flex *tview.Flex pages *tview.Pages focusedColumn int board board.Board quit *tview.Modal add *modalInput move *tview.Flex } // NewUI returns a new UI value. func NewUI() UI { ui := UI{ Application: tview.NewApplication(), pages: tview.NewPages(), flex: tview.NewFlex(), quit: tview.NewModal(), add: NewModalInput(), focusedColumn: 0, columns: nil, move: nil, board: board.Board{}, } ui.init() return ui } // closeBoard closes the board. func (u *UI) closeBoard() { _ = u.board.Close() } // init initialises the UI. func (u *UI) init() { u.pages.AddPage(mainPageName, u.flex, true, true) u.initQuitModal() u.pages.AddPage(quitPageName, u.quit, false, false) u.initAddInputModal() u.pages.AddPage(addPageName, u.add, false, false) u.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { switch event.Rune() { case 'o': if u.flex.HasFocus() && len(u.columns) == 0 { _ = u.openBoard("") } } return event }) u.SetRoot(u.pages, true) } // initAddInputModal initialises the add input modal. func (u *UI) initAddInputModal() { doneFunc := func(text string, success bool) { if success { _ = u.newCard(text, "") } u.pages.HidePage(addPageName) u.setColumnFocus() } u.add.SetDoneFunc(doneFunc) } // initQuitModal initialises the quit modal. func (u *UI) initQuitModal() { quitDoneFunc := func(_ int, buttonLabel string) { switch buttonLabel { case "Quit": u.shutdown() default: u.pages.SwitchToPage(mainPageName) u.setColumnFocus() } } u.quit.SetText("Do you want to quit the application?"). AddButtons([]string{"Quit", "Cancel"}). SetDoneFunc(quitDoneFunc) } // newCard creates and saves a new card to the database. func (u *UI) newCard(title, content string) error { args := board.CardArgs{ NewTitle: title, NewContent: content, } if _, err := u.board.CreateCard(args); err != nil { return fmt.Errorf("unable to create card, %w", err) } _ = u.refresh() return nil } // openBoard opens the kanban board. func (u *UI) openBoard(path string) error { b, err := board.Open(path) if err != nil { return fmt.Errorf("unable to load board, %w", err) } u.board = b if err = u.refresh(); err != nil { return fmt.Errorf("error refreshing the board, %w", err) } return nil } // refresh refreshes the UI. func (u *UI) refresh() error { statusList, err := u.board.StatusList() if err != nil { return fmt.Errorf("unable to get the status list, %w", err) } u.updateColumns(statusList) u.updateMovePage(statusList) u.setColumnFocus() return nil } // shutdown shuts down the application. func (u *UI) shutdown() { u.closeBoard() u.Stop() } func (u *UI) updateMovePage(statusList []board.Status) { if u.pages.HasPage(movePageName) { u.pages.RemovePage(movePageName) } move := tview.NewFlex() statusSelection := tview.NewList() statusSelection.SetBorder(true) statusSelection.ShowSecondaryText(false) statusSelection.SetHighlightFullLine(true) statusSelection.SetSelectedFocusOnly(true) statusSelection.SetWrapAround(false) doneFunc := func() { u.pages.HidePage(movePageName) u.setColumnFocus() } statusSelection.SetDoneFunc(doneFunc) selectedFunc := func(_ int, _, secondary string, _ rune) { currentStatusID := u.columns[u.focusedColumn].statusID nextStatusID, err := strconv.Atoi(secondary) if err != nil { nextStatusID = 0 } currentItem := u.columns[u.focusedColumn].cards.GetCurrentItem() _, cardIDText := u.columns[u.focusedColumn].cards.GetItemText(currentItem) cardID, _ := strconv.Atoi(cardIDText) args := board.MoveToStatusArgs{ CardID: cardID, CurrentStatusID: currentStatusID, NextStatusID: nextStatusID, } _ = u.board.MoveToStatus(args) u.pages.HidePage(movePageName) _ = u.refresh() } statusSelection.SetSelectedFunc(selectedFunc) for _, status := range statusList { id := strconv.Itoa(status.ID) statusSelection.AddItem(fmt.Sprintf("\u25C9 %s", status.Name), id, 0, nil) } move.AddItem(statusSelection, 0, 1, true) u.move = move u.pages.AddPage(movePageName, move, false, false) }