refactor(office): generalize DataTable layout and document TUI table skill
Unify project/user flex column layout into layoutFlexTable with per-subject policy in model. Add office-tui-table skill and AGENTS notes; fix CI go mod tidy drift and test matrix Go version to match go.mod 1.25. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -39,7 +39,11 @@ func TestUserFormFieldCount(t *testing.T) {
|
||||
|
||||
func TestLayoutUserTableEmailAbsorbsWidth(t *testing.T) {
|
||||
cols := model.BuildColumns(model.SubjectUsers, nil)
|
||||
lay := layoutUserTable(cols, 100)
|
||||
flex, ok := model.TableFlexLayoutFor(model.SubjectUsers)
|
||||
if !ok {
|
||||
t.Fatal("expected flex layout")
|
||||
}
|
||||
lay := layoutFlexTable(cols, 100, flex.FlexColumnKey, flex.MinFlexWidth)
|
||||
sum := 0
|
||||
emailW := 0
|
||||
for _, i := range lay.indices {
|
||||
|
||||
@@ -400,7 +400,7 @@ func (t *DataTable) styleCell(row, col int, colKey string, selected bool, item m
|
||||
}
|
||||
|
||||
func (t *DataTable) ensureColVisible() {
|
||||
if t.spec.Subject == model.SubjectProjects || t.spec.Subject == model.SubjectUsers {
|
||||
if flex, ok := model.TableFlexLayoutFor(t.spec.Subject); ok && flex.FlexColumnKey != "" {
|
||||
t.colScroll = 0
|
||||
return
|
||||
}
|
||||
|
||||
@@ -15,15 +15,75 @@ func (t *DataTable) computeLayout() columnLayout {
|
||||
if len(t.columns) == 0 || contentW <= 0 {
|
||||
return columnLayout{widths: map[int]int{}}
|
||||
}
|
||||
if t.spec.Subject == model.SubjectProjects {
|
||||
return layoutProjectTable(t.columns, contentW)
|
||||
}
|
||||
if t.spec.Subject == model.SubjectUsers {
|
||||
return layoutUserTable(t.columns, contentW)
|
||||
if flex, ok := model.TableFlexLayoutFor(t.spec.Subject); ok {
|
||||
return layoutFlexTable(t.columns, contentW, flex.FlexColumnKey, flex.MinFlexWidth)
|
||||
}
|
||||
return layoutScrollingTable(t.columns, t.colScroll, contentW)
|
||||
}
|
||||
|
||||
const minFixedColumnWidth = 3
|
||||
|
||||
// layoutFlexTable shows every column; flexKey absorbs leftover width.
|
||||
func layoutFlexTable(cols []model.TableColumn, totalW int, flexKey string, minFlexWidth int) columnLayout {
|
||||
indices := make([]int, len(cols))
|
||||
for i := range cols {
|
||||
indices[i] = i
|
||||
}
|
||||
widths := make(map[int]int, len(cols))
|
||||
|
||||
flexIdx := -1
|
||||
fixed := 0
|
||||
for i, col := range cols {
|
||||
if col.Key == flexKey {
|
||||
flexIdx = i
|
||||
continue
|
||||
}
|
||||
widths[i] = col.Width
|
||||
fixed += widths[i]
|
||||
}
|
||||
if flexIdx < 0 {
|
||||
return layoutScrollingTable(cols, 0, totalW)
|
||||
}
|
||||
|
||||
flexW := totalW - fixed
|
||||
if flexW < minFlexWidth {
|
||||
shrinkFixedColumns(widths, indices, flexIdx, fixed+minFlexWidth-totalW)
|
||||
fixed = 0
|
||||
for _, i := range indices {
|
||||
if i != flexIdx {
|
||||
fixed += widths[i]
|
||||
}
|
||||
}
|
||||
flexW = totalW - fixed
|
||||
if flexW < minFlexWidth {
|
||||
flexW = minFlexWidth
|
||||
}
|
||||
}
|
||||
widths[flexIdx] = flexW
|
||||
normalizeWidthSum(widths, indices, totalW, flexIdx)
|
||||
return columnLayout{indices: indices, widths: widths}
|
||||
}
|
||||
|
||||
func shrinkFixedColumns(widths map[int]int, indices []int, flexIdx, need int) {
|
||||
for need > 0 {
|
||||
changed := false
|
||||
for _, i := range indices {
|
||||
if i == flexIdx || widths[i] <= minFixedColumnWidth {
|
||||
continue
|
||||
}
|
||||
widths[i]--
|
||||
need--
|
||||
changed = true
|
||||
if need == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func layoutScrollingTable(cols []model.TableColumn, colScroll, contentW int) columnLayout {
|
||||
indices := pickVisibleColumnIndices(cols, colScroll, contentW)
|
||||
widths := make(map[int]int, len(indices))
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/eslider/go-onlyoffice/cmd/office/model"
|
||||
)
|
||||
|
||||
func TestLayoutFlexTableAllProjectColumnsVisible(t *testing.T) {
|
||||
cols := model.BuildColumns(model.SubjectProjects, nil)
|
||||
flex, ok := model.TableFlexLayoutFor(model.SubjectProjects)
|
||||
if !ok {
|
||||
t.Fatal("expected flex layout for projects")
|
||||
}
|
||||
lay := layoutFlexTable(cols, 80, flex.FlexColumnKey, flex.MinFlexWidth)
|
||||
if len(lay.indices) != 7 {
|
||||
t.Fatalf("got %d visible columns, want 7", len(lay.indices))
|
||||
}
|
||||
sum := 0
|
||||
for _, i := range lay.indices {
|
||||
sum += lay.widths[i]
|
||||
}
|
||||
if sum != 80 {
|
||||
t.Fatalf("width sum=%d want 80", sum)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLayoutFlexTableUserEmailAbsorbsWidth(t *testing.T) {
|
||||
cols := model.BuildColumns(model.SubjectUsers, nil)
|
||||
flex, ok := model.TableFlexLayoutFor(model.SubjectUsers)
|
||||
if !ok {
|
||||
t.Fatal("expected flex layout for users")
|
||||
}
|
||||
lay := layoutFlexTable(cols, 100, flex.FlexColumnKey, flex.MinFlexWidth)
|
||||
sum := 0
|
||||
emailW := 0
|
||||
for _, i := range lay.indices {
|
||||
sum += lay.widths[i]
|
||||
if cols[i].Key == "email" {
|
||||
emailW = lay.widths[i]
|
||||
}
|
||||
}
|
||||
if sum != 100 {
|
||||
t.Fatalf("sum=%d want 100", sum)
|
||||
}
|
||||
if emailW < 20 {
|
||||
t.Fatalf("email width=%d should absorb remainder", emailW)
|
||||
}
|
||||
}
|
||||
@@ -5,72 +5,6 @@ import (
|
||||
"github.com/eslider/go-onlyoffice/cmd/office/model"
|
||||
)
|
||||
|
||||
const (
|
||||
projectMinTitleWidth = 12
|
||||
projectMinFixedWidth = 3
|
||||
)
|
||||
|
||||
// layoutProjectTable shows every project column; the title column absorbs leftover width.
|
||||
func layoutProjectTable(cols []model.TableColumn, totalW int) columnLayout {
|
||||
indices := make([]int, len(cols))
|
||||
for i := range cols {
|
||||
indices[i] = i
|
||||
}
|
||||
widths := make(map[int]int, len(cols))
|
||||
|
||||
titleIdx := -1
|
||||
fixed := 0
|
||||
for i, col := range cols {
|
||||
if col.Key == "title" {
|
||||
titleIdx = i
|
||||
continue
|
||||
}
|
||||
widths[i] = col.Width
|
||||
fixed += widths[i]
|
||||
}
|
||||
if titleIdx < 0 {
|
||||
return layoutScrollingTable(cols, 0, totalW)
|
||||
}
|
||||
|
||||
titleW := totalW - fixed
|
||||
if titleW < projectMinTitleWidth {
|
||||
shrinkProjectFixedColumns(widths, indices, titleIdx, fixed+projectMinTitleWidth-totalW)
|
||||
fixed = 0
|
||||
for _, i := range indices {
|
||||
if i != titleIdx {
|
||||
fixed += widths[i]
|
||||
}
|
||||
}
|
||||
titleW = totalW - fixed
|
||||
if titleW < projectMinTitleWidth {
|
||||
titleW = projectMinTitleWidth
|
||||
}
|
||||
}
|
||||
widths[titleIdx] = titleW
|
||||
normalizeWidthSum(widths, indices, totalW, titleIdx)
|
||||
return columnLayout{indices: indices, widths: widths}
|
||||
}
|
||||
|
||||
func shrinkProjectFixedColumns(widths map[int]int, indices []int, titleIdx, need int) {
|
||||
for need > 0 {
|
||||
changed := false
|
||||
for _, i := range indices {
|
||||
if i == titleIdx || widths[i] <= projectMinFixedWidth {
|
||||
continue
|
||||
}
|
||||
widths[i]--
|
||||
need--
|
||||
changed = true
|
||||
if need == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *DataTable) projectBaseStyle(item model.Item) lipgloss.Style {
|
||||
if model.ProjectIsOpen(item.Raw) {
|
||||
return lipgloss.NewStyle().
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/eslider/go-onlyoffice/cmd/office/model"
|
||||
)
|
||||
|
||||
func TestLayoutProjectTableAllColumnsVisible(t *testing.T) {
|
||||
cols := model.BuildColumns(model.SubjectProjects, nil)
|
||||
lay := layoutProjectTable(cols, 80)
|
||||
if len(lay.indices) != 7 {
|
||||
t.Fatalf("got %d visible columns, want 7", len(lay.indices))
|
||||
}
|
||||
sum := 0
|
||||
for _, i := range lay.indices {
|
||||
sum += lay.widths[i]
|
||||
}
|
||||
if sum != 80 {
|
||||
t.Fatalf("width sum=%d want 80", sum)
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,11 @@ func TestDistributeColumnWidthsFillsTotal(t *testing.T) {
|
||||
|
||||
func TestProjectTableTitleAbsorbsWidth(t *testing.T) {
|
||||
cols := model.BuildColumns(model.SubjectProjects, nil)
|
||||
lay := layoutProjectTable(cols, 120)
|
||||
flex, ok := model.TableFlexLayoutFor(model.SubjectProjects)
|
||||
if !ok {
|
||||
t.Fatal("expected flex layout")
|
||||
}
|
||||
lay := layoutFlexTable(cols, 120, flex.FlexColumnKey, flex.MinFlexWidth)
|
||||
if len(lay.indices) != len(cols) {
|
||||
t.Fatalf("expected all %d columns visible, got %d", len(cols), len(lay.indices))
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package ui
|
||||
|
||||
import "github.com/eslider/go-onlyoffice/cmd/office/model"
|
||||
|
||||
const (
|
||||
userMinEmailWidth = 16
|
||||
userMinFixedWidth = 3
|
||||
)
|
||||
|
||||
// layoutUserTable shows every user column; email absorbs leftover width.
|
||||
func layoutUserTable(cols []model.TableColumn, totalW int) columnLayout {
|
||||
indices := make([]int, len(cols))
|
||||
for i := range cols {
|
||||
indices[i] = i
|
||||
}
|
||||
widths := make(map[int]int, len(cols))
|
||||
|
||||
emailIdx := -1
|
||||
fixed := 0
|
||||
for i, col := range cols {
|
||||
if col.Key == "email" {
|
||||
emailIdx = i
|
||||
continue
|
||||
}
|
||||
widths[i] = col.Width
|
||||
fixed += widths[i]
|
||||
}
|
||||
if emailIdx < 0 {
|
||||
return layoutScrollingTable(cols, 0, totalW)
|
||||
}
|
||||
|
||||
emailW := totalW - fixed
|
||||
if emailW < userMinEmailWidth {
|
||||
shrinkUserFixedColumns(widths, indices, emailIdx, fixed+userMinEmailWidth-totalW)
|
||||
fixed = 0
|
||||
for _, i := range indices {
|
||||
if i != emailIdx {
|
||||
fixed += widths[i]
|
||||
}
|
||||
}
|
||||
emailW = totalW - fixed
|
||||
if emailW < userMinEmailWidth {
|
||||
emailW = userMinEmailWidth
|
||||
}
|
||||
}
|
||||
widths[emailIdx] = emailW
|
||||
normalizeWidthSum(widths, indices, totalW, emailIdx)
|
||||
return columnLayout{indices: indices, widths: widths}
|
||||
}
|
||||
|
||||
func shrinkUserFixedColumns(widths map[int]int, indices []int, emailIdx, need int) {
|
||||
for need > 0 {
|
||||
changed := false
|
||||
for _, i := range indices {
|
||||
if i == emailIdx || widths[i] <= userMinFixedWidth {
|
||||
continue
|
||||
}
|
||||
widths[i]--
|
||||
need--
|
||||
changed = true
|
||||
if need == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user