render.gno
6.89 Kb · 267 lines
1package personal_world
2
3import (
4 "net/url"
5
6 "gno.land/p/jeronimoalbi/pager"
7 "gno.land/p/nt/ufmt"
8 "gno.land/r/akkadia/admin"
9)
10
11// Render handles RESTful routing and returns Markdown responses
12func Render(iUrl string) string {
13 u, err := url.Parse(iUrl)
14 if err != nil {
15 return "404\n"
16 }
17
18 query := u.Query()
19
20 switch u.Path {
21 case "":
22 return renderHome()
23 case "worlds":
24 owner := query.Get("owner")
25 if owner != "" {
26 return renderWorldsByOwner(iUrl, owner)
27 }
28 return renderWorldsList(iUrl)
29 case "biomes":
30 return renderBiomes()
31 case "sizes":
32 return renderSizes()
33 default:
34 return renderError("Not found")
35 }
36}
37
38// renderHome renders the home page
39func renderHome() string {
40 output := `# Personal World
41
42Welcome to the Personal World system on Akkadia.
43
44## What is Personal World?
45
46Personal World is a decentralized world creation and management system where users can:
47
48* Create and customize their own worlds
49* Manage world editors and permissions
50* Expand world sizes dynamically
51* Set custom world types and properties
52
53## Getting Started
54
551. **Create Your First World**: Your first default-type world is **free**!
562. **Customize**: Set name, slug and visibility
573. **Manage**: Add editors, expand size, update properties
584. **Explore**: Browse all worlds and discover unique creations
59
60## Config
61
62`
63 output += ufmt.Sprintf("* **Default Biome**: %s\n", defaultBiome)
64 output += ufmt.Sprintf("* **Fee Collector BPS**: %d (%d.%02d%%)\n", feeCollectorBPS, feeCollectorBPS/100, feeCollectorBPS%100)
65 output += ufmt.Sprintf("* **List Limit**: %d\n", listLimit)
66 output += ufmt.Sprintf("* **Batch Limit**: %d\n", batchLimit)
67
68 output += `
69## Stats
70
71`
72 output += ufmt.Sprintf("* **Total Worlds**: %d\n", worlds.Size())
73
74 output += `
75## Quick Links
76
77* [Browse All Worlds](/r/akkadia/personal_world:worlds)
78* [View Biomes](/r/akkadia/personal_world:biomes)
79* [View Sizes](/r/akkadia/personal_world:sizes)
80
81## Search by Owner
82
83<gno-form path="worlds">
84 <gno-input name="owner" placeholder="Enter owner address" />
85</gno-form>
86`
87
88 return output
89}
90
91// renderBiomes renders the biomes page
92func renderBiomes() string {
93 output := `# Biomes
94
95[← Home](/r/akkadia/personal_world:)
96
97The system supports multiple biomes with different creation costs.
98
99## How Pricing Works
100
101* **cost**: The base cost to create a world with this biome
102* **priceMultiplier**: Multiplier applied to world expansion costs
103 * Example: If expansion base cost is 1000 ugnot and priceMultiplier is 1.5, actual cost = 1500 ugnot
104
105---
106
107`
108 biomeInfos.Iterate("", "", func(key string, value interface{}) bool {
109 biome := value.(map[string]string)
110
111 output += ufmt.Sprintf("### %s\n\n", biome["name"])
112
113 for k, v := range biome {
114 if k == "name" {
115 if v == defaultBiome {
116 output += ufmt.Sprintf("* **%s**: %s (default)\n", k, v)
117 } else {
118 output += ufmt.Sprintf("* **%s**: %s\n", k, v)
119 }
120 } else if k == "cost" {
121 output += ufmt.Sprintf("* **%s**: %s ugnot\n", k, v)
122 } else {
123 output += ufmt.Sprintf("* **%s**: %s\n", k, v)
124 }
125 }
126 output += "\n---\n\n"
127 return false
128 })
129
130 return output
131}
132
133// renderSizes renders the sizes page
134func renderSizes() string {
135 output := `# World Sizes
136
137[← Home](/r/akkadia/personal_world:)
138
139Worlds can be expanded to larger sizes. Each expansion has a base cost.
140
141## How Expansion Works
142
143* **size**: The world dimension (size x size blocks)
144* **cost**: Base cost to expand to this size level
145* Actual expansion cost = base cost × biome priceMultiplier
146* New worlds start at size level 0
147
148---
149
150`
151 sizeInfos.Iterate("", "", func(key string, value interface{}) bool {
152 size := value.(map[string]string)
153
154 output += ufmt.Sprintf("### Level %s\n\n", size["id"])
155 output += ufmt.Sprintf("* **Size**: %s × %s blocks\n", size["size"], size["size"])
156 output += ufmt.Sprintf("* **Base Cost**: %s ugnot\n", size["cost"])
157 output += "\n---\n\n"
158 return false
159 })
160
161 return output
162}
163
164// renderWorldsList renders a list of all worlds with pagination
165func renderWorldsList(iUrl string) string {
166 output := `# All Worlds
167
168[← Home](/r/akkadia/personal_world:)
169
170`
171
172 if worlds.Size() == 0 {
173 output += "*No worlds created yet.*\n"
174 return output
175 }
176
177 pages, err := pager.New(iUrl, worlds.Size(), pager.WithPageSize(10))
178 if err != nil {
179 return renderError("Invalid page")
180 }
181
182 // Get current page number for back links
183 currentPage := (pages.Offset() / pages.PageSize()) + 1
184
185 worlds.ReverseIterateByOffset(pages.Offset(), pages.PageSize(), func(key string, value interface{}) bool {
186 world := value.(map[string]string)
187 output += renderWorldItem(world, currentPage)
188 return false
189 })
190
191 if pages.HasPages() {
192 output += pager.Picker(pages)
193 }
194
195 return output
196}
197
198// renderWorldsByOwner renders worlds owned by a specific address with pagination
199func renderWorldsByOwner(iUrl string, owner string) string {
200 u, _ := url.Parse(iUrl)
201 page := u.Query().Get("page")
202
203 output := ufmt.Sprintf("# Personal Worlds by %s\n\n", owner)
204 if page != "" {
205 output += ufmt.Sprintf("[← Back to List](/r/akkadia/personal_world:worlds?page=%s)\n\n", page)
206 } else {
207 output += "[← Home](/r/akkadia/personal_world:)\n\n"
208 }
209
210 // Collect worlds owned by the specified owner
211 var ownerWorlds []map[string]string
212 worlds.Iterate("", "", func(key string, value interface{}) bool {
213 world := value.(map[string]string)
214 if world["owner"] == owner {
215 ownerWorlds = append(ownerWorlds, world)
216 }
217 return false
218 })
219
220 if len(ownerWorlds) == 0 {
221 output += "*No worlds found for this owner.*\n"
222 return output
223 }
224
225 pages, err := pager.New(iUrl, len(ownerWorlds), pager.WithPageSize(10))
226 if err != nil {
227 return renderError("Invalid page")
228 }
229
230 // Render paginated slice (reverse order - newest first)
231 start := len(ownerWorlds) - pages.Offset() - 1
232 end := start - pages.PageSize()
233 if end < -1 {
234 end = -1
235 }
236
237 for i := start; i > end; i-- {
238 output += renderWorldItem(ownerWorlds[i], 0)
239 }
240
241 if pages.HasPages() {
242 output += pager.Picker(pages)
243 }
244
245 return output
246}
247
248// renderWorldItem renders a single world item
249func renderWorldItem(world map[string]string, page int) string {
250 output := ""
251 output += ufmt.Sprintf("* **ID**: %s\n", world["id"])
252 output += ufmt.Sprintf("* **Name**: %s\n", world["name"])
253 output += ufmt.Sprintf("* **Owner**: %s [[View on Explorer](%s/m/explorer/player?address=%s)] [[Worlds by Owner](/r/akkadia/personal_world:worlds?owner=%s&page=%d)]\n", world["owner"], admin.GetExplorerURL(), world["owner"], world["owner"], page)
254 output += ufmt.Sprintf("* **Biome**: %s\n", world["biome"])
255 output += ufmt.Sprintf("* **Size**: %s\n", world["size"])
256 output += ufmt.Sprintf("* **Slug**: `%s` [[View on Explorer](%s/m/explorer/community-realm?slug=%s)]\n", world["slug"], admin.GetExplorerURL(), world["slug"])
257 output += "\n---\n\n"
258 return output
259}
260
261// renderError renders an error page
262func renderError(message string) string {
263 output := "# Error\n\n"
264 output += ufmt.Sprintf("> %s\n\n", message)
265 output += "[← Back to Home](/r/akkadia/personal_world:)\n"
266 return output
267}