utils.gno
5.45 Kb · 191 lines
1package basedao
2
3import (
4 "strings"
5 "time"
6
7 "gno.land/p/demo/svg"
8 "gno.land/p/moul/md"
9 "gno.land/p/nt/avl/v0"
10 "gno.land/p/nt/avl/v0/pager"
11 "gno.land/p/nt/seqid/v0"
12 "gno.land/p/nt/ufmt/v0"
13 "gno.land/p/samcrew/daokit"
14)
15
16// Truncates a string to a maximum length, adding "..." in the middle if it exceeds the limit.
17func TruncateMiddle(s string, max ...int) string {
18 defMax := 12
19 if len(max) > 0 {
20 defMax = max[0]
21 }
22 runes := []rune(s)
23 if len(runes) <= defMax {
24 return s
25 }
26 if defMax <= 3 {
27 return string(runes[:defMax])
28 }
29 half := (defMax - 3) / 2
30 return ufmt.Sprintf("%s...%s", string(runes[:half]), string(runes[len(runes)-half:]))
31}
32
33// Formats an address as a clickable link in Markdown.
34func RenderAddressLink(addr string) string {
35 return md.Link(TruncateMiddle(addr), ufmt.Sprintf("/u/%s", addr))
36}
37
38// Replaces all occurrences of roles in the given string with clickable links in Markdown.
39func (d *DAOPrivate) RenderWithRolesLinks(s string) string {
40 roles := d.Members.GetRoles()
41 for _, role := range roles {
42 s = strings.ReplaceAll(s, role, d.RenderRoleLink(role))
43 }
44 return s
45}
46
47// Formats a role as a clickable link in Markdown.
48func (d *DAOPrivate) RenderRoleLink(role string) string {
49 pkgPath := d.Realm.PkgPath()
50 linkPath := getLinkPath(pkgPath)
51 roleName := d.Members.RoleInfo(role).Name
52
53 if role == "" {
54 return "*No role assigned*"
55 }
56 return ufmt.Sprintf("[%s](%s:%s/%s)", roleName, linkPath, "role", role)
57}
58
59// Formats a role as a clickable link in Markdown, with colored chip at left.
60func (d *DAOPrivate) RenderRoleLinkWithChip(role string) string {
61 pkgPath := d.Realm.PkgPath()
62 linkPath := getLinkPath(pkgPath)
63
64 if role == "" {
65 return ufmt.Sprintf("%s %s", d.RoleColoredChip(""), "*No role assigned*")
66 }
67 return ufmt.Sprintf("[%s %s](%s:%s/%s)", d.RoleColoredChip(role), role, linkPath, "role", role)
68}
69
70// Formats a time.Time object as a string with UTC offset.
71func FormatDateTimeWithUTCOffset(t time.Time) string {
72 _, offsetSeconds := t.Zone()
73 hours := offsetSeconds / 3600
74 minutes := (offsetSeconds % 3600) / 60
75 sign := "+"
76 if hours < 0 || minutes < 0 {
77 sign = "-"
78 hours = -hours
79 minutes = -minutes
80 }
81 return ufmt.Sprintf("%s UTC%s%02d:%02d",
82 t.Format("2006-01-02 15:04:05"),
83 sign, hours, minutes)
84}
85
86// Returns a colored chip svg for the given role. If no role is given, a gray chip is returned.
87// It can optionally take a custom svg.Canvas to define the size and style of the chip.
88func (d *DAOPrivate) RoleColoredChip(role string, canvas ...*svg.Canvas) string {
89 roleName := ""
90 chipColor := ""
91 canvasWidth := 16
92 canvasHeight := 16
93 canvasElem := []svg.Elem{}
94 canvasStyle := avl.NewTree()
95
96 if canvas != nil && len(canvas) > 0 {
97 canvasWidth = canvas[0].Width
98 canvasHeight = canvas[0].Height
99 canvasElem = canvas[0].Elems
100 canvasStyle = canvas[0].Style
101 }
102
103 if role == "" {
104 chipColor = "#CDCDCD"
105 } else {
106 info := d.Members.RoleInfo(role)
107 chipColor = info.Color
108 roleName = info.Name
109 }
110 chipCanvas := &svg.Canvas{
111 Width: canvasWidth,
112 Height: canvasHeight,
113 Elems: canvasElem,
114 Style: canvasStyle,
115 }
116
117 chipCanvas.Append(svg.NewRectangle(0, 0, canvasWidth, canvasHeight, chipColor))
118 return chipCanvas.Render(roleName + " colored chip")
119}
120
121// Renders a table of members with their display name, address, roles, and profile link.
122// It supports pagination.
123func (d *DAOPrivate) RenderMembersTable(path string, members *avl.Tree) string {
124 pkgPath := d.Realm.PkgPath()
125 linkPath := getLinkPath(pkgPath)
126 const pageSize = 10
127 pager := pager.NewPager(members, pageSize, false)
128 page := pager.MustGetPageByPath(path)
129
130 s := ""
131 s += ufmt.Sprintf("| **Name** | **Address 🔗** | **Roles 🎭** | **Profile** |\n")
132 s += ufmt.Sprintf("|----------|----------------|--------------|-------------|\n")
133
134 for _, item := range page.Items {
135 displayName := d.GetProfileString(address(item.Key), "DisplayName", FALLBACK_DISPLAY_NAME)
136 roles := d.Members.GetMemberRoles(item.Key)
137 rolesStr := strings.Join(roles, ", ")
138
139 if len(roles) == 0 {
140 rolesStr = d.RenderRoleLinkWithChip("")
141 }
142 for _, role := range roles {
143 rolesStr = strings.Replace(rolesStr, role, d.RenderRoleLinkWithChip(role), -1)
144 }
145
146 s += ufmt.Sprintf("| %s | %s | %s | [View](%s:%s/%s) |\n",
147 displayName,
148 RenderAddressLink(item.Key),
149 rolesStr,
150 linkPath, "member", item.Key)
151 }
152
153 s += "\n" + page.Picker(path) + "\n"
154 return s
155}
156
157func (d *DAOPrivate) RenderProposalsTable(path string, proposals *daokit.ProposalsStore) string {
158 pkgPath := d.Realm.PkgPath()
159 linkPath := getLinkPath(pkgPath)
160 proposalsCount := proposals.Tree.Size()
161 const pageSize = 10
162 pager := pager.NewPager(proposals.Tree, pageSize, true)
163 page := pager.MustGetPageByPath(path)
164
165 s := ""
166 if proposalsCount == 0 {
167 s += ufmt.Sprintf("\t⚠️ There are no proposals to show\n\n")
168 } else {
169 s += ufmt.Sprintf("| **ID** | **Resource** | **Proposer** | **CreatedAt** | **Status** |\n")
170 s += ufmt.Sprintf("|--------|--------------|--------------|---------------|------------|\n")
171 }
172
173 for _, item := range page.Items {
174 proposal := item.Value.(*daokit.Proposal)
175 key := item.Key
176 id, err := seqid.FromString(key)
177 if err != nil {
178 panic(err)
179 }
180 resource := d.Core.Resources.Get(proposal.Action.Type())
181 s += ufmt.Sprintf("| [%d](%s:%s/%d) | %s | %s | %s | %s |\n",
182 uint64(id), linkPath, "proposal", uint64(id),
183 resource.DisplayName,
184 RenderAddressLink(proposal.ProposerID),
185 FormatDateTimeWithUTCOffset(proposal.CreatedAt),
186 proposal.Status.String())
187 }
188
189 s += "\n" + page.Picker(path) + "\n"
190 return s
191}