checkpoint: refactoring stuff
- Rename UI to App - Rename NewUI to NewApp - Move init functions to internal/ui/ui.go - Call app's initialisation function from main. - Move the keybinding functionality to internal/ui/keymappings.go - Add documentation for updateColumn and updateAllColumns
This commit is contained in:
parent
43095b9932
commit
db65a51c66
6 changed files with 357 additions and 295 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,3 @@
|
||||||
/test/databases/*.db
|
/test/databases/*.db
|
||||||
/pelican
|
/pelican
|
||||||
/kanban.pelican
|
/*.pelican
|
||||||
|
|
|
@ -25,12 +25,16 @@ func main() {
|
||||||
log.Fatalf("ERROR: Unexpected number of command-line arguments; want 1; got %d", len(args))
|
log.Fatalf("ERROR: Unexpected number of command-line arguments; want 1; got %d", len(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
pelican, err := ui.NewUI(args[0])
|
pelican, err := ui.NewApp(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("ERROR: Unable to initialise Pelican; %v", err)
|
log.Fatalf("ERROR: Unable to create pelican; %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pelican.Init(); err != nil {
|
||||||
|
log.Fatalf("ERROR: Unable to initialise pelican; %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pelican.Run(); err != nil {
|
if err := pelican.Run(); err != nil {
|
||||||
log.Fatalf("Error: an error occurred while running pelican, %s", err)
|
log.Fatalf("ERROR: an error occurred while running pelican, %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
106
internal/ui/init.go
Normal file
106
internal/ui/init.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// initColumns initialises the columns of the Kanban board.
|
||||||
|
func (a *App) initColumns() error {
|
||||||
|
a.columnFlex.Clear()
|
||||||
|
|
||||||
|
statusList, err := a.board.StatusList()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to retrieve the list of statuses; %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
columns := make([]*column, len(statusList))
|
||||||
|
|
||||||
|
for i := range statusList {
|
||||||
|
column := newColumn(statusList[i].ID, statusList[i].Name, a.boardMode)
|
||||||
|
a.columnFlex.AddItem(column, 50, 1, true)
|
||||||
|
columns[i] = column
|
||||||
|
}
|
||||||
|
|
||||||
|
a.columns = columns
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// initCardForm initialises the card form.
|
||||||
|
func (a *App) initCardForm() {
|
||||||
|
doneFunc := func(title, description string, success bool, mode cardFormMode) {
|
||||||
|
if success {
|
||||||
|
switch mode {
|
||||||
|
case create:
|
||||||
|
a.saveCard(title, description)
|
||||||
|
case edit:
|
||||||
|
a.editFocusedCard(title, description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.pages.HidePage(cardFormPage)
|
||||||
|
a.setColumnFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
a.cardForm.setDoneFunc(doneFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initDeleteCardModal initialises the modal for deleting cards.
|
||||||
|
func (a *App) initDeleteCardModal() {
|
||||||
|
doneFunc := func(_ int, buttonLabel string) {
|
||||||
|
if buttonLabel == "Confirm" {
|
||||||
|
a.deleteFocusedCard()
|
||||||
|
a.refresh(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.pages.HidePage(deleteCardModalPage)
|
||||||
|
a.setColumnFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
a.deleteCardModal.AddButtons([]string{"Confirm", "Cancel"}).SetDoneFunc(doneFunc)
|
||||||
|
|
||||||
|
a.deleteCardModal.SetBorder(true).SetBorderColor(tcell.ColorOrangeRed)
|
||||||
|
a.deleteCardModal.SetBackgroundColor(tcell.ColorBlack.TrueColor())
|
||||||
|
a.deleteCardModal.SetButtonBackgroundColor(tcell.ColorBlueViolet.TrueColor())
|
||||||
|
a.deleteCardModal.SetTextColor(tcell.ColorWhite.TrueColor())
|
||||||
|
}
|
||||||
|
|
||||||
|
// initQuitModal initialises the quit modal.
|
||||||
|
func (a *App) initQuitModal() {
|
||||||
|
doneFunc := func(_ int, buttonLabel string) {
|
||||||
|
switch buttonLabel {
|
||||||
|
case "Quit":
|
||||||
|
a.shutdown()
|
||||||
|
default:
|
||||||
|
a.pages.HidePage(quitPage)
|
||||||
|
a.setColumnFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.quitModal.SetText("Do you want to quit the application?").
|
||||||
|
AddButtons([]string{"Quit", "Cancel"}).
|
||||||
|
SetDoneFunc(doneFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initView initialises the view window for displaying the card.
|
||||||
|
func (a *App) initView() {
|
||||||
|
doneFunc := func(key tcell.Key) {
|
||||||
|
if key == tcell.KeyEsc {
|
||||||
|
a.pages.HidePage(viewPage)
|
||||||
|
a.view.Clear()
|
||||||
|
a.setColumnFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.view.setDoneFunc(doneFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) initStatusbar() {
|
||||||
|
changedFunc := func() {
|
||||||
|
a.Draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
a.statusbar.SetChangedFunc(changedFunc)
|
||||||
|
}
|
131
internal/ui/keymappings.go
Normal file
131
internal/ui/keymappings.go
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *App) inputCapture() func(event *tcell.EventKey) *tcell.EventKey {
|
||||||
|
return func(event *tcell.EventKey) *tcell.EventKey {
|
||||||
|
key, letter := event.Key(), event.Rune()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case letter == 'h' || key == tcell.KeyLeft:
|
||||||
|
a.shiftColumnFocus(previous)
|
||||||
|
case letter == 'l' || key == tcell.KeyRight:
|
||||||
|
a.shiftColumnFocus(next)
|
||||||
|
case letter == 'c':
|
||||||
|
a.create()
|
||||||
|
case letter == 'm':
|
||||||
|
a.move()
|
||||||
|
case letter == 'e':
|
||||||
|
a.edit()
|
||||||
|
case key == tcell.KeyCtrlD:
|
||||||
|
a.delete()
|
||||||
|
case key == tcell.KeyCtrlQ:
|
||||||
|
a.quit()
|
||||||
|
case key == tcell.KeyESC:
|
||||||
|
a.escape()
|
||||||
|
case key == tcell.KeyEnter:
|
||||||
|
a.selectHandle()
|
||||||
|
}
|
||||||
|
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) create() {
|
||||||
|
if a.mode == normal {
|
||||||
|
a.cardForm.mode = create
|
||||||
|
a.cardForm.updateInputFields("", "")
|
||||||
|
a.cardForm.frame.SetTitle(" Create Card ")
|
||||||
|
a.pages.ShowPage(cardFormPage)
|
||||||
|
a.SetFocus(a.cardForm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) move() {
|
||||||
|
if a.mode == normal {
|
||||||
|
a.statusSelection.cardID = a.focusedCardID()
|
||||||
|
a.statusSelection.currentStatusID = a.focusedStatusID()
|
||||||
|
a.mode = selection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) edit() {
|
||||||
|
if a.mode == normal {
|
||||||
|
a.cardForm.mode = edit
|
||||||
|
|
||||||
|
card, ok := a.getFocusedCard()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a.cardForm.updateInputFields(card.Title, card.Description)
|
||||||
|
a.cardForm.frame.SetTitle(" Edit Card ")
|
||||||
|
a.pages.ShowPage(cardFormPage)
|
||||||
|
a.SetFocus(a.cardForm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) delete() {
|
||||||
|
if a.mode == normal {
|
||||||
|
card, ok := a.getFocusedCard()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
text := fmt.Sprintf("Do you want to delete '%s'?", card.Title)
|
||||||
|
a.deleteCardModal.SetText(text)
|
||||||
|
a.pages.ShowPage(deleteCardModalPage)
|
||||||
|
a.SetFocus(a.deleteCardModal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) quit() {
|
||||||
|
if a.mode == normal {
|
||||||
|
a.pages.ShowPage(quitPage)
|
||||||
|
a.SetFocus(a.quitModal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) selectHandle() {
|
||||||
|
switch a.mode {
|
||||||
|
case normal:
|
||||||
|
card, ok := a.getFocusedCard()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
status := a.focusedStatusName()
|
||||||
|
a.view.setText(card.ID, card.Title, status, card.Created, card.Description)
|
||||||
|
a.pages.ShowPage(viewPage)
|
||||||
|
a.SetFocus(a.view)
|
||||||
|
case selection:
|
||||||
|
a.statusSelection.nextStatusID = a.focusedStatusID()
|
||||||
|
if a.statusSelection.currentStatusID != a.statusSelection.nextStatusID {
|
||||||
|
if err := a.statusSelection.moveCard(a.board); err != nil {
|
||||||
|
a.statusbar.displayMessage(
|
||||||
|
errorLevel,
|
||||||
|
fmt.Sprintf("Failed to move card: %v", err),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
a.statusbar.displayMessage(
|
||||||
|
infoLevel,
|
||||||
|
"Card moved successfully.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.statusSelection = statusSelection{0, 0, 0}
|
||||||
|
a.mode = normal
|
||||||
|
a.refresh(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) escape() {
|
||||||
|
if a.mode != normal {
|
||||||
|
a.mode = normal
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"codeflow.dananglin.me.uk/apollo/pelican/internal/board"
|
"codeflow.dananglin.me.uk/apollo/pelican/internal/board"
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,7 +30,7 @@ const (
|
||||||
viewPage string = "view"
|
viewPage string = "view"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UI struct {
|
type App struct {
|
||||||
*tview.Application
|
*tview.Application
|
||||||
|
|
||||||
columns []*column
|
columns []*column
|
||||||
|
@ -49,14 +48,14 @@ type UI struct {
|
||||||
statusbar *statusbar
|
statusbar *statusbar
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUI returns a new UI value.
|
// NewApp returns a new App value.
|
||||||
func NewUI(path string) (UI, error) {
|
func NewApp(path string) (App, error) {
|
||||||
kanban, err := board.Open(path)
|
kanban, err := board.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return UI{}, fmt.Errorf("unable to open the project's board; %w", err)
|
return App{}, fmt.Errorf("unable to open the project's board; %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
userInterface := UI{
|
app := App{
|
||||||
Application: tview.NewApplication(),
|
Application: tview.NewApplication(),
|
||||||
pages: tview.NewPages(),
|
pages: tview.NewPages(),
|
||||||
baseFlex: tview.NewFlex(),
|
baseFlex: tview.NewFlex(),
|
||||||
|
@ -73,248 +72,66 @@ func NewUI(path string) (UI, error) {
|
||||||
statusbar: newStatusbar(),
|
statusbar: newStatusbar(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := userInterface.init(); err != nil {
|
return app, nil
|
||||||
return UI{}, fmt.Errorf("received an error after running the initialisation; %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return userInterface, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// closeBoard closes the board.
|
|
||||||
func (u *UI) closeBoard() {
|
|
||||||
_ = u.board.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// init initialises the UI.
|
// init initialises the UI.
|
||||||
func (u *UI) init() error {
|
func (a *App) Init() error {
|
||||||
if err := u.initColumns(); err != nil {
|
if err := a.initColumns(); err != nil {
|
||||||
return fmt.Errorf("error initialising the status columns; %w", err)
|
return fmt.Errorf("error initialising the status columns; %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.columnFlex.SetInputCapture(u.inputCapture())
|
a.columnFlex.SetInputCapture(a.inputCapture())
|
||||||
|
|
||||||
u.initStatusbar()
|
a.initStatusbar()
|
||||||
|
|
||||||
u.baseFlex.SetDirection(tview.FlexRow)
|
a.baseFlex.SetDirection(tview.FlexRow)
|
||||||
u.baseFlex.AddItem(u.columnFlex, 0, 1, true)
|
a.baseFlex.AddItem(a.columnFlex, 0, 1, true)
|
||||||
u.baseFlex.AddItem(u.statusbar, 2, 1, false)
|
a.baseFlex.AddItem(a.statusbar, 2, 1, false)
|
||||||
|
|
||||||
u.pages.AddPage(mainPage, u.baseFlex, true, true)
|
a.pages.AddPage(mainPage, a.baseFlex, true, true)
|
||||||
|
|
||||||
u.initQuitModal()
|
a.initQuitModal()
|
||||||
u.pages.AddPage(quitPage, u.quitModal, false, false)
|
a.pages.AddPage(quitPage, a.quitModal, false, false)
|
||||||
|
|
||||||
u.initCardForm()
|
a.initCardForm()
|
||||||
u.pages.AddPage(cardFormPage, u.cardForm, false, false)
|
a.pages.AddPage(cardFormPage, a.cardForm, false, false)
|
||||||
|
|
||||||
u.initDeleteCardModal()
|
a.initDeleteCardModal()
|
||||||
u.pages.AddPage(deleteCardModalPage, u.deleteCardModal, false, false)
|
a.pages.AddPage(deleteCardModalPage, a.deleteCardModal, false, false)
|
||||||
|
|
||||||
u.initView()
|
a.initView()
|
||||||
u.pages.AddPage(viewPage, u.view, false, false)
|
a.pages.AddPage(viewPage, a.view, false, false)
|
||||||
|
|
||||||
u.SetRoot(u.pages, true)
|
a.SetRoot(a.pages, true)
|
||||||
|
|
||||||
u.refresh(false)
|
a.refresh(false)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UI) inputCapture() func(event *tcell.EventKey) *tcell.EventKey {
|
// saveCard saves a new card to the database.
|
||||||
return func(event *tcell.EventKey) *tcell.EventKey {
|
func (a *App) saveCard(title, description string) {
|
||||||
key, letter := event.Key(), event.Rune()
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case letter == 'h' || key == tcell.KeyLeft:
|
|
||||||
u.shiftColumnFocus(previous)
|
|
||||||
case letter == 'l' || key == tcell.KeyRight:
|
|
||||||
u.shiftColumnFocus(next)
|
|
||||||
case letter == 'c':
|
|
||||||
if u.mode == normal {
|
|
||||||
u.cardForm.mode = create
|
|
||||||
u.cardForm.updateInputFields("", "")
|
|
||||||
u.cardForm.frame.SetTitle(" Create Card ")
|
|
||||||
u.pages.ShowPage(cardFormPage)
|
|
||||||
u.SetFocus(u.cardForm)
|
|
||||||
}
|
|
||||||
case letter == 'm':
|
|
||||||
if u.mode == normal {
|
|
||||||
u.statusSelection.cardID = u.focusedCardID()
|
|
||||||
u.statusSelection.currentStatusID = u.focusedStatusID()
|
|
||||||
u.mode = selection
|
|
||||||
}
|
|
||||||
case letter == 'e':
|
|
||||||
if u.mode == normal {
|
|
||||||
u.cardForm.mode = edit
|
|
||||||
|
|
||||||
card, ok := u.getFocusedCard()
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
u.cardForm.updateInputFields(card.Title, card.Description)
|
|
||||||
u.cardForm.frame.SetTitle(" Edit Card ")
|
|
||||||
u.pages.ShowPage(cardFormPage)
|
|
||||||
u.SetFocus(u.cardForm)
|
|
||||||
}
|
|
||||||
case key == tcell.KeyCtrlD:
|
|
||||||
if u.mode == normal {
|
|
||||||
card, ok := u.getFocusedCard()
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
text := fmt.Sprintf("Do you want to delete '%s'?", card.Title)
|
|
||||||
u.deleteCardModal.SetText(text)
|
|
||||||
u.pages.ShowPage(deleteCardModalPage)
|
|
||||||
u.SetFocus(u.deleteCardModal)
|
|
||||||
}
|
|
||||||
case key == tcell.KeyCtrlQ:
|
|
||||||
if u.mode == normal {
|
|
||||||
u.pages.ShowPage(quitPage)
|
|
||||||
u.SetFocus(u.quitModal)
|
|
||||||
}
|
|
||||||
case key == tcell.KeyESC:
|
|
||||||
if u.mode != normal {
|
|
||||||
u.mode = normal
|
|
||||||
}
|
|
||||||
case key == tcell.KeyEnter:
|
|
||||||
switch u.mode {
|
|
||||||
case normal:
|
|
||||||
card, ok := u.getFocusedCard()
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
status := u.focusedStatusName()
|
|
||||||
u.view.setText(card.ID, card.Title, status, card.Created, card.Description)
|
|
||||||
u.pages.ShowPage(viewPage)
|
|
||||||
u.SetFocus(u.view)
|
|
||||||
case selection:
|
|
||||||
u.statusSelection.nextStatusID = u.focusedStatusID()
|
|
||||||
if u.statusSelection.currentStatusID != u.statusSelection.nextStatusID {
|
|
||||||
if err := u.statusSelection.moveCard(u.board); err != nil {
|
|
||||||
u.statusbar.displayMessage(
|
|
||||||
errorLevel,
|
|
||||||
fmt.Sprintf("Failed to move card: %v", err),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
u.statusbar.displayMessage(
|
|
||||||
infoLevel,
|
|
||||||
"Card moved successfully.",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u.statusSelection = statusSelection{0, 0, 0}
|
|
||||||
u.mode = normal
|
|
||||||
u.refresh(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return event
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// initCardForm initialises the card form.
|
|
||||||
func (u *UI) initCardForm() {
|
|
||||||
doneFunc := func(title, description string, success bool, mode cardFormMode) {
|
|
||||||
if success {
|
|
||||||
switch mode {
|
|
||||||
case create:
|
|
||||||
u.saveCard(title, description)
|
|
||||||
case edit:
|
|
||||||
u.editFocusedCard(title, description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u.pages.HidePage(cardFormPage)
|
|
||||||
u.setColumnFocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
u.cardForm.setDoneFunc(doneFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// initDeleteCardModal initialises the modal for deleting cards.
|
|
||||||
func (u *UI) initDeleteCardModal() {
|
|
||||||
doneFunc := func(_ int, buttonLabel string) {
|
|
||||||
if buttonLabel == "Confirm" {
|
|
||||||
u.deleteFocusedCard()
|
|
||||||
u.refresh(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
u.pages.HidePage(deleteCardModalPage)
|
|
||||||
u.setColumnFocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
u.deleteCardModal.AddButtons([]string{"Confirm", "Cancel"}).SetDoneFunc(doneFunc)
|
|
||||||
|
|
||||||
u.deleteCardModal.SetBorder(true).SetBorderColor(tcell.ColorOrangeRed)
|
|
||||||
u.deleteCardModal.SetBackgroundColor(tcell.ColorBlack.TrueColor())
|
|
||||||
u.deleteCardModal.SetButtonBackgroundColor(tcell.ColorBlueViolet.TrueColor())
|
|
||||||
u.deleteCardModal.SetTextColor(tcell.ColorWhite.TrueColor())
|
|
||||||
}
|
|
||||||
|
|
||||||
// initQuitModal initialises the quit modal.
|
|
||||||
func (u *UI) initQuitModal() {
|
|
||||||
doneFunc := func(_ int, buttonLabel string) {
|
|
||||||
switch buttonLabel {
|
|
||||||
case "Quit":
|
|
||||||
u.shutdown()
|
|
||||||
default:
|
|
||||||
u.pages.HidePage(quitPage)
|
|
||||||
u.setColumnFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u.quitModal.SetText("Do you want to quit the application?").
|
|
||||||
AddButtons([]string{"Quit", "Cancel"}).
|
|
||||||
SetDoneFunc(doneFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// initView initialises the view window for displaying the card.
|
|
||||||
func (u *UI) initView() {
|
|
||||||
doneFunc := func(key tcell.Key) {
|
|
||||||
if key == tcell.KeyEsc {
|
|
||||||
u.pages.HidePage(viewPage)
|
|
||||||
u.view.Clear()
|
|
||||||
u.setColumnFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u.view.setDoneFunc(doneFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UI) initStatusbar() {
|
|
||||||
changedFunc := func() {
|
|
||||||
u.Draw()
|
|
||||||
}
|
|
||||||
|
|
||||||
u.statusbar.SetChangedFunc(changedFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// saveCard creates and saves a new card to the database.
|
|
||||||
func (u *UI) saveCard(title, description string) {
|
|
||||||
args := board.CardArgs{
|
args := board.CardArgs{
|
||||||
NewTitle: title,
|
NewTitle: title,
|
||||||
NewDescription: description,
|
NewDescription: description,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := u.board.CreateCard(args)
|
_, err := a.board.CreateCard(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.statusbar.displayMessage(errorLevel, fmt.Sprintf("Failed to create card: %v.", err))
|
a.statusbar.displayMessage(errorLevel, fmt.Sprintf("Failed to create card: %v.", err))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
u.statusbar.displayMessage(infoLevel, "Card created successfully.")
|
a.statusbar.displayMessage(infoLevel, "Card created successfully.")
|
||||||
|
|
||||||
u.refresh(false)
|
a.refresh(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// editFocusedCard saves and edited card to the database.
|
// editFocusedCard saves an edited card to the database.
|
||||||
func (u *UI) editFocusedCard(title, description string) {
|
func (a *App) editFocusedCard(title, description string) {
|
||||||
cardID := u.focusedCardID()
|
cardID := a.focusedCardID()
|
||||||
|
|
||||||
args := board.UpdateCardArgs{
|
args := board.UpdateCardArgs{
|
||||||
CardID: cardID,
|
CardID: cardID,
|
||||||
|
@ -324,24 +141,24 @@ func (u *UI) editFocusedCard(title, description string) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := u.board.UpdateCard(args); err != nil {
|
if err := a.board.UpdateCard(args); err != nil {
|
||||||
u.statusbar.displayMessage(errorLevel, fmt.Sprintf("Failed to edit card: %v.", err))
|
a.statusbar.displayMessage(errorLevel, fmt.Sprintf("Failed to edit card: %v.", err))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
u.statusbar.displayMessage(infoLevel, "Card edited successfully.")
|
a.statusbar.displayMessage(infoLevel, "Card edited successfully.")
|
||||||
|
|
||||||
u.refresh(true)
|
a.refresh(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getFocusedCard retrieves the details of the focused card.
|
// getFocusedCard retrieves the details of the card in focus.
|
||||||
func (u *UI) getFocusedCard() (board.Card, bool) {
|
func (a *App) getFocusedCard() (board.Card, bool) {
|
||||||
cardID := u.focusedCardID()
|
cardID := a.focusedCardID()
|
||||||
|
|
||||||
card, err := u.board.Card(cardID)
|
card, err := a.board.Card(cardID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.statusbar.displayMessage(
|
a.statusbar.displayMessage(
|
||||||
errorLevel,
|
errorLevel,
|
||||||
fmt.Sprintf("Failed to retrieve the card with card ID %d, %v.", cardID, err),
|
fmt.Sprintf("Failed to retrieve the card with card ID %d, %v.", cardID, err),
|
||||||
)
|
)
|
||||||
|
@ -352,47 +169,26 @@ func (u *UI) getFocusedCard() (board.Card, bool) {
|
||||||
return card, true
|
return card, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteFocusedCard deletes the focused card from the board.
|
// deleteFocusedCard deletes the card in focus from the board.
|
||||||
func (u *UI) deleteFocusedCard() {
|
func (a *App) deleteFocusedCard() {
|
||||||
args := board.DeleteCardArgs{
|
args := board.DeleteCardArgs{
|
||||||
CardID: u.focusedCardID(),
|
CardID: a.focusedCardID(),
|
||||||
StatusID: u.focusedStatusID(),
|
StatusID: a.focusedStatusID(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := u.board.DeleteCard(args); err != nil {
|
if err := a.board.DeleteCard(args); err != nil {
|
||||||
u.statusbar.displayMessage(errorLevel, fmt.Sprintf("Failed to delete card: %v.", err))
|
a.statusbar.displayMessage(errorLevel, fmt.Sprintf("Failed to delete card: %v.", err))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
u.statusbar.displayMessage(infoLevel, "Card deleted successfully.")
|
a.statusbar.displayMessage(infoLevel, "Card deleted successfully.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// initColumns initialises the columns of the Kanban board.
|
// updateAllColumns ensures that all columns are up-to-date.
|
||||||
func (u *UI) initColumns() error {
|
func (a *App) updateAllColumns() error {
|
||||||
u.columnFlex.Clear()
|
for i := range a.columns {
|
||||||
|
if err := a.updateColumn(i); err != nil {
|
||||||
statusList, err := u.board.StatusList()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to retrieve the list of statuses; %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
columns := make([]*column, len(statusList))
|
|
||||||
|
|
||||||
for i := range statusList {
|
|
||||||
column := newColumn(statusList[i].ID, statusList[i].Name, u.boardMode)
|
|
||||||
u.columnFlex.AddItem(column, 50, 1, true)
|
|
||||||
columns[i] = column
|
|
||||||
}
|
|
||||||
|
|
||||||
u.columns = columns
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UI) updateAllColumns() error {
|
|
||||||
for i := range u.columns {
|
|
||||||
if err := u.updateColumn(i); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -400,51 +196,55 @@ func (u *UI) updateAllColumns() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UI) updateColumn(index int) error {
|
// updateColumn ensures that the column is up-to-date.
|
||||||
if err := u.columns[index].update(u.board); err != nil {
|
func (a *App) updateColumn(index int) error {
|
||||||
|
if err := a.columns[index].update(a.board); err != nil {
|
||||||
return fmt.Errorf("unable to update column; %w", err)
|
return fmt.Errorf("unable to update column; %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UI) shiftColumnFocus(movement boardMovement) {
|
// shiftColumnFocus shifts the focus to the column to the left or right depending
|
||||||
|
// on the movement set by the user.
|
||||||
|
func (a *App) shiftColumnFocus(movement boardMovement) {
|
||||||
switch movement {
|
switch movement {
|
||||||
case next:
|
case next:
|
||||||
if u.focusedColumn == len(u.columns)-1 {
|
if a.focusedColumn == len(a.columns)-1 {
|
||||||
u.focusedColumn = 0
|
a.focusedColumn = 0
|
||||||
} else {
|
} else {
|
||||||
u.focusedColumn++
|
a.focusedColumn++
|
||||||
}
|
}
|
||||||
case previous:
|
case previous:
|
||||||
if u.focusedColumn == 0 {
|
if a.focusedColumn == 0 {
|
||||||
u.focusedColumn = len(u.columns) - 1
|
a.focusedColumn = len(a.columns) - 1
|
||||||
} else {
|
} else {
|
||||||
u.focusedColumn--
|
a.focusedColumn--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u.setColumnFocus()
|
a.setColumnFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UI) setColumnFocus() {
|
// setColumnFocus sets the focus to the column primitive as specified by focusedColumn.
|
||||||
u.SetFocus(u.columns[u.focusedColumn])
|
func (a *App) setColumnFocus() {
|
||||||
|
a.SetFocus(a.columns[a.focusedColumn])
|
||||||
}
|
}
|
||||||
|
|
||||||
// refresh refreshes the UI.
|
// refresh refreshes the UI.
|
||||||
func (u *UI) refresh(updateFocusedColumnOnly bool) {
|
func (a *App) refresh(updateFocusedColumnOnly bool) {
|
||||||
if updateFocusedColumnOnly {
|
if updateFocusedColumnOnly {
|
||||||
if err := u.updateColumn(u.focusedColumn); err != nil {
|
if err := a.updateColumn(a.focusedColumn); err != nil {
|
||||||
u.statusbar.displayMessage(
|
a.statusbar.displayMessage(
|
||||||
errorLevel,
|
errorLevel,
|
||||||
fmt.Sprintf("Failed to update status column %q: %v", u.focusedStatusName(), err),
|
fmt.Sprintf("Failed to update status column %q: %v", a.focusedStatusName(), err),
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := u.updateAllColumns(); err != nil {
|
if err := a.updateAllColumns(); err != nil {
|
||||||
u.statusbar.displayMessage(
|
a.statusbar.displayMessage(
|
||||||
errorLevel,
|
errorLevel,
|
||||||
fmt.Sprintf("Failed to update status columns: %v", err),
|
fmt.Sprintf("Failed to update status columns: %v", err),
|
||||||
)
|
)
|
||||||
|
@ -453,34 +253,39 @@ func (u *UI) refresh(updateFocusedColumnOnly bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u.setColumnFocus()
|
a.setColumnFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
// shutdown shuts down the application.
|
// shutdown shuts down the application.
|
||||||
func (u *UI) shutdown() {
|
func (a *App) shutdown() {
|
||||||
u.closeBoard()
|
a.closeBoard()
|
||||||
u.Stop()
|
a.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// boardMode returns the current board mode.
|
// boardMode returns the current board mode.
|
||||||
func (u *UI) boardMode() boardMode {
|
func (a *App) boardMode() boardMode {
|
||||||
return u.mode
|
return a.mode
|
||||||
}
|
}
|
||||||
|
|
||||||
// focusedCardID returns the ID of the card in focus.
|
// focusedCardID returns the ID of the card in focus.
|
||||||
func (u *UI) focusedCardID() int {
|
func (a *App) focusedCardID() int {
|
||||||
focusedCard := u.columns[u.focusedColumn].focusedCard
|
focusedCard := a.columns[a.focusedColumn].focusedCard
|
||||||
id := u.columns[u.focusedColumn].cards[focusedCard].id
|
id := a.columns[a.focusedColumn].cards[focusedCard].id
|
||||||
|
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
// focusedStatusID returns the ID of the status (column) in focus.
|
// focusedStatusID returns the ID of the status (column) in focus.
|
||||||
func (u *UI) focusedStatusID() int {
|
func (a *App) focusedStatusID() int {
|
||||||
return u.columns[u.focusedColumn].statusID
|
return a.columns[a.focusedColumn].statusID
|
||||||
}
|
}
|
||||||
|
|
||||||
// focusedStatusName returns the name of the status (column) in focus.
|
// focusedStatusName returns the name of the status (column) in focus.
|
||||||
func (u *UI) focusedStatusName() string {
|
func (a *App) focusedStatusName() string {
|
||||||
return u.columns[u.focusedColumn].statusName
|
return a.columns[a.focusedColumn].statusName
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeBoard closes the board.
|
||||||
|
func (a *App) closeBoard() {
|
||||||
|
_ = a.board.Close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ var Default = Build
|
||||||
// To enable verbose mode set PELICAN_TEST_VERBOSE=1.
|
// To enable verbose mode set PELICAN_TEST_VERBOSE=1.
|
||||||
// To enable coverage mode set PELICAN_TEST_COVER=1.
|
// To enable coverage mode set PELICAN_TEST_COVER=1.
|
||||||
func Test() error {
|
func Test() error {
|
||||||
goTest := sh.RunCmd("go", "test")
|
test := sh.RunCmd("go", "test")
|
||||||
|
|
||||||
args := []string{"./..."}
|
args := []string{"./..."}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ func Test() error {
|
||||||
args = append(args, "-cover")
|
args = append(args, "-cover")
|
||||||
}
|
}
|
||||||
|
|
||||||
return goTest(args...)
|
return test(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lint runs golangci-lint against the code.
|
// Lint runs golangci-lint against the code.
|
||||||
|
@ -46,11 +46,27 @@ func Lint() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build build the executable
|
// Build build the executable
|
||||||
|
// To rebuild packages that are already up-to-date set PELICAN_BUILD_REBUILD_ALL=1
|
||||||
|
// To enable verbose mode set PELICAN_BUILD_VERBOSE=1
|
||||||
func Build() error {
|
func Build() error {
|
||||||
main := "./cmd/" + binary
|
main := "./cmd/" + binary
|
||||||
flags := ldflags()
|
flags := ldflags()
|
||||||
|
|
||||||
return sh.Run("go", "build", "-ldflags="+flags, "-a", "-o", binary, main)
|
build := sh.RunCmd("go", "build")
|
||||||
|
|
||||||
|
args:= []string{"-ldflags="+flags, "-o", binary}
|
||||||
|
|
||||||
|
if os.Getenv("PELICAN_BUILD_REBUILD_ALL") == "1" {
|
||||||
|
args = append(args, "-a")
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("PELICAN_BUILD_VERBOSE") == "1" {
|
||||||
|
args = append(args, "-v")
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, main)
|
||||||
|
|
||||||
|
return build(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Install() error {
|
func Install() error {
|
||||||
|
|
Loading…
Reference in a new issue