Search Apps Documentation Source Content File Folder Download Copy Actions Download

render.gno

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