Work workspace with runs, change and facts tabs; vim keys; responsive Board; theme file
This commit is contained in:
55
tui/board.go
55
tui/board.go
@@ -130,13 +130,13 @@ func (m boardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case "r":
|
||||
m.loading = true
|
||||
return m, m.reload()
|
||||
case "up":
|
||||
case "up", "k":
|
||||
if m.cursor > 0 {
|
||||
m.cursor--
|
||||
return m, m.loadDetail(m.selectedID())
|
||||
}
|
||||
return m, nil
|
||||
case "down":
|
||||
case "down", "j":
|
||||
if m.cursor < len(m.rows)-1 {
|
||||
m.cursor++
|
||||
return m, m.loadDetail(m.selectedID())
|
||||
@@ -166,12 +166,12 @@ func (m boardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.notice = "showing " + r.Issue.Identifier + " in every workspace"
|
||||
}
|
||||
return m, nil
|
||||
case "[":
|
||||
case "[", "h":
|
||||
if m.focus == focusNeeds && m.needsTab > 0 {
|
||||
m.needsTab--
|
||||
}
|
||||
return m, nil
|
||||
case "]":
|
||||
case "]", "l":
|
||||
if m.focus == focusNeeds && m.needsTab < 1 {
|
||||
m.needsTab++
|
||||
}
|
||||
@@ -230,12 +230,36 @@ func (m boardModel) View() string {
|
||||
footer := m.footerLine()
|
||||
bodyHeight := m.height - 2
|
||||
|
||||
// Three ways to use the same width, chosen by what the terminal actually
|
||||
// has. Wide screens get the three panels side by side; narrow ones stack.
|
||||
if m.width >= 140 {
|
||||
treeW := max(34, m.width*30/100)
|
||||
sumW := max(38, m.width*38/100)
|
||||
needsW := max(30, m.width-treeW-sumW-2)
|
||||
|
||||
left := panel{title: "tasks", width: treeW, height: bodyHeight, focused: m.focus == focusTree}
|
||||
mid := panel{title: "the chosen task", width: sumW, height: bodyHeight, focused: m.focus == focusSummary}
|
||||
rightP := panel{
|
||||
title: "needs you", width: needsW, height: bodyHeight,
|
||||
tabs: []string{"decisions", "activity"}, active: m.needsTab, focused: m.focus == focusNeeds,
|
||||
}
|
||||
body := m.needsLines(needsW-3, bodyHeight-3)
|
||||
if m.needsTab == 1 {
|
||||
body = m.activityLines(needsW-3, bodyHeight-3)
|
||||
}
|
||||
page := lipgloss.JoinHorizontal(lipgloss.Top,
|
||||
left.render(m.treeLines(treeW-3)), " ",
|
||||
mid.render(m.summaryLines(sumW-3)), " ",
|
||||
rightP.render(body))
|
||||
return strings.Join([]string{header, page, footer}, "\n")
|
||||
}
|
||||
|
||||
leftWidth := max(34, m.width*40/100)
|
||||
rightWidth := max(30, m.width-leftWidth-1)
|
||||
|
||||
// Left: the task tree.
|
||||
tree := panel{title: "tasks", width: leftWidth, height: bodyHeight, focused: m.focus == focusTree}
|
||||
treeLines := m.treeLines(leftWidth - 2)
|
||||
treeLines := m.treeLines(leftWidth - 3)
|
||||
left := tree.render(treeLines)
|
||||
|
||||
// Right, upper: the summary of the chosen task.
|
||||
@@ -247,13 +271,13 @@ func (m boardModel) View() string {
|
||||
title: "needs you", width: rightWidth, height: needsHeight,
|
||||
tabs: []string{"decisions", "activity"}, active: m.needsTab, focused: m.focus == focusNeeds,
|
||||
}
|
||||
needsBody := m.needsLines(rightWidth-2, needsHeight-2)
|
||||
needsBody := m.needsLines(rightWidth-3, needsHeight-3)
|
||||
if m.needsTab == 1 {
|
||||
needsBody = m.activityLines(rightWidth-2, needsHeight-2)
|
||||
needsBody = m.activityLines(rightWidth-3, needsHeight-3)
|
||||
}
|
||||
|
||||
right := lipgloss.JoinVertical(lipgloss.Left,
|
||||
sum.render(m.summaryLines(rightWidth-2)),
|
||||
sum.render(m.summaryLines(rightWidth-3)),
|
||||
needs.render(needsBody),
|
||||
)
|
||||
|
||||
@@ -286,7 +310,7 @@ func (m boardModel) footerLine() string {
|
||||
switch m.focus {
|
||||
case focusTree:
|
||||
return hintLine([]string{
|
||||
keyHint("↑↓", "choose a task"),
|
||||
keyHint("↑↓ or j k", "choose a task"),
|
||||
keyHint("enter", "show it everywhere"),
|
||||
keyHint("a", "assign"),
|
||||
keyHint("tab", "next panel"),
|
||||
@@ -295,7 +319,7 @@ func (m boardModel) footerLine() string {
|
||||
}, m.width)
|
||||
case focusSummary:
|
||||
return hintLine([]string{
|
||||
keyHint("↑↓", "choose a task"),
|
||||
keyHint("↑↓ or j k", "choose a task"),
|
||||
keyHint("tab", "next panel"),
|
||||
keyHint("r", "read again"),
|
||||
keyHint("q", "leave"),
|
||||
@@ -328,7 +352,7 @@ func (m boardModel) treeLines(width int) []string {
|
||||
if len(group) == 0 {
|
||||
continue
|
||||
}
|
||||
out = append(out, groupHeading(fmt.Sprintf("%s %d", strings.ToUpper(g), len(group))))
|
||||
out = append(out, groupHeading(g, len(group), width))
|
||||
for _, r := range group {
|
||||
out = append(out, m.taskLine(r, index, width)...)
|
||||
index++
|
||||
@@ -338,8 +362,13 @@ func (m boardModel) treeLines(width int) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
func groupHeading(s string) string {
|
||||
return lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(active.Mauve)).Render(s)
|
||||
// groupHeading is a label, a rule, and how many tasks are under it. The rule
|
||||
// makes the groups visible at a glance without a box around every one.
|
||||
func groupHeading(name string, count, width int) string {
|
||||
label := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(active.Mauve)).Render(strings.ToUpper(name))
|
||||
n := lipgloss.NewStyle().Foreground(lipgloss.Color(active.Muted)).Render(fmt.Sprintf(" %d ", count))
|
||||
rule := strings.Repeat("─", max(0, width-lipgloss.Width(label)-lipgloss.Width(n)))
|
||||
return label + dimStyle().Render(rule) + n
|
||||
}
|
||||
|
||||
func (m boardModel) taskLine(r Row, index, width int) []string {
|
||||
|
||||
Reference in New Issue
Block a user