- commit
- e94d326
- parent
- c2f5b9e
- author
- Eric Bower
- date
- 2024-02-27 17:14:33 +0000 UTC
design(pgs): cli aesthetic
4 files changed,
+155,
-67
+1,
-1
1@@ -264,7 +264,7 @@ const (
2 LEFT JOIN app_users ON app_users.id = projects.user_id
3 ORDER BY created_at ASC
4 LIMIT $1 OFFSET $2`
5- sqlFindProjectsByUser = `SELECT id, user_id, name, project_dir, acl, created_at, updated_at FROM projects WHERE user_id = $1 ORDER BY updated_at DESC;`
6+ sqlFindProjectsByUser = `SELECT id, user_id, name, project_dir, acl, created_at, updated_at FROM projects WHERE user_id = $1 ORDER BY name ASC, updated_at DESC;`
7 sqlFindProjectsByPrefix = `SELECT id, user_id, name, project_dir, acl, created_at, updated_at FROM projects WHERE user_id = $1 AND name = project_dir AND name ILIKE $2 ORDER BY updated_at ASC, name ASC;`
8 sqlFindProjectLinks = `SELECT id, user_id, name, project_dir, acl, created_at, updated_at FROM projects WHERE user_id = $1 AND name != project_dir AND project_dir = $2 ORDER BY name ASC;`
9 sqlLinkToProject = `UPDATE projects SET project_dir = $1, updated_at = $2 WHERE id = $3;`
+130,
-42
1@@ -9,29 +9,122 @@ import (
2 "path/filepath"
3 "strings"
4
5+ "github.com/charmbracelet/lipgloss"
6+ "github.com/charmbracelet/lipgloss/table"
7 "github.com/picosh/pico/db"
8 "github.com/picosh/pico/shared"
9 "github.com/picosh/pico/shared/storage"
10+ "github.com/picosh/pico/wish/cms/ui/common"
11 )
12
13+var re = lipgloss.NewRenderer(os.Stdout)
14+var baseStyle = re.NewStyle().Padding(0, 1)
15+var headerStyle = baseStyle.Copy().Foreground(common.Indigo).Bold(true)
16+var borderStyle = re.NewStyle().Foreground(lipgloss.Color("238"))
17+
18+func styleRows(row, col int) lipgloss.Style {
19+ if row == 0 {
20+ return headerStyle
21+ }
22+
23+ even := row%2 == 0
24+ if even {
25+ return baseStyle.Copy().Foreground(lipgloss.Color("245"))
26+ }
27+ return baseStyle.Copy().Foreground(lipgloss.Color("252"))
28+}
29+
30+func projectTable(projects []*db.Project) *table.Table {
31+ headers := []string{
32+ "Name",
33+ "Last Updated",
34+ "Links To",
35+ "ACL Type",
36+ "ACL",
37+ }
38+ data := [][]string{}
39+ for _, project := range projects {
40+ row := []string{
41+ project.Name,
42+ project.UpdatedAt.Format("2006-01-02 15:04:05"),
43+ }
44+ links := ""
45+ if project.ProjectDir != project.Name {
46+ links = project.ProjectDir
47+ }
48+ row = append(row, links)
49+ row = append(row,
50+ project.Acl.Type,
51+ strings.Join(project.Acl.Data, " "),
52+ )
53+ data = append(data, row)
54+ }
55+
56+ t := table.New().
57+ Border(lipgloss.NormalBorder()).
58+ BorderStyle(borderStyle).
59+ Headers(headers...).
60+ Rows(data...).
61+ StyleFunc(styleRows)
62+ return t
63+}
64+
65 func getHelpText(userName, projectName string) string {
66- helpStr := "commands: [help, stats, ls, rm, link, unlink, prune, retain, depends]\n\n"
67- helpStr += "NOTICE: any cmd that results in a mutation *must* be appended with `--write` for the changes to persist, otherwise it will simply output a dry-run.\n\n"
68-
69- sshCmdStr := fmt.Sprintf("ssh %s@pgs.sh", userName)
70- helpStr += fmt.Sprintf("`%s help`: prints this screen\n", sshCmdStr)
71- helpStr += fmt.Sprintf("`%s stats`: prints stats for user\n", sshCmdStr)
72- helpStr += fmt.Sprintf("`%s ls`: lists projects\n", sshCmdStr)
73- helpStr += fmt.Sprintf("`%s rm %s`: deletes `%s`\n", sshCmdStr, projectName, projectName)
74- helpStr += fmt.Sprintf("`%s link %s project-b`: symbolic link from `%s` to `project-b`\n", sshCmdStr, projectName, projectName)
75- helpStr += fmt.Sprintf(
76- "`%s unlink %s`: alias for `link %s %s`, which removes symbolic link for `%s`\n",
77- sshCmdStr, projectName, projectName, projectName, projectName,
78- )
79- helpStr += fmt.Sprintf("`%s prune %s`: removes all projects that match prefix `%s` and is not linked to another project\n", sshCmdStr, projectName, projectName)
80- helpStr += fmt.Sprintf("`%s retain %s`: alias for `prune` but retains the (3) most recently updated projects\n", sshCmdStr, projectName)
81- helpStr += fmt.Sprintf("`%s depends %s`: lists all projects linked to `%s`\n", sshCmdStr, projectName, projectName)
82- helpStr += fmt.Sprintf("`%s acl %s [public, public_keys, pico] [comma delimited shasum keys or pico usernames]`: control access to project `%s`\n", sshCmdStr, projectName, projectName)
83+ helpStr := "Commands: [help, stats, ls, rm, link, unlink, prune, retain, depends, acl]\n\n"
84+ helpStr += "NOTICE: *must* append with `--write` for the changes to persist.\n\n"
85+
86+ headers := []string{"Cmd", "Description"}
87+ data := [][]string{
88+ {
89+ "help",
90+ "prints this screen",
91+ },
92+ {
93+ "stats",
94+ "usage statistics",
95+ },
96+ {
97+ "ls",
98+ "lists projects",
99+ },
100+ {
101+ fmt.Sprintf("rm %s", projectName),
102+ fmt.Sprintf("delete %s", projectName),
103+ },
104+ {
105+ fmt.Sprintf("link %s project-b", projectName),
106+ fmt.Sprintf("symbolic link `%s` to `project-b`", projectName),
107+ },
108+ {
109+ fmt.Sprintf("unlink %s", projectName),
110+ fmt.Sprintf("removes symbolic link for `%s`", projectName),
111+ },
112+ {
113+ fmt.Sprintf("prune %s", projectName),
114+ fmt.Sprintf("removes projects that match prefix `%s`", projectName),
115+ },
116+ {
117+ fmt.Sprintf("retain %s", projectName),
118+ "alias `prune` but keeps last (3) projects",
119+ },
120+ {
121+ fmt.Sprintf("depends %s", projectName),
122+ fmt.Sprintf("lists all projects linked to `%s`", projectName),
123+ },
124+ {
125+ fmt.Sprintf("acl %s", projectName),
126+ fmt.Sprintf("access control for `%s`", projectName),
127+ },
128+ }
129+
130+ t := table.New().
131+ Border(lipgloss.NormalBorder()).
132+ BorderStyle(borderStyle).
133+ Headers(headers...).
134+ Rows(data...).
135+ StyleFunc(styleRows)
136+
137+ helpStr += t.String()
138 return helpStr
139 }
140
141@@ -170,16 +263,21 @@ func (c *Cmd) stats(cfgMaxSize uint64) error {
142 return err
143 }
144
145- str := "stats\n"
146- str += "=====\n"
147- str += fmt.Sprintf(
148- "space:\t\t%.4f/%.4fGB, %.4f%%\n",
149- shared.BytesToGB(int(totalFileSize)),
150- shared.BytesToGB(int(storageMax)),
151- (float32(totalFileSize)/float32(storageMax))*100,
152- )
153- str += fmt.Sprintf("projects:\t%d", len(projects))
154- c.output(str)
155+ headers := []string{"Used (GB)", "Quota (GB)", "Used (%)", "Projects (#)"}
156+ data := []string{
157+ fmt.Sprintf("%.4f", shared.BytesToGB(int(totalFileSize))),
158+ fmt.Sprintf("%.4f", shared.BytesToGB(int(storageMax))),
159+ fmt.Sprintf("%.4f", (float32(totalFileSize)/float32(storageMax))*100),
160+ fmt.Sprintf("%d", len(projects)),
161+ }
162+
163+ t := table.New().
164+ Border(lipgloss.NormalBorder()).
165+ BorderStyle(borderStyle).
166+ Headers(headers...).
167+ Rows(data).
168+ StyleFunc(styleRows)
169+ c.output(t.String())
170
171 return nil
172 }
173@@ -194,13 +292,8 @@ func (c *Cmd) ls() error {
174 c.output("no projects found")
175 }
176
177- for _, project := range projects {
178- out := fmt.Sprintf("%s (links to: %s)", project.Name, project.ProjectDir)
179- if project.Name == project.ProjectDir {
180- out = fmt.Sprintf("%s\t(last updated: %s)", project.Name, project.UpdatedAt)
181- }
182- c.output(out)
183- }
184+ t := projectTable(projects)
185+ c.output(t.String())
186
187 return nil
188 }
189@@ -280,18 +373,13 @@ func (c *Cmd) depends(projectName string) error {
190 }
191
192 if len(projects) == 0 {
193- out := fmt.Sprintf("no projects linked to this project (%s) found", projectName)
194+ out := fmt.Sprintf("no projects linked to (%s)", projectName)
195 c.output(out)
196 return nil
197 }
198
199- for _, project := range projects {
200- out := fmt.Sprintf("%s (links to: %s)", project.Name, project.ProjectDir)
201- if project.Name == project.ProjectDir {
202- out = project.Name
203- }
204- c.output(out)
205- }
206+ t := projectTable(projects)
207+ c.output(t.String())
208
209 return nil
210 }
+21,
-21
1@@ -6,13 +6,13 @@ import (
2
3 // Color definitions.
4 var (
5- indigo = lipgloss.AdaptiveColor{Light: "#5A56E0", Dark: "#7571F9"}
6- subtleIndigo = lipgloss.AdaptiveColor{Light: "#7D79F6", Dark: "#514DC1"}
7- cream = lipgloss.AdaptiveColor{Light: "#FFFDF5", Dark: "#FFFDF5"}
8- fuschia = lipgloss.AdaptiveColor{Light: "#EE6FF8", Dark: "#EE6FF8"}
9- green = lipgloss.Color("#04B575")
10- red = lipgloss.AdaptiveColor{Light: "#FF4672", Dark: "#ED567A"}
11- faintRed = lipgloss.AdaptiveColor{Light: "#FF6F91", Dark: "#C74665"}
12+ Indigo = lipgloss.AdaptiveColor{Light: "#5A56E0", Dark: "#7571F9"}
13+ SubtleIndigo = lipgloss.AdaptiveColor{Light: "#7D79F6", Dark: "#514DC1"}
14+ Cream = lipgloss.AdaptiveColor{Light: "#FFFDF5", Dark: "#FFFDF5"}
15+ Fuschia = lipgloss.AdaptiveColor{Light: "#EE6FF8", Dark: "#EE6FF8"}
16+ Green = lipgloss.Color("#04B575")
17+ Red = lipgloss.AdaptiveColor{Light: "#FF4672", Dark: "#ED567A"}
18+ FaintRed = lipgloss.AdaptiveColor{Light: "#FF6F91", Dark: "#C74665"}
19 )
20
21 type Styles struct {
22@@ -44,9 +44,9 @@ type Styles struct {
23 func DefaultStyles() Styles {
24 s := Styles{}
25
26- s.Cursor = lipgloss.NewStyle().Foreground(fuschia)
27+ s.Cursor = lipgloss.NewStyle().Foreground(Fuschia)
28 s.Wrap = lipgloss.NewStyle().Width(58)
29- s.Keyword = lipgloss.NewStyle().Foreground(green)
30+ s.Keyword = lipgloss.NewStyle().Foreground(Green)
31 s.Paragraph = s.Wrap.Copy().Margin(1, 0, 0, 2)
32 s.Code = lipgloss.NewStyle().
33 Foreground(lipgloss.AdaptiveColor{Light: "#FF4672", Dark: "#ED567A"}).
34@@ -54,30 +54,30 @@ func DefaultStyles() Styles {
35 Padding(0, 1)
36 s.Subtle = lipgloss.NewStyle().
37 Foreground(lipgloss.AdaptiveColor{Light: "#9B9B9B", Dark: "#5C5C5C"})
38- s.Error = lipgloss.NewStyle().Foreground(red)
39+ s.Error = lipgloss.NewStyle().Foreground(Red)
40 s.Prompt = lipgloss.NewStyle().MarginRight(1).SetString(">")
41- s.FocusedPrompt = s.Prompt.Copy().Foreground(fuschia)
42- s.Note = lipgloss.NewStyle().Foreground(green)
43+ s.FocusedPrompt = s.Prompt.Copy().Foreground(Fuschia)
44+ s.Note = lipgloss.NewStyle().Foreground(Green)
45 s.NoteDim = lipgloss.NewStyle().
46 Foreground(lipgloss.AdaptiveColor{Light: "#ABE5D1", Dark: "#2B4A3F"})
47 s.Delete = s.Error.Copy()
48- s.DeleteDim = lipgloss.NewStyle().Foreground(faintRed)
49- s.Label = lipgloss.NewStyle().Foreground(fuschia)
50- s.LabelDim = lipgloss.NewStyle().Foreground(indigo)
51- s.ListKey = lipgloss.NewStyle().Foreground(indigo)
52- s.ListDim = lipgloss.NewStyle().Foreground(subtleIndigo)
53+ s.DeleteDim = lipgloss.NewStyle().Foreground(FaintRed)
54+ s.Label = lipgloss.NewStyle().Foreground(Fuschia)
55+ s.LabelDim = lipgloss.NewStyle().Foreground(Indigo)
56+ s.ListKey = lipgloss.NewStyle().Foreground(Indigo)
57+ s.ListDim = lipgloss.NewStyle().Foreground(SubtleIndigo)
58 s.InactivePagination = lipgloss.NewStyle().
59 Foreground(lipgloss.AdaptiveColor{Light: "#CACACA", Dark: "#4F4F4F"})
60 s.SelectionMarker = lipgloss.NewStyle().
61- Foreground(fuschia).
62+ Foreground(Fuschia).
63 PaddingRight(1).
64 SetString(">")
65 s.Checkmark = lipgloss.NewStyle().
66 SetString("✔").
67- Foreground(green)
68- s.SelectedMenuItem = lipgloss.NewStyle().Foreground(fuschia)
69+ Foreground(Green)
70+ s.SelectedMenuItem = lipgloss.NewStyle().Foreground(Fuschia)
71 s.Logo = lipgloss.NewStyle().
72- Foreground(cream).
73+ Foreground(Cream).
74 Background(lipgloss.Color("#5A56E0")).
75 Padding(0, 1)
76 s.App = lipgloss.NewStyle().Margin(1, 0, 1, 2)
+3,
-3
1@@ -35,19 +35,19 @@ func VerticalLine(state State) string {
2 String()
3 }
4
5-var valStyle = lipgloss.NewStyle().Foreground(indigo)
6+var valStyle = lipgloss.NewStyle().Foreground(Indigo)
7
8 var (
9 spinnerStyle = lipgloss.NewStyle().
10 Foreground(lipgloss.AdaptiveColor{Light: "#8E8E8E", Dark: "#747373"})
11
12 blurredButtonStyle = lipgloss.NewStyle().
13- Foreground(cream).
14+ Foreground(Cream).
15 Background(lipgloss.AdaptiveColor{Light: "#BDB0BE", Dark: "#827983"}).
16 Padding(0, 3)
17
18 focusedButtonStyle = blurredButtonStyle.Copy().
19- Background(fuschia)
20+ Background(Fuschia)
21 )
22
23 // KeyValueView renders key-value pairs.