add mascot
This commit is contained in:
294
tui.go
294
tui.go
@@ -137,6 +137,8 @@ type App struct {
|
||||
listHidden bool
|
||||
scroll int
|
||||
width, height int
|
||||
contentTop int
|
||||
headerWidth int
|
||||
loading bool
|
||||
secondaryLoading bool
|
||||
err error
|
||||
@@ -201,6 +203,7 @@ type App struct {
|
||||
aiProgress AIRunProgress
|
||||
aiSpinner int
|
||||
aiEvents <-chan tea.Msg
|
||||
difflet diffletModel
|
||||
}
|
||||
|
||||
type AppSettings struct {
|
||||
@@ -219,6 +222,9 @@ type AppSettings struct {
|
||||
Drafts *draftStore
|
||||
AI *AIController
|
||||
AIStore *AIStore
|
||||
Mascot bool
|
||||
MascotExpressive bool
|
||||
MascotAnimated bool
|
||||
}
|
||||
|
||||
func defaultAppSettings() AppSettings {
|
||||
@@ -252,6 +258,8 @@ func NewAppWithSettings(
|
||||
if state == nil {
|
||||
state = &readStateStore{Data: make(map[string]readPRState)}
|
||||
}
|
||||
difflet := newDifflet(settings.Mascot, settings.MascotExpressive, settings.MascotAnimated)
|
||||
difflet.visible = false
|
||||
return App{
|
||||
service: service, owner: owner, repo: repo, showAll: showAll, limit: limit, poll: poll,
|
||||
folded: make(map[string]bool), loading: true,
|
||||
@@ -273,6 +281,7 @@ func NewAppWithSettings(
|
||||
unreadComments: make(map[string]bool), newThreads: make(map[string]bool),
|
||||
updatedThreads: make(map[string]bool),
|
||||
ai: settings.AI, aiStore: settings.AIStore,
|
||||
difflet: difflet,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,6 +290,9 @@ func (m App) Init() tea.Cmd {
|
||||
if m.pathScroll {
|
||||
commands = append(commands, m.nextPathTick())
|
||||
}
|
||||
if command := m.difflet.start(); command != nil {
|
||||
commands = append(commands, command)
|
||||
}
|
||||
return tea.Batch(commands...)
|
||||
}
|
||||
|
||||
@@ -599,7 +611,7 @@ func (m App) updateWriteInput(key tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch k {
|
||||
case "y":
|
||||
m.writeMode = writeReplyBusy
|
||||
return m, m.submitReply()
|
||||
return m, tea.Batch(m.submitReply(), m.difflet.setState(diffletLoading))
|
||||
case "n", "esc":
|
||||
m.writeMode = writeReply
|
||||
m.scroll = m.detailMaxScroll()
|
||||
@@ -608,7 +620,7 @@ func (m App) updateWriteInput(key tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch k {
|
||||
case "y":
|
||||
m.writeMode = writeResolveBusy
|
||||
return m, m.submitResolution()
|
||||
return m, tea.Batch(m.submitResolution(), m.difflet.setState(diffletLoading))
|
||||
case "n", "esc":
|
||||
m.writeMode, m.writeThreadID = writeNone, ""
|
||||
}
|
||||
@@ -616,7 +628,7 @@ func (m App) updateWriteInput(key tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch k {
|
||||
case "y":
|
||||
m.writeMode = writeAutoMergeBusy
|
||||
return m, m.submitAutoMerge()
|
||||
return m, tea.Batch(m.submitAutoMerge(), m.difflet.setState(diffletLoading))
|
||||
case "n", "esc":
|
||||
m.writeMode = writeNone
|
||||
}
|
||||
@@ -624,7 +636,7 @@ func (m App) updateWriteInput(key tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch k {
|
||||
case "y":
|
||||
m.writeMode = writeMergeNowBusy
|
||||
return m, m.submitMergeNow()
|
||||
return m, tea.Batch(m.submitMergeNow(), m.difflet.setState(diffletLoading))
|
||||
case "n", "esc":
|
||||
m.writeMode = writeNone
|
||||
}
|
||||
@@ -688,6 +700,9 @@ func (m App) submitMergeNow() tea.Cmd {
|
||||
}
|
||||
|
||||
func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if tick, ok := msg.(diffletTickMsg); ok {
|
||||
return m, m.difflet.update(tick)
|
||||
}
|
||||
if m.aiMode != aiNone {
|
||||
if updated, command, handled := m.updateAI(msg); handled {
|
||||
return updated, command
|
||||
@@ -696,17 +711,22 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width, m.height = msg.Width, msg.Height
|
||||
return m, m.difflet.setVisible(
|
||||
m.width >= diffletWidth &&
|
||||
m.height >= diffletHeight+4,
|
||||
)
|
||||
case tickMsg:
|
||||
if !m.loading {
|
||||
m.loading = true
|
||||
diffletCommand := m.difflet.setState(diffletLoading)
|
||||
targetScreen := m.screen
|
||||
if targetScreen == healthScreen {
|
||||
targetScreen = m.healthReturn
|
||||
}
|
||||
if (targetScreen == dashboardScreen || targetScreen == threadScreen) && m.details.Number != 0 {
|
||||
return m, tea.Batch(m.loadDetails(m.details.PullRequest, false), m.nextTick())
|
||||
return m, tea.Batch(m.loadDetails(m.details.PullRequest, false), m.nextTick(), diffletCommand)
|
||||
}
|
||||
return m, tea.Batch(m.loadPRs(false), m.nextTick())
|
||||
return m, tea.Batch(m.loadPRs(false), m.nextTick(), diffletCommand)
|
||||
}
|
||||
return m, m.nextTick()
|
||||
case pathTickMsg:
|
||||
@@ -734,8 +754,9 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
m.err = msg.err
|
||||
command := m.difflet.setState(diffletSad)
|
||||
m.recordHealth("pull request list", healthError, msg.err.Error())
|
||||
return m, nil
|
||||
return m, command
|
||||
}
|
||||
selected := ""
|
||||
if len(m.prs) > 0 && m.prIndex < len(m.prs) {
|
||||
@@ -756,6 +777,10 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
} else {
|
||||
m.lastRefresh = time.Now()
|
||||
}
|
||||
if len(m.prs) == 0 {
|
||||
return m, m.difflet.setState(diffletSleeping)
|
||||
}
|
||||
return m, m.difflet.setState(diffletIdle)
|
||||
case detailsLoadedMsg:
|
||||
if m.requests != nil && !m.requests.current(msg.requestID) {
|
||||
return m, nil
|
||||
@@ -781,8 +806,9 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
m.err = msg.err
|
||||
command := m.difflet.setState(diffletSad)
|
||||
m.recordHealth("PR refresh", healthError, msg.err.Error())
|
||||
return m, nil
|
||||
return m, command
|
||||
}
|
||||
selected := ""
|
||||
anchor := ""
|
||||
@@ -801,6 +827,11 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.trackThreadUpdates(msg.details)
|
||||
sortReviewThreads(msg.details.Threads, m.threadStatusOrder, m.threadWithinStatus)
|
||||
m.details = msg.details
|
||||
m.err = nil
|
||||
diffletCommand := m.difflet.setState(m.restingDiffletState())
|
||||
if len(m.updatedThreads) > 0 {
|
||||
diffletCommand = m.difflet.setState(diffletNewComment)
|
||||
}
|
||||
for _, issue := range msg.details.DataIssues {
|
||||
m.recordHealth(issue.Component, healthWarning, issue.Message)
|
||||
}
|
||||
@@ -832,9 +863,11 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.lastRefresh = time.Now()
|
||||
if _, ok := m.service.(GitHubEnrichmentService); ok {
|
||||
m.secondaryLoading = true
|
||||
return m, m.loadDetailsEnrichment(msg.details)
|
||||
diffletCommand = m.difflet.setState(diffletLoading)
|
||||
return m, tea.Batch(m.loadDetailsEnrichment(msg.details), diffletCommand)
|
||||
}
|
||||
}
|
||||
return m, diffletCommand
|
||||
case detailsEnrichedMsg:
|
||||
if m.requests != nil && !m.requests.current(msg.requestID) {
|
||||
return m, nil
|
||||
@@ -866,6 +899,7 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
m.recordHealth(issue.Component, healthWarning, issue.Message)
|
||||
}
|
||||
return m, m.difflet.setState(m.restingDiffletState())
|
||||
case draftFlushMsg:
|
||||
if msg.err != nil {
|
||||
m.recordHealth("draft persistence", healthWarning, msg.err.Error())
|
||||
@@ -907,7 +941,7 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if msg.err != nil {
|
||||
m.err = fmt.Errorf("change thread resolution: %w", msg.err)
|
||||
m.recordHealth("thread resolution", healthError, msg.err.Error())
|
||||
return m, nil
|
||||
return m, m.difflet.setState(diffletRecoverableError)
|
||||
}
|
||||
selected := ""
|
||||
if m.threadIndex >= 0 && m.threadIndex < len(m.details.Threads) {
|
||||
@@ -931,13 +965,14 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.writeThreadID = ""
|
||||
m.err = nil
|
||||
m.lastRefresh = time.Now()
|
||||
return m, m.difflet.setState(diffletSuccess)
|
||||
case threadRepliedMsg:
|
||||
if msg.err != nil {
|
||||
m.writeMode = writeReply
|
||||
m.err = fmt.Errorf("reply to review thread: %w", msg.err)
|
||||
m.recordHealth("thread reply", healthError, msg.err.Error())
|
||||
m.scroll = m.detailMaxScroll()
|
||||
return m, nil
|
||||
return m, m.difflet.setState(diffletRecoverableError)
|
||||
}
|
||||
m.writeMode = writeNone
|
||||
for index := range m.details.Threads {
|
||||
@@ -957,12 +992,13 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.replyDraft, m.writeThreadID = "", ""
|
||||
m.err = nil
|
||||
m.lastRefresh = time.Now()
|
||||
return m, m.difflet.setState(diffletSuccess)
|
||||
case autoMergeUpdatedMsg:
|
||||
m.writeMode = writeNone
|
||||
if msg.err != nil {
|
||||
m.err = fmt.Errorf("change auto-merge: %w", msg.err)
|
||||
m.recordHealth("auto-merge", healthError, msg.err.Error())
|
||||
return m, nil
|
||||
return m, m.difflet.setState(diffletRecoverableError)
|
||||
}
|
||||
m.err = nil
|
||||
if msg.enabled {
|
||||
@@ -975,12 +1011,13 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.details.Permissions.CanDisableMerge = false
|
||||
}
|
||||
m.lastRefresh = time.Now()
|
||||
return m, m.difflet.setState(diffletSuccess)
|
||||
case pullRequestMergedMsg:
|
||||
m.writeMode = writeNone
|
||||
if msg.err != nil {
|
||||
m.err = fmt.Errorf("merge pull request: %w", msg.err)
|
||||
m.recordHealth("merge pull request", healthError, msg.err.Error())
|
||||
return m, nil
|
||||
return m, m.difflet.setState(diffletRecoverableError)
|
||||
}
|
||||
m.err = nil
|
||||
m.details.Merged = msg.result.Merged
|
||||
@@ -988,6 +1025,7 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.details.State = "MERGED"
|
||||
m.details.AutoMerge = nil
|
||||
m.lastRefresh = time.Now()
|
||||
return m, m.difflet.setState(diffletSuccess)
|
||||
case pullRequestUpdatedMsg:
|
||||
if msg.err != nil {
|
||||
m.writeMode = writePREdit
|
||||
@@ -999,7 +1037,7 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.err = fmt.Errorf("update pull request: %w", msg.err)
|
||||
m.recordHealth("PR metadata update", healthError, msg.err.Error())
|
||||
m.scroll = 0
|
||||
return m, nil
|
||||
return m, m.difflet.setState(diffletRecoverableError)
|
||||
}
|
||||
m.writeMode = writeNone
|
||||
m.details.Title = msg.metadata.Title
|
||||
@@ -1031,7 +1069,7 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.err = nil
|
||||
m.lastRefresh = time.Now()
|
||||
m.loading = true
|
||||
return m, m.loadDetails(m.details.PullRequest, false)
|
||||
return m, tea.Batch(m.loadDetails(m.details.PullRequest, false), m.difflet.setState(diffletLoading))
|
||||
}
|
||||
|
||||
key, ok := msg.(tea.KeyMsg)
|
||||
@@ -1172,14 +1210,15 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
m.loading = true
|
||||
diffletCommand := m.difflet.setState(diffletLoading)
|
||||
targetScreen := m.screen
|
||||
if targetScreen == healthScreen {
|
||||
targetScreen = m.healthReturn
|
||||
}
|
||||
if targetScreen == dashboardScreen || targetScreen == threadScreen {
|
||||
return m, m.loadDetails(m.details.PullRequest, false)
|
||||
return m, tea.Batch(m.loadDetails(m.details.PullRequest, false), diffletCommand)
|
||||
}
|
||||
return m, m.loadPRs(false)
|
||||
return m, tea.Batch(m.loadPRs(false), diffletCommand)
|
||||
case "j", "down":
|
||||
if m.screen == dashboardScreen {
|
||||
m.scrollDashboard(1)
|
||||
@@ -1282,14 +1321,14 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
m.screen, m.err, m.loading = prScreen, nil, true
|
||||
return m, m.loadPRs(false)
|
||||
return m, tea.Batch(m.loadPRs(false), m.difflet.setState(diffletLoading))
|
||||
}
|
||||
if m.screen == dashboardScreen {
|
||||
m.screen, m.scroll, m.err = m.dashboardReturn, 0, nil
|
||||
if m.screen == prScreen {
|
||||
m.loading = true
|
||||
m.searching, m.searchQuery = false, ""
|
||||
return m, m.loadPRs(false)
|
||||
return m, tea.Batch(m.loadPRs(false), m.difflet.setState(diffletLoading))
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@@ -1382,7 +1421,7 @@ func (m *App) openSelectedPR(target screen) tea.Cmd {
|
||||
m.threadIndex, m.scroll, m.focus, m.listHidden, m.loading, m.err = 0, 0, threadListPane, false, true, nil
|
||||
m.searching, m.searchQuery = false, ""
|
||||
m.pathScrollStep = 0
|
||||
return m.loadDetails(m.details.PullRequest, true)
|
||||
return tea.Batch(m.loadDetails(m.details.PullRequest, true), m.difflet.setState(diffletLoading))
|
||||
}
|
||||
|
||||
func (m *App) trackThreadUpdates(details PRDetails) {
|
||||
@@ -1849,11 +1888,8 @@ func (m App) detailPaneSize() (int, int) {
|
||||
}
|
||||
|
||||
func (m App) threadTopLineCount() int {
|
||||
topLines := 3
|
||||
if m.details.FromCache {
|
||||
topLines++
|
||||
}
|
||||
if m.details.ThreadsTruncated {
|
||||
topLines := len(m.threadTopLines())
|
||||
if m.headerWidth > 0 {
|
||||
topLines++
|
||||
}
|
||||
return topLines
|
||||
@@ -1884,6 +1920,117 @@ func (m App) View() string {
|
||||
if m.width == 0 {
|
||||
return "Loading…"
|
||||
}
|
||||
mascot := m.difflet.frameLines()
|
||||
if len(mascot) != diffletHeight {
|
||||
return m.viewContent()
|
||||
}
|
||||
gap := diffletGap
|
||||
headerWidth := diffletHeaderWidth(m.width)
|
||||
if headerWidth < 1 {
|
||||
headerWidth = m.width
|
||||
}
|
||||
content := m
|
||||
content.headerWidth = headerWidth
|
||||
headerLineCount, hasHeader := content.diffletHeaderLineCount()
|
||||
if !hasHeader {
|
||||
content.height = max(1, m.height-diffletHeight-1)
|
||||
content.contentTop = diffletHeight + 1
|
||||
return lipgloss.NewStyle().Width(m.width).Height(m.height).Render(
|
||||
strings.Join(mascot, "\n") + "\n\n" + content.viewContent(),
|
||||
)
|
||||
}
|
||||
bandHeight := max(diffletHeight, headerLineCount)
|
||||
addedRows := bandHeight - headerLineCount
|
||||
if m.width <= diffletWidth {
|
||||
addedRows = diffletHeight
|
||||
}
|
||||
content.height = max(1, m.height-addedRows)
|
||||
content.contentTop = addedRows
|
||||
rendered := content.viewContent()
|
||||
header, body, ok := splitHeader(rendered)
|
||||
if !ok {
|
||||
return lipgloss.NewStyle().Width(m.width).Height(m.height).Render(
|
||||
strings.Join(mascot, "\n") + "\n\n" + rendered,
|
||||
)
|
||||
}
|
||||
headerBand := renderHeaderWithDifflet(header, mascot, m.width, headerWidth, gap)
|
||||
return lipgloss.NewStyle().Width(m.width).Height(m.height).Render(
|
||||
headerBand + "\n\n" + body,
|
||||
)
|
||||
}
|
||||
|
||||
func (m App) diffletHeaderLineCount() (int, bool) {
|
||||
if m.helpVisible ||
|
||||
(m.aiMode != aiNone && m.aiMode != aiDiscussion) ||
|
||||
(m.writeMode != writeNone && m.writeMode != writeReply && m.writeMode != writePREdit) ||
|
||||
m.screen == healthScreen {
|
||||
return 0, false
|
||||
}
|
||||
switch m.screen {
|
||||
case prScreen:
|
||||
return len(m.prHeaderLines()), true
|
||||
case dashboardScreen:
|
||||
if m.scroll > 0 {
|
||||
return 0, false
|
||||
}
|
||||
if m.writeMode == writePREdit {
|
||||
return 1, true
|
||||
}
|
||||
return len(m.dashboardHeaderLines()), true
|
||||
case threadScreen:
|
||||
return len(m.threadTopLines()), true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
func splitHeader(view string) (header []string, body string, ok bool) {
|
||||
lines := strings.Split(view, "\n")
|
||||
for index, line := range lines {
|
||||
if strings.TrimSpace(ansi.Strip(line)) == "" {
|
||||
if index == 0 {
|
||||
return nil, view, false
|
||||
}
|
||||
return lines[:index], strings.Join(lines[index+1:], "\n"), true
|
||||
}
|
||||
}
|
||||
return nil, view, false
|
||||
}
|
||||
|
||||
func renderHeaderWithDifflet(
|
||||
header, mascot []string,
|
||||
width, headerWidth, gap int,
|
||||
) string {
|
||||
if width <= diffletWidth {
|
||||
lines := append([]string(nil), mascot...)
|
||||
lines = append(lines, header...)
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
height := max(len(header), len(mascot))
|
||||
headerText := strings.Join(header, "\n")
|
||||
left := lipgloss.NewStyle().Width(headerWidth).Height(height).Render(headerText)
|
||||
right := lipgloss.NewStyle().Width(diffletWidth).Height(height).Render(strings.Join(mascot, "\n"))
|
||||
return lipgloss.JoinHorizontal(
|
||||
lipgloss.Top,
|
||||
left,
|
||||
strings.Repeat(" ", gap),
|
||||
right,
|
||||
)
|
||||
}
|
||||
|
||||
func (m App) wrapHeaderLines(lines []string) []string {
|
||||
if m.headerWidth < 1 {
|
||||
return lines
|
||||
}
|
||||
var wrapped []string
|
||||
for _, line := range lines {
|
||||
value := ansi.Hardwrap(ansi.Wordwrap(line, m.headerWidth, ""), m.headerWidth, false)
|
||||
wrapped = append(wrapped, strings.Split(value, "\n")...)
|
||||
}
|
||||
return wrapped
|
||||
}
|
||||
|
||||
func (m App) viewContent() string {
|
||||
if m.helpVisible {
|
||||
return m.viewHelp()
|
||||
}
|
||||
@@ -2326,16 +2473,7 @@ func paneStyle(active bool) lipgloss.Style {
|
||||
}
|
||||
|
||||
func (m App) viewPRs() string {
|
||||
header := titleStyle.Render("diple")
|
||||
if m.owner != "" {
|
||||
header += " " + m.owner + "/" + m.repo
|
||||
}
|
||||
if m.showAll {
|
||||
header += dimStyle.Render(" all open pull requests")
|
||||
} else {
|
||||
header += dimStyle.Render(" assigned to you")
|
||||
}
|
||||
lines := []string{header, ""}
|
||||
lines := append(m.prHeaderLines(), "")
|
||||
if m.loading && len(m.prs) == 0 {
|
||||
lines = append(lines, "Loading open pull requests…")
|
||||
} else if len(m.prs) == 0 && m.err == nil {
|
||||
@@ -2384,6 +2522,19 @@ func (m App) viewPRs() string {
|
||||
return m.frame(lines, footer)
|
||||
}
|
||||
|
||||
func (m App) prHeaderLines() []string {
|
||||
header := titleStyle.Render("diple")
|
||||
if m.owner != "" {
|
||||
header += " " + m.owner + "/" + m.repo
|
||||
}
|
||||
if m.showAll {
|
||||
header += dimStyle.Render(" all open pull requests")
|
||||
} else {
|
||||
header += dimStyle.Render(" assigned to you")
|
||||
}
|
||||
return m.wrapHeaderLines([]string{header})
|
||||
}
|
||||
|
||||
type prListRow struct {
|
||||
repository string
|
||||
prIndex int
|
||||
@@ -2669,10 +2820,7 @@ func (m App) healthLines() []string {
|
||||
return lines
|
||||
}
|
||||
|
||||
func (m App) dashboardLines() []string {
|
||||
if m.writeMode == writePREdit {
|
||||
return m.dashboardEditLines()
|
||||
}
|
||||
func (m App) dashboardHeaderLines() []string {
|
||||
pr := m.details
|
||||
width := max(10, m.width-2)
|
||||
draft := ""
|
||||
@@ -2684,7 +2832,9 @@ func (m App) dashboardLines() []string {
|
||||
titleStyle.Render(truncate(pr.Title, width)),
|
||||
}
|
||||
if pr.FromCache {
|
||||
lines = append(lines, warnStyle.Render("OFFLINE CACHE • saved "+pr.CachedAt.Local().Format("2006-01-02 15:04")))
|
||||
lines = append(lines, warnStyle.Render(
|
||||
"OFFLINE CACHE • saved "+pr.CachedAt.Local().Format("2006-01-02 15:04"),
|
||||
))
|
||||
}
|
||||
if len(pr.DataIssues) > 0 {
|
||||
lines = append(lines, warnStyle.Render(fmt.Sprintf(
|
||||
@@ -2692,6 +2842,16 @@ func (m App) dashboardLines() []string {
|
||||
len(pr.DataIssues), primaryKeyLabel(m.keybindings.Views.Health),
|
||||
)))
|
||||
}
|
||||
return m.wrapHeaderLines(lines)
|
||||
}
|
||||
|
||||
func (m App) dashboardLines() []string {
|
||||
if m.writeMode == writePREdit {
|
||||
return m.dashboardEditLines()
|
||||
}
|
||||
pr := m.details
|
||||
width := max(10, m.width-2)
|
||||
lines := m.dashboardHeaderLines()
|
||||
if m.loading && pr.BaseRef == "" {
|
||||
return append(lines, "", "Loading pull request details…")
|
||||
}
|
||||
@@ -3100,9 +3260,13 @@ func viewerPermissionsText(permissions ViewerPermissions) string {
|
||||
return strings.Join(items, ", ")
|
||||
}
|
||||
|
||||
func (m App) viewThreads() string {
|
||||
func (m App) threadTopLines() []string {
|
||||
pr := m.details
|
||||
header := titleStyle.Render(fmt.Sprintf("%s #%d %s", pr.RepoWithOwner, pr.Number, truncate(pr.Title, max(10, m.width-len(pr.RepoWithOwner)-12))))
|
||||
width := m.width
|
||||
if m.headerWidth > 0 {
|
||||
width = m.headerWidth
|
||||
}
|
||||
header := titleStyle.Render(fmt.Sprintf("%s #%d %s", pr.RepoWithOwner, pr.Number, truncate(pr.Title, max(10, width-len(pr.RepoWithOwner)-12))))
|
||||
meta := fmt.Sprintf("%s → %s checks: %s %s", pr.HeadRef, pr.BaseRef, coloredState(pr.CheckState), reviewAndMergeState(pr))
|
||||
people := "assignees: " + m.handlesText(pr.Assignees) + " reviewers: " + m.reviewersText(pr.Reviewers)
|
||||
top := []string{header, meta, people}
|
||||
@@ -3115,8 +3279,16 @@ func (m App) viewThreads() string {
|
||||
if pr.ThreadsTruncated {
|
||||
top = append(top, warnStyle.Render("Showing the first 100 review threads."))
|
||||
}
|
||||
return m.wrapHeaderLines(top)
|
||||
}
|
||||
|
||||
contentHeight := max(3, m.height-len(top)-1)
|
||||
func (m App) viewThreads() string {
|
||||
top := m.threadTopLines()
|
||||
topLineCount := len(top)
|
||||
if m.headerWidth > 0 {
|
||||
topLineCount++
|
||||
}
|
||||
contentHeight := max(3, m.height-topLineCount-1)
|
||||
var body string
|
||||
if m.width < 70 {
|
||||
if m.focus == threadListPane {
|
||||
@@ -3167,6 +3339,9 @@ func (m App) viewThreads() string {
|
||||
)
|
||||
}
|
||||
m.positionThreadInputHardwareCursor()
|
||||
if m.headerWidth > 0 {
|
||||
top = append(top, "")
|
||||
}
|
||||
view := m.frame(append(top, body), help)
|
||||
if m.cursorOutput != nil {
|
||||
view += m.cursorOutput.FrameMarker()
|
||||
@@ -3571,7 +3746,7 @@ func (m App) positionThreadInputHardwareCursor() {
|
||||
m.cursorOutput.SetCursor(
|
||||
true,
|
||||
2+ansi.StringWidth("Filter: ")+ansi.StringWidth(query),
|
||||
topLines+3,
|
||||
m.contentTop+topLines+3,
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -3607,7 +3782,7 @@ func (m App) positionThreadInputHardwareCursor() {
|
||||
m.cursorOutput.SetCursor(
|
||||
true,
|
||||
paneStart+2+ansi.StringWidth(line.rail)+ansi.StringWidth(line.fixed+line.text),
|
||||
topLines+2+screenLine,
|
||||
m.contentTop+topLines+2+screenLine,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3902,6 +4077,35 @@ func (m App) frame(lines []string, help string) string {
|
||||
return lipgloss.NewStyle().Width(m.width).Height(m.height).Render(strings.Join(bodyLines, "\n"))
|
||||
}
|
||||
|
||||
func (m App) restingDiffletState() diffletState {
|
||||
if m.err != nil {
|
||||
return diffletSad
|
||||
}
|
||||
if m.loading || m.secondaryLoading {
|
||||
return diffletLoading
|
||||
}
|
||||
if m.details.Number == 0 {
|
||||
if !m.lastRefresh.IsZero() && len(m.prs) == 0 {
|
||||
return diffletSleeping
|
||||
}
|
||||
return diffletIdle
|
||||
}
|
||||
if m.details.ReviewDecision == "APPROVED" {
|
||||
return diffletApproved
|
||||
}
|
||||
open, outdated, resolved := threadStatusCounts(m.details.Threads)
|
||||
if open+outdated > 0 {
|
||||
return diffletConcerned
|
||||
}
|
||||
if resolved > 0 {
|
||||
return diffletHappy
|
||||
}
|
||||
if m.screen == threadScreen {
|
||||
return diffletFocused
|
||||
}
|
||||
return diffletIdle
|
||||
}
|
||||
|
||||
func renderPane(lines []string, width, height int, active bool) string {
|
||||
innerWidth := max(1, width-2)
|
||||
innerHeight := max(1, height-2)
|
||||
|
||||
Reference in New Issue
Block a user