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}