Search Apps Documentation Source Content File Folder Download Copy Actions Download

render.gno

4.80 Kb ยท 191 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/pager"
 10	"gno.land/p/nt/mux"
 11	"gno.land/p/nt/seqid"
 12	"gno.land/p/nt/ufmt"
 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	out += "\n\n---\n\n"
102	out += ps.String()
103	out += "\n"
104	out += ufmt.Sprintf("[Detailed voting list](%v:%v/votes)", ren.relativeRealmPath, pid)
105	out += "\n\n---\n\n"
106
107	out += renderActionBar(ufmt.Sprintf("%v", pid))
108
109	return out
110}
111
112func (ren *render) renderProposalListItem(sPid string, d *GovDAO) string {
113	pid, err := strconv.ParseInt(sPid, 10, 64)
114	if err != nil {
115		return ufmt.Sprintf("# Error: Invalid proposal ID format.\n\n\n%s\n\n", err.Error())
116	}
117
118	p, err := dao.GetProposal(cross, dao.ProposalID(pid))
119	if err != nil {
120		return ufmt.Sprintf("# Proposal not found\n\n%s\n\n", err.Error())
121	}
122
123	ps := d.pss.GetStatus(dao.ProposalID(pid))
124	out := ufmt.Sprintf("### [Prop #%v - %v](%v:%v)\n", pid, p.Title(), ren.relativeRealmPath, pid)
125	out += ufmt.Sprintf("Author: %s\n\n", tryResolveAddr(p.Author()))
126
127	out += "Status: " + getPropStatus(ps)
128	out += "\n\n"
129
130	out += "Tiers eligible to vote: "
131	out += strings.Join(ps.TiersAllowedToVote, ", ")
132
133	out += "\n\n"
134	return out
135}
136
137func (ren *render) renderVotesForProposal(sPid string, d *GovDAO) string {
138	pid, err := strconv.ParseInt(sPid, 10, 64)
139	if err != nil {
140		return ufmt.Sprintf("# Error: Invalid proposal ID format.\n\n\n%s\n\n", err.Error())
141	}
142
143	ps := d.pss.GetStatus(dao.ProposalID(pid))
144	if ps == nil {
145		return ufmt.Sprintf("# Proposal not found\n\nProposal %v does not exist.", pid)
146	}
147
148	out := ""
149	out += ufmt.Sprintf("# Proposal #%v - Vote List\n\n", pid)
150	out += StringifyVotes(ps)
151
152	return out
153}
154
155func isPropActive(ps *proposalStatus) bool {
156	return !ps.Accepted && !ps.Denied
157}
158
159func getPropStatus(ps *proposalStatus) string {
160	if ps == nil {
161		return "UNKNOWN"
162	}
163	if ps.Accepted {
164		return "ACCEPTED"
165	} else if ps.Denied {
166		return "REJECTED"
167	}
168	return "ACTIVE"
169}
170
171func renderActionBar(sPid string) string {
172	out := "### Actions\n"
173
174	proxy := helplink.Realm("gno.land/r/gov/dao")
175	out += proxy.Func("Vote YES", "MustVoteOnProposalSimple", "pid", sPid, "option", "YES") + " | "
176	out += proxy.Func("Vote NO", "MustVoteOnProposalSimple", "pid", sPid, "option", "NO") + " | "
177	out += proxy.Func("Vote ABSTAIN", "MustVoteOnProposalSimple", "pid", sPid, "option", "ABSTAIN")
178
179	out += "\n\n"
180	out += "WARNING: Please double check transaction data before voting.\n\n"
181	out += "**New**: Members **must** have a namespace to vote to enforce simpler tracking."
182	return out
183}
184
185func tryResolveAddr(addr address) string {
186	userData := users.ResolveAddress(addr)
187	if userData == nil {
188		return addr.String()
189	}
190	return userData.RenderLink("")
191}