feat(ui): add support for viewing the details of a card #17
4 changed files with 134 additions and 24 deletions
|
@ -81,10 +81,13 @@ and initialises the database with an empty project.
|
|||
|Create a new card
|
||||
|
||||
|CTRL + d
|
||||
|Delete card
|
||||
|Delete a card
|
||||
|
||||
|m
|
||||
|Move card between statuses
|
||||
|Move a card from one column to another (press `Enter` to confirm your selection).
|
||||
|
||||
|Enter
|
||||
|View the details of a card (press `ESC` to go back to the main board).
|
||||
|===
|
||||
|
||||
== Inspiration
|
||||
|
|
69
internal/ui/cardview.go
Normal file
69
internal/ui/cardview.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
type cardView struct {
|
||||
*tview.TextView
|
||||
frame *tview.Frame
|
||||
contentFormat string
|
||||
}
|
||||
|
||||
func newCardView() *cardView {
|
||||
border := tcell.ColorOrangeRed.TrueColor()
|
||||
background := tcell.ColorBlack.TrueColor()
|
||||
|
||||
content := tview.NewTextView()
|
||||
|
||||
// Stylise the TextView
|
||||
content.SetDynamicColors(true).
|
||||
SetBorder(true).
|
||||
SetBorderPadding(0, 0, 1, 1).
|
||||
SetBorderColor(border).
|
||||
SetBackgroundColor(background)
|
||||
|
||||
cardContentFormat := `[green::b][#%d[] %s[-:-:-:-]
|
||||
|
||||
[green::b]Status:[white::-] %s [green::b]Created:[white::-] %s
|
||||
|
||||
[green::b]Description:[white::-]
|
||||
|
||||
%s
|
||||
`
|
||||
|
||||
view := cardView{
|
||||
TextView: content,
|
||||
frame: tview.NewFrame(content),
|
||||
contentFormat: cardContentFormat,
|
||||
}
|
||||
|
||||
return &view
|
||||
}
|
||||
|
||||
func (c *cardView) setDoneFunc(handler func(key tcell.Key)) {
|
||||
c.SetDoneFunc(handler)
|
||||
}
|
||||
|
||||
func (c *cardView) print(id int, title, status, created, description string) {
|
||||
fmt.Fprintf(c, c.contentFormat, id, title, status, created, description)
|
||||
}
|
||||
|
||||
func (c *cardView) Draw(screen tcell.Screen) {
|
||||
height := 25
|
||||
width := 50
|
||||
|
||||
screenWidth, screenHeight := screen.Size()
|
||||
|
||||
// Set the form's position and size.
|
||||
x := (screenWidth - width) / 2
|
||||
y := (screenHeight - height) / 2
|
||||
c.SetRect(x, y, width, height)
|
||||
|
||||
// Draw the frame.
|
||||
c.frame.SetRect(x, y, width, height)
|
||||
c.frame.Draw(screen)
|
||||
}
|
|
@ -19,6 +19,7 @@ type column struct {
|
|||
*tview.Box
|
||||
|
||||
statusID int
|
||||
statusName string
|
||||
cards []card
|
||||
focusedCard int
|
||||
getBoardModeFunc func() boardMode
|
||||
|
@ -28,6 +29,7 @@ func newColumn(statusID int, statusName string, getBoardModeFunc func() boardMod
|
|||
column := column{
|
||||
Box: tview.NewBox(),
|
||||
statusID: statusID,
|
||||
statusName: statusName,
|
||||
cards: nil,
|
||||
focusedCard: 0,
|
||||
getBoardModeFunc: getBoardModeFunc,
|
||||
|
|
|
@ -28,6 +28,7 @@ const (
|
|||
quitPage string = "quit"
|
||||
cardFormPage string = "card form"
|
||||
deleteCardModalPage string = "delete card modal"
|
||||
viewPage string = "view"
|
||||
)
|
||||
|
||||
type UI struct {
|
||||
|
@ -43,6 +44,7 @@ type UI struct {
|
|||
cardForm *cardForm
|
||||
deleteCardModal *tview.Modal
|
||||
statusSelection statusSelection
|
||||
view *cardView
|
||||
}
|
||||
|
||||
// NewUI returns a new UI value.
|
||||
|
@ -64,6 +66,7 @@ func NewUI(path string) (UI, error) {
|
|||
deleteCardModal: tview.NewModal(),
|
||||
mode: normal,
|
||||
statusSelection: statusSelection{0, 0, 0},
|
||||
view: newCardView(),
|
||||
}
|
||||
|
||||
if err := userInterface.init(); err != nil {
|
||||
|
@ -85,7 +88,33 @@ func (u *UI) init() error {
|
|||
return fmt.Errorf("error initialising the status columns; %w", err)
|
||||
}
|
||||
|
||||
u.flex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
u.flex.SetInputCapture(u.inputCapture())
|
||||
|
||||
u.pages.AddPage(mainPage, u.flex, true, true)
|
||||
|
||||
u.initQuitModal()
|
||||
u.pages.AddPage(quitPage, u.quitModal, false, false)
|
||||
|
||||
u.initCardForm()
|
||||
u.pages.AddPage(cardFormPage, u.cardForm, false, false)
|
||||
|
||||
u.initDeleteCardModal()
|
||||
u.pages.AddPage(deleteCardModalPage, u.deleteCardModal, false, false)
|
||||
|
||||
u.initView()
|
||||
u.pages.AddPage(viewPage, u.view, false, false)
|
||||
|
||||
u.SetRoot(u.pages, true)
|
||||
|
||||
if err := u.refresh(false); err != nil {
|
||||
return fmt.Errorf("error refreshing the board; %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UI) inputCapture() func(event *tcell.EventKey) *tcell.EventKey {
|
||||
return func(event *tcell.EventKey) *tcell.EventKey {
|
||||
key, letter := event.Key(), event.Rune()
|
||||
|
||||
switch {
|
||||
|
@ -131,11 +160,19 @@ func (u *UI) init() error {
|
|||
u.mode = normal
|
||||
}
|
||||
case key == tcell.KeyEnter:
|
||||
if u.mode == selection {
|
||||
switch u.mode {
|
||||
case normal:
|
||||
card, _ := u.getFocusedCard()
|
||||
status := u.focusedStatusName()
|
||||
u.view.print(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 {
|
||||
u.statusSelection.moveCard(u.board)
|
||||
}
|
||||
|
||||
u.statusSelection = statusSelection{0, 0, 0}
|
||||
u.mode = normal
|
||||
_ = u.refresh(false)
|
||||
|
@ -143,26 +180,7 @@ func (u *UI) init() error {
|
|||
}
|
||||
|
||||
return event
|
||||
})
|
||||
|
||||
u.pages.AddPage(mainPage, u.flex, true, true)
|
||||
|
||||
u.initQuitModal()
|
||||
u.pages.AddPage(quitPage, u.quitModal, false, false)
|
||||
|
||||
u.initCardForm()
|
||||
u.pages.AddPage(cardFormPage, u.cardForm, false, false)
|
||||
|
||||
u.initDeleteCardModal()
|
||||
u.pages.AddPage(deleteCardModalPage, u.deleteCardModal, false, false)
|
||||
|
||||
u.SetRoot(u.pages, true)
|
||||
|
||||
if err := u.refresh(false); err != nil {
|
||||
return fmt.Errorf("error refreshing the board; %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// initCardForm initialises the card form.
|
||||
|
@ -218,6 +236,19 @@ func (u *UI) initQuitModal() {
|
|||
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)
|
||||
}
|
||||
|
||||
// newCard creates and saves a new card to the database.
|
||||
func (u *UI) newCard(title, description string) error {
|
||||
args := board.CardArgs{
|
||||
|
@ -376,7 +407,12 @@ func (u *UI) focusedCardID() int {
|
|||
return id
|
||||
}
|
||||
|
||||
// focusedStatusID returns the ID of thestatus (column) in focus.
|
||||
// focusedStatusID returns the ID of the status (column) in focus.
|
||||
func (u *UI) focusedStatusID() int {
|
||||
return u.columns[u.focusedColumn].statusID
|
||||
}
|
||||
|
||||
// focusedStatusName returns the name of the status (column) in focus.
|
||||
func (u *UI) focusedStatusName() string {
|
||||
return u.columns[u.focusedColumn].statusName
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue