Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}