package ui import ( "fmt" "forge.dananglin.me.uk/code/dananglin/canal/internal/board" "github.com/rivo/tview" bolt "go.etcd.io/bbolt" ) type shiftDirection int const ( shiftLeft shiftDirection = iota shiftRight ) const ( mainPage string = "main" quitPage string = "quit" addPage string = "add" ) // App does some magical stuff. type App struct { *tview.Application columns []column flex *tview.Flex pages *tview.Pages focusedColumn int db *bolt.DB } // shutdown shuts down the application. func (a *App) shutdown() { a.closeDB() a.Stop() } // closeDB closes the BoltDB database. func (a *App) closeDB() { if a.db != nil { _ = a.db.Close() } } // openProject opens the kanban project. func (a *App) openProject(path string) error { if a.db != nil && len(a.db.Path()) > 0 { a.db.Close() } db, err := board.OpenProject(path) if err != nil { return fmt.Errorf("unable to load board, %w", err) } a.db = db if err = a.refresh(); err != nil { return err } return nil } // refresh refreshes the UI. func (a *App) refresh() error { statusList, err := board.ReadStatusList(a.db) if err != nil { return fmt.Errorf("unable to get the status list, %w", err) } a.updateBoard(statusList) a.setColumnFocus() return nil } func (a *App) updateBoard(statusList []board.Status) error { a.flex.Clear() columns := make([]column, len(statusList)) for i := range statusList { columns[i] = a.newColumn(statusList[i].ID, statusList[i].Name) if len(statusList[i].CardIds) > 0 { cards, err := board.ReadCardList(a.db, 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) } } a.flex.AddItem(columns[i].cards, 0, 1, false) } a.columns = columns return nil } func (a *App) shiftColumnFocus(s shiftDirection) { switch s { case shiftRight: if a.focusedColumn == len(a.columns)-1 { a.focusedColumn = 0 } else { a.focusedColumn++ } case shiftLeft: if a.focusedColumn == 0 { a.focusedColumn = len(a.columns) - 1 } else { a.focusedColumn-- } } a.setColumnFocus() } func (a *App) setColumnFocus() { a.SetFocus(a.columns[a.focusedColumn].cards) } // newCard creates a new card and saves it to the database. func (a *App) newCard(title, content string) error { args := board.CardArgs{ NewTitle: title, NewContent: content, } if err := board.CreateCard(a.db, args); err != nil { return fmt.Errorf("unable to create card, %w", err) } a.refresh() return nil } // TODO: Move 'Add' to the centre of the app // TODO: Customize list primitive or create a new one // TODO: If customizing exisiing 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 //}