Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

render.gno

5.24 Kb · 148 lines
  1package clubpoll
  2
  3import (
  4	"strconv"
  5	"strings"
  6	"time"
  7
  8	"gno.land/p/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/remixlab/core/v1"
  9	"gno.land/p/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/remixlab/render/v1"
 10)
 11
 12// Render serves the native pages: overview, polls, a single poll, members
 13// and rules.
 14func Render(path string) string {
 15	if members == nil {
 16		return render.Heading(1, appTitle) + "This app is not initialized.\n"
 17	}
 18	segs, q := render.ParsePath(path)
 19	switch {
 20	case len(segs) == 0:
 21		return renderHome()
 22	case segs[0] == "polls" && len(segs) == 1:
 23		return renderPolls(render.Page(q, render.PageCount(polls.Count(), pageSize)))
 24	case segs[0] == "polls" && len(segs) == 2:
 25		id, err := strconv.ParseUint(segs[1], 10, 64)
 26		if err != nil {
 27			return notFound()
 28		}
 29		return renderPoll(id)
 30	case segs[0] == "members" && len(segs) == 1:
 31		return renderMembers(render.Page(q, render.PageCount(members.Size(), pageSize)))
 32	case segs[0] == "rules" && len(segs) == 1:
 33		return renderRules()
 34	}
 35	return notFound()
 36}
 37
 38func renderHome() string {
 39	var sb strings.Builder
 40	sb.WriteString(render.Heading(1, appTitle))
 41	sb.WriteString("Members vote in single-choice polls. Each poll's electorate is frozen when it opens.\n\n")
 42	sb.WriteString("- " + render.Link("Polls", render.RealmURL(pkgPath, "polls")) + " (" + strconv.Itoa(polls.Count()) + ")\n")
 43	sb.WriteString("- " + render.Link("Members", render.RealmURL(pkgPath, "members")) + " (" + strconv.Itoa(members.Size()) + ")\n")
 44	sb.WriteString("- " + render.Link("Rules", render.RealmURL(pkgPath, "rules")) + "\n\n")
 45	sb.WriteString(render.TxLink("Join (requires an invitation)", pkgPath, "Join") + "\n\n")
 46	sb.WriteString(renderPollTable(polls.List(0, 5)))
 47	sb.WriteString("_Recipe " + render.Text(recipeID+"@"+recipeVersion) + ". Membership is address-based; one address is not one person._\n")
 48	return sb.String()
 49}
 50
 51func renderPolls(page int) string {
 52	pages := render.PageCount(polls.Count(), pageSize)
 53	return render.Heading(1, "Polls") +
 54		renderPollTable(polls.List((page-1)*pageSize, pageSize)) +
 55		render.Pager(pkgPath, "polls", page, pages)
 56}
 57
 58func renderPollTable(list []core.BallotView) string {
 59	if len(list) == 0 {
 60		return "No polls yet.\n\n"
 61	}
 62	var rows [][]string
 63	for _, v := range list {
 64		rows = append(rows, render.TrustedRow(
 65			render.Link("#"+strconv.FormatUint(v.ID, 10), render.RealmURL(pkgPath, "polls/"+strconv.FormatUint(v.ID, 10))),
 66			render.Text(v.Title),
 67			render.Text(statusLabel(v, time.Now().Unix())),
 68			strconv.Itoa(v.Turnout),
 69		))
 70	}
 71	return render.Table([]string{"Poll", "Title", "Status", "Ballots"}, rows)
 72}
 73
 74func renderPoll(id uint64) string {
 75	v, ok := polls.Ballot(id)
 76	if !ok {
 77		return notFound()
 78	}
 79	now := time.Now().Unix()
 80	idStr := strconv.FormatUint(id, 10)
 81	var sb strings.Builder
 82	sb.WriteString(render.Heading(1, v.Title))
 83	sb.WriteString("Status: **" + render.Text(statusLabel(v, now)) + "**")
 84	if v.Status != core.BallotDraft {
 85		sb.WriteString(" · electorate " + strconv.Itoa(v.ElectorateSize) + " · ballots " + strconv.Itoa(v.Turnout))
 86		sb.WriteString(" · deadline " + render.Text(time.Unix(v.Deadline, 0).UTC().Format(time.RFC3339)))
 87	}
 88	sb.WriteString("\n\n")
 89
 90	var rows [][]string
 91	for i, opt := range v.Options {
 92		action := ""
 93		if v.Status == core.BallotOpen && now < v.Deadline {
 94			action = render.TxLink("vote", pkgPath, "Vote", "id", idStr, "choice", strconv.Itoa(i))
 95		}
 96		rows = append(rows, render.TrustedRow(render.Text(opt), strconv.Itoa(v.Counts[i]), action))
 97	}
 98	sb.WriteString(render.Table([]string{"Option", "Votes", ""}, rows))
 99
100	switch {
101	case v.Status == core.BallotDraft:
102		sb.WriteString(render.TxLink("Open this poll", pkgPath, "OpenPoll", "id", idStr) + "\n\n")
103	case v.Status == core.BallotOpen && now >= v.Deadline:
104		sb.WriteString(render.TxLink("Finalize result", pkgPath, "Finalize", "id", idStr) + "\n\n")
105	case v.Status == core.BallotFinalized:
106		sb.WriteString("Result: **" + render.Text(outcomeLabel(v)) + "**\n\n")
107	}
108	sb.WriteString(render.Link("All polls", render.RealmURL(pkgPath, "polls")) + "\n")
109	return sb.String()
110}
111
112func renderMembers(page int) string {
113	var rows [][]string
114	for _, m := range members.List((page-1)*pageSize, pageSize) {
115		rows = append(rows, render.Row(m.Address.String(), m.Role.String()))
116	}
117	return render.Heading(1, "Members") +
118		render.Table([]string{"Address", "Role"}, rows) +
119		render.Pager(pkgPath, "members", page, render.PageCount(members.Size(), pageSize))
120}
121
122func renderRules() string {
123	var rows [][]string
124	for _, r := range policy.List() {
125		rows = append(rows, render.Row(r.Op, r.Rule.String(), r.Floor.String()))
126	}
127	rows = append(rows, render.Row("poll.vote", "electorate frozen at opening (members)", "-"))
128	return render.Heading(1, "Rules") +
129		render.Table([]string{"Operation", "Who may", "Most permissive allowed"}, rows)
130}
131
132func statusLabel(v core.BallotView, now int64) string {
133	if v.Status == core.BallotOpen && now >= v.Deadline {
134		return "voting ended, awaiting finalization"
135	}
136	return v.Status.String()
137}
138
139func outcomeLabel(v core.BallotView) string {
140	if v.Outcome == core.OutcomeWinner && v.Winner >= 0 && v.Winner < len(v.Options) {
141		return "winner: " + v.Options[v.Winner]
142	}
143	return v.Outcome.String()
144}
145
146func notFound() string {
147	return render.Heading(1, "Not found") + render.Link("Back to the app", render.RealmURL(pkgPath, "")) + "\n"
148}