cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm
leaflet
readability
golang
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}