package ui import ( "fmt" "codeflow.dananglin.me.uk/apollo/canal/internal/board" "github.com/rivo/tview" "github.com/gdamore/tcell/v2" ) const ( shiftLeft int = iota shiftRight ) const ( mainPageName string = "main" quitPageName string = "quit" addPageName string = "add" ) // UI does some magical stuff. type UI struct { *tview.Application columns []column flex *tview.Flex pages *tview.Pages focusedColumn int board board.Board quit *tview.Modal } // NewUI returns a new UI value. func NewUI() UI { u := UI{ Application: tview.NewApplication(), pages: tview.NewPages(), flex: tview.NewFlex(), quit: tview.NewModal(), focusedColumn: 0, } u.initialise() return u } // initialise the UI. func (u *UI) initialise() { u.pages.AddPage(mainPageName, u.flex, true, true) u.initQuitModal() u.pages.AddPage(quitPageName, u.quit, false, false) u.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Rune() == 'q' { u.pages.ShowPage(quitPageName) u.SetFocus(u.quit) } else if event.Rune() == 'o' { u.openBoard("") } return event }) u.SetRoot(u.pages, true) } // initQuitModal initialises the quit modal. func (u *UI) initQuitModal() { quitDoneFunc := func(_ int, buttonLabel string) { switch buttonLabel { case "Quit": u.shutdown() default: u.pages.SwitchToPage("main") u.setColumnFocus() } } u.quit.SetText("Do you want to quit the application?"). AddButtons([]string{"Quit", "Cancel"}). SetDoneFunc(quitDoneFunc) } // shutdown shuts down the application. func (u *UI) shutdown() { u.closeBoard() u.Stop() } // closeBoard closes the BoltDB database. func (u *UI) closeBoard() { _ = u.board.Close() } // openBoard opens the kanban project. 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 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.updateBoard(statusList) u.setColumnFocus() return nil } func (u *UI) updateBoard(statusList []board.Status) error { u.flex.Clear() columns := make([]column, len(statusList)) for i := range statusList { columns[i] = u.newColumn(statusList[i].ID, statusList[i].Name) if len(statusList[i].CardIds) > 0 { cards, err := u.board.CardList(statusList[i].CardIds) if err != nil { return fmt.Errorf("unable to get the card list. %w", err) } for _, c := range cards { columns[i].cards.AddItem(fmt.Sprintf("[%d] %s", c.Id(), c.Title), "", 0, nil) } } u.flex.AddItem(columns[i].cards, 0, 1, false) } u.columns = columns return nil } func (u *UI) shiftColumnFocus(s int) { switch s { case shiftRight: if u.focusedColumn == len(u.columns)-1 { u.focusedColumn = 0 } else { u.focusedColumn++ } case shiftLeft: if u.focusedColumn == 0 { u.focusedColumn = len(u.columns) - 1 } else { u.focusedColumn-- } } u.setColumnFocus() } func (u *UI) setColumnFocus() { u.SetFocus(u.columns[u.focusedColumn].cards) } // newCard creates a new card and saves it 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 } // TODO: Move 'Add' to the centre of the app // TODO: Customize list primitive or create a new one // TODO: If customizing existing list primitive, wrap list around a column type. Add statusID to it. // TODO: Update card status (card ID, oldStatus, newStatus) //func viewCard() error { //return nil //}