feat(office): mail preview, infinite scroll, pane resize, and scrollbars
Release Please / Release Please (push) Skipped
Tests / Test (Go 1.22) (push) Skipped
Tests / Test (Go stable) (push) Skipped
Release / GoReleaser (push) Failing after 38s
Release Please / Release Please (push) Skipped
Tests / Test (Go 1.22) (push) Skipped
Tests / Test (Go stable) (push) Skipped
Release / GoReleaser (push) Failing after 38s
Polish the office TUI with proportional pane layout, drag resize, live filtering, HTML mail preview, paginated mail lists, overflow scrollbars, flattened navigation, and project save/status fixes; add UpdateProjectStatus. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+112
-4
@@ -15,6 +15,7 @@ type entityField int
|
||||
const (
|
||||
entityFieldPrimary entityField = iota
|
||||
entityFieldSecondary
|
||||
entityFieldStatus
|
||||
)
|
||||
|
||||
// EntityForm is the top section of the detail pane for editable entities.
|
||||
@@ -25,6 +26,9 @@ type EntityForm struct {
|
||||
primaryLabel string
|
||||
secondaryLabel string
|
||||
readOnly bool
|
||||
hasStatus bool
|
||||
status model.ProjectLifecycle
|
||||
responsibleID string
|
||||
primary textinput.Model
|
||||
secondary textarea.Model
|
||||
field entityField
|
||||
@@ -65,6 +69,27 @@ func newEntityForm() EntityForm {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *EntityForm) FieldCount() int {
|
||||
n := 2
|
||||
if f.hasStatus {
|
||||
n = 3
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (f *EntityForm) FormFields() model.FormFields {
|
||||
return model.FormFields{
|
||||
PrimaryLabel: f.primaryLabel,
|
||||
SecondaryLabel: f.secondaryLabel,
|
||||
Primary: f.primary.Value(),
|
||||
Secondary: f.secondary.Value(),
|
||||
ReadOnly: f.readOnly,
|
||||
HasStatus: f.hasStatus,
|
||||
Status: f.status,
|
||||
ResponsibleID: f.responsibleID,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *EntityForm) Active() bool { return f.active }
|
||||
|
||||
func (f *EntityForm) ItemID() string { return f.itemID }
|
||||
@@ -82,10 +107,16 @@ func (f *EntityForm) Load(kind model.Kind, itemID string, fields model.FormField
|
||||
f.primaryLabel = fields.PrimaryLabel
|
||||
f.secondaryLabel = fields.SecondaryLabel
|
||||
f.readOnly = fields.ReadOnly
|
||||
f.hasStatus = fields.HasStatus
|
||||
f.status = fields.Status
|
||||
f.responsibleID = fields.ResponsibleID
|
||||
f.primary.SetValue(fields.Primary)
|
||||
f.secondary.SetValue(fields.Secondary)
|
||||
f.dirty = false
|
||||
f.field = entityFieldPrimary
|
||||
if f.hasStatus && f.readOnly {
|
||||
f.field = entityFieldStatus
|
||||
}
|
||||
f.applyFocus()
|
||||
f.layoutFields()
|
||||
}
|
||||
@@ -93,6 +124,9 @@ func (f *EntityForm) Load(kind model.Kind, itemID string, fields model.FormField
|
||||
func (f *EntityForm) Clear() {
|
||||
f.active = false
|
||||
f.itemID = ""
|
||||
f.hasStatus = false
|
||||
f.status = model.ProjectLifecycleOpen
|
||||
f.responsibleID = ""
|
||||
f.primary.SetValue("")
|
||||
f.secondary.SetValue("")
|
||||
f.dirty = false
|
||||
@@ -140,19 +174,52 @@ func (f *EntityForm) layoutFields() {
|
||||
}
|
||||
|
||||
func (f *EntityForm) FocusNext() {
|
||||
if f.field < entityFieldSecondary {
|
||||
last := entityFieldSecondary
|
||||
if f.hasStatus {
|
||||
last = entityFieldStatus
|
||||
}
|
||||
if f.field < last {
|
||||
f.field++
|
||||
}
|
||||
f.applyFocus()
|
||||
}
|
||||
|
||||
func (f *EntityForm) FocusPrev() {
|
||||
if f.field > entityFieldPrimary {
|
||||
first := entityFieldPrimary
|
||||
if f.field > first {
|
||||
f.field--
|
||||
}
|
||||
f.applyFocus()
|
||||
}
|
||||
|
||||
func (f *EntityForm) SetFieldIndex(i int) {
|
||||
switch i {
|
||||
case 0:
|
||||
f.field = entityFieldPrimary
|
||||
case 1:
|
||||
f.field = entityFieldSecondary
|
||||
case 2:
|
||||
if f.hasStatus {
|
||||
f.field = entityFieldStatus
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
f.applyFocus()
|
||||
}
|
||||
|
||||
func (f *EntityForm) cycleStatus(delta int) {
|
||||
if !f.hasStatus || f.readOnly {
|
||||
return
|
||||
}
|
||||
if delta > 0 {
|
||||
f.status = f.status.Next()
|
||||
} else {
|
||||
f.status = f.status.Prev()
|
||||
}
|
||||
f.dirty = true
|
||||
}
|
||||
|
||||
func (f *EntityForm) applyFocus() {
|
||||
if f.readOnly {
|
||||
f.primary.Blur()
|
||||
@@ -163,9 +230,12 @@ func (f *EntityForm) applyFocus() {
|
||||
case entityFieldPrimary:
|
||||
f.primary.Focus()
|
||||
f.secondary.Blur()
|
||||
default:
|
||||
case entityFieldSecondary:
|
||||
f.primary.Blur()
|
||||
f.secondary.Focus()
|
||||
default:
|
||||
f.primary.Blur()
|
||||
f.secondary.Blur()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,12 +243,27 @@ func (f *EntityForm) Update(msg tea.Msg) tea.Cmd {
|
||||
if !f.active || !f.focused || f.readOnly {
|
||||
return nil
|
||||
}
|
||||
if key, ok := msg.(tea.KeyMsg); ok && f.field == entityFieldStatus {
|
||||
switch key.String() {
|
||||
case "left", "h":
|
||||
f.cycleStatus(-1)
|
||||
return nil
|
||||
case "right", "l":
|
||||
f.cycleStatus(1)
|
||||
return nil
|
||||
case " ":
|
||||
f.cycleStatus(1)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
var cmd tea.Cmd
|
||||
switch f.field {
|
||||
case entityFieldPrimary:
|
||||
f.primary, cmd = f.primary.Update(msg)
|
||||
default:
|
||||
case entityFieldSecondary:
|
||||
f.secondary, cmd = f.secondary.Update(msg)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if _, ok := msg.(tea.KeyMsg); ok {
|
||||
f.dirty = true
|
||||
@@ -199,6 +284,28 @@ func (f EntityForm) View() string {
|
||||
if f.focused && f.field == entityFieldSecondary && !f.readOnly {
|
||||
sLabel = f.styles.labelAct.Render(f.secondaryLabel)
|
||||
}
|
||||
statusBlock := ""
|
||||
if f.hasStatus {
|
||||
stLabel := f.styles.label.Render("Status")
|
||||
if f.focused && f.field == entityFieldStatus && !f.readOnly {
|
||||
stLabel = f.styles.labelAct.Render("Status")
|
||||
}
|
||||
valStyle := f.styles.label
|
||||
if f.status == model.ProjectLifecycleClosed {
|
||||
valStyle = valStyle.Foreground(lipgloss.Color("245"))
|
||||
} else {
|
||||
valStyle = valStyle.Foreground(lipgloss.Color("42"))
|
||||
}
|
||||
hint := ""
|
||||
if f.focused && f.field == entityFieldStatus && !f.readOnly {
|
||||
hint = f.styles.label.Render(" ←/→ toggle")
|
||||
}
|
||||
statusBlock = strings.Join([]string{
|
||||
"",
|
||||
stLabel,
|
||||
valStyle.Render(f.status.Label()) + hint,
|
||||
}, "\n")
|
||||
}
|
||||
ro := ""
|
||||
if f.readOnly {
|
||||
ro = lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render(" (read-only)") + "\n"
|
||||
@@ -210,6 +317,7 @@ func (f EntityForm) View() string {
|
||||
"",
|
||||
sLabel,
|
||||
f.secondary.View(),
|
||||
statusBlock,
|
||||
}, "\n")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user