11
This commit is contained in:
1 parent
e6f1776d4f
commit
495e961e53
6 files changed
+966
-40
No files matched your search
+193
-14
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/charmbracelet/x/ansi"
|
||||
@@ -117,7 +118,7 @@ var (
|
||||
|
||||
// detailBody builds the two stacked blocks (header + STATE JSON table) shown for
|
||||
// an issue, sized to the given outer width.
|
||||
func detailBody(iss issue.Issue, width int) string {
|
||||
func (m Model) detailBody(iss issue.Issue, width int) string {
|
||||
box := lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(detailBorderColor).
|
||||
@@ -144,9 +145,8 @@ func detailBody(iss issue.Issue, width int) string {
|
||||
}
|
||||
header := lipgloss.JoinVertical(lipgloss.Left,
|
||||
cell(title, false),
|
||||
cell("", false),
|
||||
cell(hint.Render("[b] 头脑风暴 [i] 实施 [v] 审查 [t] 测试 [p] PR提交 [g] PR摘要"), false),
|
||||
cell(hint.Render("[f] 配置 [F] 测试配置 [o] 打开文件 [c] 关闭 [d] 删除 esc 返回"), false),
|
||||
cell(hint.Render("↑↓/jk 选择 ←→/hl 列 H/L 移动 c 关闭 d 删除 p 前置 P 清前置 n 新建"), false),
|
||||
cell(m.actionBarView(), false),
|
||||
)
|
||||
headerBox := box.Render(header)
|
||||
|
||||
@@ -178,22 +178,201 @@ func detailBody(iss issue.Issue, width int) string {
|
||||
return lipgloss.JoinVertical(lipgloss.Left, headerBox, tableBox)
|
||||
}
|
||||
|
||||
// detailView renders the issue detail as a centered modal (two stacked blocks)
|
||||
// floating over the board via lipgloss v2 layer compositing.
|
||||
// detailView renders the board above a bottom issue property panel, matching
|
||||
// the VSCode extension's kanban + attributes split.
|
||||
func (m Model) detailView() string {
|
||||
bg := m.boardView()
|
||||
if m.detailIss == nil {
|
||||
return bg
|
||||
iss, ok := m.currentDetailIssue()
|
||||
if !ok {
|
||||
return m.boardView()
|
||||
}
|
||||
|
||||
width := 84
|
||||
if m.width > 0 && m.width-8 < width {
|
||||
width = m.width - 8
|
||||
return m.boardWithDetailView(iss)
|
||||
}
|
||||
|
||||
func (m Model) boardWithDetailView(iss issue.Issue) string {
|
||||
width := m.width - 2
|
||||
if m.width <= 0 {
|
||||
width = 100
|
||||
}
|
||||
if width < 40 {
|
||||
width = 40
|
||||
}
|
||||
|
||||
modal := detailBody(*m.detailIss, width)
|
||||
return placeOverlayCenter(bg, modal)
|
||||
panel := m.bottomTabsPanel(iss, width)
|
||||
panelH := lipgloss.Height(panel)
|
||||
|
||||
statusLine := ""
|
||||
indicator := ""
|
||||
if m.hasChanges {
|
||||
indicator = " " + confirmStyle.Render("● 未提交")
|
||||
}
|
||||
if status := m.statusBar(); status != "" {
|
||||
statusLine = status + indicator
|
||||
} else if indicator != "" {
|
||||
statusLine = indicator
|
||||
}
|
||||
|
||||
reserved := panelH + 1
|
||||
if statusLine != "" {
|
||||
reserved++
|
||||
}
|
||||
boardH := m.height - reserved
|
||||
if m.height == 0 {
|
||||
boardH = 12
|
||||
}
|
||||
if boardH < 5 {
|
||||
boardH = 5
|
||||
}
|
||||
|
||||
parts := []string{m.boardOnlyView(boardH)}
|
||||
if statusLine != "" {
|
||||
parts = append(parts, statusLine)
|
||||
}
|
||||
parts = append(parts, panel)
|
||||
return lipgloss.JoinVertical(lipgloss.Left, parts...)
|
||||
}
|
||||
|
||||
func (m Model) bottomTabsPanel(iss issue.Issue, width int) string {
|
||||
tabs := m.bottomTabsHeader(width)
|
||||
content := ""
|
||||
switch m.bottomTab {
|
||||
case bottomTabProfile:
|
||||
content = m.profileTabBody(width)
|
||||
case bottomTabSessions:
|
||||
content = m.sessionsTabBody(width)
|
||||
case bottomTabCommits:
|
||||
content = m.commitsTabBody(width)
|
||||
default:
|
||||
content = m.detailBody(iss, width)
|
||||
}
|
||||
return lipgloss.JoinVertical(lipgloss.Left, tabs, content)
|
||||
}
|
||||
|
||||
func (m Model) bottomTabsHeader(width int) string {
|
||||
labels := []struct {
|
||||
tab bottomTab
|
||||
label string
|
||||
}{
|
||||
{bottomTabIssue, "1 工单"},
|
||||
{bottomTabProfile, "2 Profile"},
|
||||
{bottomTabSessions, "3 会话"},
|
||||
{bottomTabCommits, "4 提交"},
|
||||
}
|
||||
var parts []string
|
||||
for _, item := range labels {
|
||||
prefix := " "
|
||||
if m.focusPane == focusPanel && m.bottomTab == item.tab {
|
||||
prefix = "▸"
|
||||
}
|
||||
text := prefix + item.label + " "
|
||||
if m.bottomTab == item.tab {
|
||||
parts = append(parts, selectedCardStyle.Render(text))
|
||||
} else {
|
||||
parts = append(parts, helpStyle.Render(text))
|
||||
}
|
||||
}
|
||||
line := strings.Join(parts, " ")
|
||||
hint := helpStyle.Render(" Tab 切焦点;面板聚焦后 ↑↓/←→ 切 tab;点击区域也可切焦点")
|
||||
if lipgloss.Width(line)+lipgloss.Width(hint) < width {
|
||||
line += hint
|
||||
}
|
||||
return lipgloss.NewStyle().Width(width).Render(line)
|
||||
}
|
||||
|
||||
func (m Model) tabBox(title string, lines []string, width int) string {
|
||||
box := lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(detailBorderColor).
|
||||
Width(width)
|
||||
titleLine := lipgloss.NewStyle().Bold(true).Render(title)
|
||||
body := append([]string{titleLine, ""}, lines...)
|
||||
return box.Render(lipgloss.JoinVertical(lipgloss.Left, body...))
|
||||
}
|
||||
|
||||
func (m Model) profileTabBody(width int) string {
|
||||
lines := []string{
|
||||
helpStyle.Render("只读摘要;按 G 打开 Profile 表格编辑器"),
|
||||
}
|
||||
if len(m.profileGridData.Profiles) == 0 {
|
||||
lines = append(lines, "未加载 Profile 数据,按 2 重新加载")
|
||||
return m.tabBox("Profile", lines, width)
|
||||
}
|
||||
lines = append(lines, "Profiles: "+strings.Join(m.profileGridData.Profiles, ", "))
|
||||
if len(m.profileGridData.Rows) == 0 {
|
||||
lines = append(lines, "暂无配置行")
|
||||
return m.tabBox("Profile", lines, width)
|
||||
}
|
||||
maxRows := 8
|
||||
for i, row := range m.profileGridData.Rows {
|
||||
if i >= maxRows {
|
||||
lines = append(lines, fmt.Sprintf("... 还有 %d 行", len(m.profileGridData.Rows)-maxRows))
|
||||
break
|
||||
}
|
||||
var vals []string
|
||||
for _, prof := range m.profileGridData.Profiles {
|
||||
vals = append(vals, fmt.Sprintf("%s=%s", prof, orDash(row.Values[prof])))
|
||||
}
|
||||
lines = append(lines, truncate(row.Key+" "+strings.Join(vals, " "), width-6))
|
||||
}
|
||||
return m.tabBox("Profile", lines, width)
|
||||
}
|
||||
|
||||
func (m Model) sessionsTabBody(width int) string {
|
||||
lines := []string{
|
||||
helpStyle.Render("只读摘要;按 m 打开会话管理器"),
|
||||
}
|
||||
if len(m.managedSessions.Sessions) == 0 {
|
||||
lines = append(lines, "暂无托管会话")
|
||||
return m.tabBox("会话", lines, width)
|
||||
}
|
||||
maxRows := 8
|
||||
for i, sess := range m.managedSessions.Sessions {
|
||||
if i >= maxRows {
|
||||
lines = append(lines, fmt.Sprintf("... 还有 %d 个会话", len(m.managedSessions.Sessions)-maxRows))
|
||||
break
|
||||
}
|
||||
ts := time.Unix(sess.CreatedAt, 0).Format("2006-01-02 15:04")
|
||||
id := sess.ID
|
||||
if len(id) > 12 {
|
||||
id = id[:12]
|
||||
}
|
||||
lines = append(lines, truncate(fmt.Sprintf("%s %s %s %s", sess.Name, id, profileDisplay(sess.ProfilePath), ts), width-6))
|
||||
}
|
||||
return m.tabBox("会话", lines, width)
|
||||
}
|
||||
|
||||
func (m Model) commitsTabBody(width int) string {
|
||||
lines := []string{
|
||||
helpStyle.Render("只读摘要;按 R 打开 PR 提交确认面板"),
|
||||
}
|
||||
if m.prCommitsIss == nil || m.prCommitsIss.PR == "" {
|
||||
lines = append(lines, "当前工单无关联 PR")
|
||||
return m.tabBox("提交", lines, width)
|
||||
}
|
||||
if m.prCommitsErr != nil {
|
||||
lines = append(lines, errorStyle.Render(m.prCommitsErr.Error()))
|
||||
return m.tabBox(fmt.Sprintf("PR #%s 提交", m.prCommitsIss.PR), lines, width)
|
||||
}
|
||||
if !m.prCommitsLoaded {
|
||||
lines = append(lines, "加载中...")
|
||||
return m.tabBox(fmt.Sprintf("PR #%s 提交", m.prCommitsIss.PR), lines, width)
|
||||
}
|
||||
maxCommits := 5
|
||||
lines = append(lines, fmt.Sprintf("Commits (%d):", len(m.prCommits)))
|
||||
for i, c := range m.prCommits {
|
||||
if i >= maxCommits {
|
||||
lines = append(lines, fmt.Sprintf("... 还有 %d 个 commit", len(m.prCommits)-maxCommits))
|
||||
break
|
||||
}
|
||||
mark := " "
|
||||
if m.prConfirmedSHAs[c.SHA] {
|
||||
mark = "x"
|
||||
}
|
||||
lines = append(lines, truncate(fmt.Sprintf("[%s] %s %s", mark, shortSHA(c.SHA), firstLine(c.Commit.Message)), width-6))
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf("Files: %d", len(m.prFiles)))
|
||||
if allFilesConfirmed(m.prFiles, m.prConfirmed) && len(m.prFiles) > 0 {
|
||||
lines = append(lines, confirmStyle.Render("所有文件已确认"))
|
||||
}
|
||||
return m.tabBox(fmt.Sprintf("PR #%s 提交", m.prCommitsIss.PR), lines, width)
|
||||
}
|
||||
Reference in new issue
Block a user