cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm leaflet readability golang
at main 119 lines 3.0 kB view raw
1package ui 2 3import ( 4 "context" 5 "fmt" 6 "io" 7 "time" 8 9 "github.com/stormlightlabs/noteleaf/internal/repo" 10) 11 12// ProjectRepository interface for dependency injection in tests 13type ProjectRepository interface { 14 GetProjects(ctx context.Context) ([]repo.ProjectSummary, error) 15} 16 17func pluralizeCount(count int) string { 18 if count == 1 { 19 return "" 20 } 21 return "s" 22} 23 24// ProjectSummaryRecord adapts repo.ProjectSummary to work with DataTable 25type ProjectSummaryRecord struct { 26 summary repo.ProjectSummary 27} 28 29func (p *ProjectSummaryRecord) GetField(name string) any { 30 switch name { 31 case "name": 32 return p.summary.Name 33 case "task_count": 34 return p.summary.TaskCount 35 default: 36 return "" 37 } 38} 39 40func (p *ProjectSummaryRecord) GetTableName() string { 41 return "projects" 42} 43 44// Use task count as pseudo-ID since projects don't have IDs 45func (p *ProjectSummaryRecord) GetID() int64 { return int64(p.summary.TaskCount) } 46func (p *ProjectSummaryRecord) SetID(id int64) {} 47func (p *ProjectSummaryRecord) GetCreatedAt() time.Time { return time.Time{} } 48func (p *ProjectSummaryRecord) SetCreatedAt(t time.Time) {} 49func (p *ProjectSummaryRecord) GetUpdatedAt() time.Time { return time.Time{} } 50func (p *ProjectSummaryRecord) SetUpdatedAt(t time.Time) {} 51 52// ProjectDataSource adapts ProjectRepository to work with DataTable 53type ProjectDataSource struct { 54 repo ProjectRepository 55} 56 57func (p *ProjectDataSource) Load(ctx context.Context, opts DataOptions) ([]DataRecord, error) { 58 projects, err := p.repo.GetProjects(ctx) 59 if err != nil { 60 return nil, err 61 } 62 63 records := make([]DataRecord, len(projects)) 64 for i, project := range projects { 65 records[i] = &ProjectSummaryRecord{summary: project} 66 } 67 68 return records, nil 69} 70 71func (p *ProjectDataSource) Count(ctx context.Context, opts DataOptions) (int, error) { 72 projects, err := p.repo.GetProjects(ctx) 73 if err != nil { 74 return 0, err 75 } 76 return len(projects), nil 77} 78 79// NewProjectDataTable creates a new DataTable for browsing projects 80func NewProjectDataTable(repo ProjectRepository, opts DataTableOptions) *DataTable { 81 if opts.Title == "" { 82 opts.Title = "Projects" 83 } 84 85 if len(opts.Fields) == 0 { 86 opts.Fields = []Field{ 87 { 88 Name: "name", 89 Title: "Project Name", 90 Width: 30, 91 }, 92 { 93 Name: "task_count", 94 Title: "Task Count", 95 Width: 15, 96 Formatter: func(value any) string { 97 if count, ok := value.(int); ok { 98 return fmt.Sprintf("%d task%s", count, pluralizeCount(count)) 99 } 100 return fmt.Sprintf("%v", value) 101 }, 102 }, 103 } 104 } 105 106 source := &ProjectDataSource{repo: repo} 107 return NewDataTable(source, opts) 108} 109 110// NewProjectListFromTable creates a ProjectList-compatible interface using DataTable 111func NewProjectListFromTable(repo ProjectRepository, output io.Writer, input io.Reader, static bool) *DataTable { 112 opts := DataTableOptions{ 113 Output: output, 114 Input: input, 115 Static: static, 116 Title: "Projects", 117 } 118 return NewProjectDataTable(repo, opts) 119}