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

6.61 Kb · 179 lines
  1package contest
  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 (entries, ballot and result), a
 13// single entry, the review queue, members 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] == "entries" && len(segs) == 2:
 23		id, err := strconv.ParseUint(segs[1], 10, 64)
 24		if err != nil {
 25			return notFound()
 26		}
 27		return renderEntry(id)
 28	case segs[0] == "queue" && len(segs) == 1:
 29		return renderQueue(render.Page(q, render.PageCount(entries.CountByStatus(core.SubmissionSubmitted), pageSize)))
 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	v, voting := Ballot()
 42	now := time.Now().Unix()
 43	switch {
 44	case !voting:
 45		sb.WriteString("**Taking entries.** Reviewers admit up to " + strconv.Itoa(maxEntries) + " entries; then voting opens on the admitted entries.\n\n")
 46		sb.WriteString(render.TxLink("Submit an entry", pkgPath, "Submit") + " · " +
 47			render.Link("Review queue", render.RealmURL(pkgPath, "queue")) + " (" + strconv.Itoa(entries.CountByStatus(core.SubmissionSubmitted)) + ") · " +
 48			render.TxLink("Open voting", pkgPath, "OpenVoting") + "\n\n")
 49	case v.Status == core.BallotOpen && now < v.Deadline:
 50		sb.WriteString("**Voting is open** until " + render.Text(time.Unix(v.Deadline, 0).UTC().Format(time.RFC3339)) +
 51			" · electorate " + strconv.Itoa(v.ElectorateSize) + " · ballots " + strconv.Itoa(v.Turnout) + "\n\n")
 52	case v.Status == core.BallotOpen:
 53		sb.WriteString("**Voting ended, awaiting finalization.** " + render.TxLink("Finalize result", pkgPath, "Finalize") + "\n\n")
 54	default:
 55		sb.WriteString("**Result: " + render.Text(resultLabel(v)) + "** · ballots " + strconv.Itoa(v.Turnout) + "\n\n")
 56	}
 57
 58	var rows [][]string
 59	for i, e := range Entries() {
 60		idStr := strconv.FormatUint(e.ID, 10)
 61		row := []string{
 62			render.Link("#"+idStr, render.RealmURL(pkgPath, "entries/"+idStr)),
 63			render.Text(e.Answers[titleIndex]),
 64		}
 65		if voting {
 66			row = append(row, strconv.Itoa(v.Counts[i]))
 67			if v.Status == core.BallotOpen && now < v.Deadline {
 68				row = append(row, render.TxLink("vote", pkgPath, "Vote", "entryID", idStr))
 69			} else {
 70				row = append(row, "")
 71			}
 72		}
 73		rows = append(rows, render.TrustedRow(row...))
 74	}
 75	headers := []string{"Entry", "Title"}
 76	if voting {
 77		headers = append(headers, "Votes", "")
 78	}
 79	if len(rows) == 0 {
 80		sb.WriteString("No admitted entries yet.\n\n")
 81	} else {
 82		sb.WriteString(render.Table(headers, rows))
 83	}
 84
 85	sb.WriteString("- " + render.Link("Members", render.RealmURL(pkgPath, "members")) + " (" + strconv.Itoa(members.Size()) + ") · " +
 86		render.TxLink("Join (requires an invitation)", pkgPath, "Join") + "\n")
 87	sb.WriteString("- " + render.Link("Rules", render.RealmURL(pkgPath, "rules")) + "\n\n")
 88	sb.WriteString("_Recipe " + render.Text(recipeID+"@"+recipeVersion) + ". Membership is address-based; one address is not one person._\n")
 89	return sb.String()
 90}
 91
 92func renderQueue(page int) string {
 93	list := entries.Submissions(int(core.SubmissionSubmitted), (page-1)*pageSize, pageSize)
 94	if len(list) == 0 {
 95		return render.Heading(1, "Review queue") + "Nothing to review.\n"
 96	}
 97	var rows [][]string
 98	for _, e := range list {
 99		idStr := strconv.FormatUint(e.ID, 10)
100		rows = append(rows, render.TrustedRow(
101			render.Link("#"+idStr, render.RealmURL(pkgPath, "entries/"+idStr)),
102			render.Text(e.Answers[titleIndex]),
103			reviewLinks(idStr),
104		))
105	}
106	return render.Heading(1, "Review queue") +
107		render.Table([]string{"Entry", "Title", "Review"}, rows) +
108		render.Pager(pkgPath, "queue", page, render.PageCount(entries.CountByStatus(core.SubmissionSubmitted), pageSize))
109}
110
111func renderEntry(id uint64) string {
112	v, ok := entries.Submission(id)
113	if !ok {
114		return notFound()
115	}
116	idStr := strconv.FormatUint(id, 10)
117	if v.Status == core.SubmissionRejected {
118		return render.Heading(1, "Entry #"+idStr) + "This entry was rejected.\n\n" +
119			render.Link("Back to the contest", render.RealmURL(pkgPath, "")) + "\n"
120	}
121	status := "admitted"
122	if v.Status == core.SubmissionSubmitted {
123		status = "awaiting review"
124	}
125	var sb strings.Builder
126	sb.WriteString(render.Heading(1, v.Answers[titleIndex]))
127	sb.WriteString("Status: **" + status + "** · submitted by " + render.Code(v.Submitter.String()) + "\n\n")
128	var rows [][]string
129	for i, f := range entries.Fields() {
130		if i != titleIndex && v.Answers[i] != "" {
131			rows = append(rows, render.Row(f.Label, v.Answers[i]))
132		}
133	}
134	if len(rows) > 0 {
135		sb.WriteString(render.Table([]string{"Field", "Answer"}, rows))
136	}
137	if v.Status == core.SubmissionSubmitted && ballotID == 0 {
138		sb.WriteString("Review: " + reviewLinks(idStr) + "\n\n")
139	}
140	sb.WriteString(render.Link("Back to the contest", render.RealmURL(pkgPath, "")) + "\n")
141	return sb.String()
142}
143
144func reviewLinks(idStr string) string {
145	return render.TxLink("admit", pkgPath, "Accept", "id", idStr) + " · " +
146		render.TxLink("reject", pkgPath, "Reject", "id", idStr)
147}
148
149func renderMembers(page int) string {
150	var rows [][]string
151	for _, m := range members.List((page-1)*pageSize, pageSize) {
152		rows = append(rows, render.Row(m.Address.String(), m.Role.String()))
153	}
154	return render.Heading(1, "Members") +
155		render.Table([]string{"Address", "Role"}, rows) +
156		render.Pager(pkgPath, "members", page, render.PageCount(members.Size(), pageSize))
157}
158
159func renderRules() string {
160	var rows [][]string
161	for _, r := range policy.List() {
162		rows = append(rows, render.Row(r.Op, r.Rule.String(), r.Floor.String()))
163	}
164	rows = append(rows, render.Row("contest.vote", "electorate frozen when voting opens (members)", "-"))
165	return render.Heading(1, "Rules") +
166		render.Table([]string{"Operation", "Who may", "Most permissive allowed"}, rows) +
167		"Up to 50 pending or admitted submissions, at most 1 per address; at most 10 admitted entries. Review decisions are final; rejecting an entry frees its slot. A tie has no winner." + "\n"
168}
169
170func resultLabel(v core.BallotView) string {
171	if w, ok := Winner(); ok {
172		return "winner: " + v.Options[v.Winner] + " (entry " + strconv.FormatUint(w, 10) + ")"
173	}
174	return v.Outcome.String()
175}
176
177func notFound() string {
178	return render.Heading(1, "Not found") + render.Link("Back to the app", render.RealmURL(pkgPath, "")) + "\n"
179}