Search Apps Documentation Source Content File Folder Download Copy Actions Download

render.gno

4.96 Kb · 201 lines
  1package impl
  2
  3import (
  4	"chain/runtime"
  5	"strconv"
  6	"strings"
  7
  8	"gno.land/p/moul/helplink"
  9	"gno.land/p/nt/avl/v0/pager"
 10	"gno.land/p/nt/mux/v0"
 11	"gno.land/p/nt/seqid/v0"
 12	"gno.land/p/nt/ufmt/v0"
 13	"gno.land/r/gov/dao"
 14	"gno.land/r/sys/users"
 15)
 16
 17type render struct {
 18	relativeRealmPath string
 19	router            *mux.Router
 20	pssPager          *pager.Pager
 21}
 22
 23func NewRender(d *GovDAO) *render {
 24	ren := &render{
 25		pssPager: pager.NewPager(d.pss.Tree, 5, true),
 26	}
 27
 28	r := mux.NewRouter()
 29
 30	r.HandleFunc("", func(rw *mux.ResponseWriter, req *mux.Request) {
 31		rw.Write(ren.renderActiveProposals(req.RawPath, d))
 32	})
 33
 34	r.HandleFunc("{pid}", func(rw *mux.ResponseWriter, req *mux.Request) {
 35		rw.Write(ren.renderProposalPage(req.GetVar("pid"), d))
 36	})
 37
 38	r.HandleFunc("{pid}/votes", func(rw *mux.ResponseWriter, req *mux.Request) {
 39		rw.Write(ren.renderVotesForProposal(req.GetVar("pid"), d))
 40	})
 41
 42	ren.router = r
 43
 44	return ren
 45}
 46
 47func (ren *render) Render(pkgPath string, path string) string {
 48	relativePath, found := strings.CutPrefix(pkgPath, runtime.ChainDomain())
 49	if !found {
 50		panic(ufmt.Sprintf(
 51			"realm package with unexpected name found: %v in chain domain %v",
 52			pkgPath, runtime.ChainDomain()))
 53	}
 54	ren.relativeRealmPath = relativePath
 55	return ren.router.Render(path)
 56}
 57
 58func (ren *render) renderActiveProposals(url string, d *GovDAO) string {
 59	out := "# GovDAO\n"
 60	out += "## Members\n"
 61	out += "[> Go to Memberstore <](/r/gov/dao/v3/memberstore)\n"
 62	out += "## Proposals\n"
 63	page := ren.pssPager.MustGetPageByPath(url)
 64	if len(page.Items) == 0 {
 65		out += "\nNo proposals yet.\n\n"
 66		return out
 67	}
 68
 69	for _, item := range page.Items {
 70		seqpid, err := seqid.FromString(item.Key)
 71		if err != nil {
 72			continue
 73		}
 74		out += ren.renderProposalListItem(ufmt.Sprintf("%v", int64(seqpid)), d)
 75		out += "---\n\n"
 76	}
 77
 78	out += page.Picker("")
 79
 80	return out
 81}
 82
 83func (ren *render) renderProposalPage(sPid string, d *GovDAO) string {
 84	pid, err := strconv.ParseInt(sPid, 10, 64)
 85	if err != nil {
 86		return ufmt.Sprintf("# Error: Invalid proposal ID format.\n\n\n%s\n\n", err.Error())
 87	}
 88
 89	p, err := dao.GetProposal(cross, dao.ProposalID(pid))
 90	if err != nil {
 91		return ufmt.Sprintf("# Proposal not found\n\n%s", err.Error())
 92	}
 93
 94	ps := d.pss.GetStatus(dao.ProposalID(pid))
 95	out := ufmt.Sprintf("## Prop #%v - %v\n", pid, p.Title())
 96	out += "Author: " + tryResolveAddr(p.Author()) + "\n\n"
 97
 98	out += p.Description()
 99	out += "\n\n"
100
101	// Add executor metadata if available
102	if p.ExecutorString() != "" {
103		out += ufmt.Sprintf(`This proposal contains the following metadata:
104
105%s
106
107Executor created in: %s
108`, p.ExecutorString(), p.ExecutorCreationRealm())
109		out += "\n\n"
110	}
111
112	out += "\n\n---\n\n"
113	out += ps.String()
114	out += "\n"
115	out += ufmt.Sprintf("[Detailed voting list](%v:%v/votes)", ren.relativeRealmPath, pid)
116	out += "\n\n---\n\n"
117
118	out += renderActionBar(ufmt.Sprintf("%v", pid))
119
120	return out
121}
122
123func (ren *render) renderProposalListItem(sPid string, d *GovDAO) string {
124	pid, err := strconv.ParseInt(sPid, 10, 64)
125	if err != nil {
126		return ufmt.Sprintf("# Error: Invalid proposal ID format.\n\n\n%s\n\n", err.Error())
127	}
128
129	p, err := dao.GetProposal(cross, dao.ProposalID(pid))
130	if err != nil {
131		return ufmt.Sprintf("# Proposal not found\n\n%s\n\n", err.Error())
132	}
133
134	ps := d.pss.GetStatus(dao.ProposalID(pid))
135	out := ufmt.Sprintf("### [Prop #%v - %v](%v:%v)\n", pid, p.Title(), ren.relativeRealmPath, pid)
136	out += ufmt.Sprintf("Author: %s\n\n", tryResolveAddr(p.Author()))
137
138	out += "Status: " + getPropStatus(ps)
139	out += "\n\n"
140
141	out += "Tiers eligible to vote: "
142	out += strings.Join(ps.TiersAllowedToVote, ", ")
143
144	out += "\n\n"
145	return out
146}
147
148func (ren *render) renderVotesForProposal(sPid string, d *GovDAO) string {
149	pid, err := strconv.ParseInt(sPid, 10, 64)
150	if err != nil {
151		return ufmt.Sprintf("# Error: Invalid proposal ID format.\n\n\n%s\n\n", err.Error())
152	}
153
154	ps := d.pss.GetStatus(dao.ProposalID(pid))
155	if ps == nil {
156		return ufmt.Sprintf("# Proposal not found\n\nProposal %v does not exist.", pid)
157	}
158
159	out := ""
160	out += ufmt.Sprintf("# Proposal #%v - Vote List\n\n", pid)
161	out += StringifyVotes(ps)
162
163	return out
164}
165
166func isPropActive(ps *proposalStatus) bool {
167	return !ps.Accepted && !ps.Denied
168}
169
170func getPropStatus(ps *proposalStatus) string {
171	if ps == nil {
172		return "UNKNOWN"
173	}
174	if ps.Accepted {
175		return "ACCEPTED"
176	} else if ps.Denied {
177		return "REJECTED"
178	}
179	return "ACTIVE"
180}
181
182func renderActionBar(sPid string) string {
183	out := "### Actions\n"
184
185	proxy := helplink.Realm("gno.land/r/gov/dao")
186	out += proxy.Func("Vote YES", "MustVoteOnProposalSimple", "pid", sPid, "option", "YES") + " | "
187	out += proxy.Func("Vote NO", "MustVoteOnProposalSimple", "pid", sPid, "option", "NO") + " | "
188	out += proxy.Func("Vote ABSTAIN", "MustVoteOnProposalSimple", "pid", sPid, "option", "ABSTAIN")
189
190	out += "\n\n"
191	out += "WARNING: Please double check transaction data before voting."
192	return out
193}
194
195func tryResolveAddr(addr address) string {
196	userData := users.ResolveAddress(addr)
197	if userData == nil {
198		return addr.String()
199	}
200	return userData.RenderLink("")
201}