render.gno
9.39 Kb · 324 lines
1package chunk
2
3import (
4 "net/url"
5 "strconv"
6 "strings"
7
8 "gno.land/p/demo/tokens/grc721"
9 "gno.land/p/jeronimoalbi/pager"
10 "gno.land/p/nt/ufmt"
11 "gno.land/r/akkadia/admin"
12)
13
14// Render
15func Render(iUrl string) string {
16 u, err := url.Parse(iUrl)
17 if err != nil {
18 return "404\n"
19 }
20
21 path := u.Path
22 query := u.Query()
23
24 switch {
25 case path == "":
26 return renderHome()
27 case path == "chunk":
28 id := query.Get("id")
29 if id == "" {
30 return renderChunkForm()
31 }
32 return renderChunk(iUrl, id)
33 case path == "chunks":
34 owner := query.Get("owner")
35 if owner != "" {
36 return renderChunksByOwner(iUrl, owner)
37 }
38 return renderChunksList(iUrl)
39 case path == "worlds":
40 return renderWorldsList(iUrl)
41 case strings.HasPrefix(path, "worlds/") && strings.HasSuffix(path, "/chunks"):
42 worldIDStr := strings.TrimPrefix(path, "worlds/")
43 worldIDStr = strings.TrimSuffix(worldIDStr, "/chunks")
44 worldID, err := strconv.ParseUint(worldIDStr, 10, 32)
45 if err != nil {
46 return "404\n"
47 }
48 return renderChunksByWorld(iUrl, uint32(worldID))
49 default:
50 return "404\n"
51 }
52}
53
54func renderHome() string {
55 output := `# Chunk
56
57Welcome to the Chunk management system on Akkadia.
58
59## What is Chunk?
60
61Chunk is an NFT-based land ownership system where each chunk represents a unique piece of land in the game world:
62
63* **GRC721 NFT**: Each chunk is a unique token with ownership and transferability
64* **World-based**: Chunks belong to specific worlds with coordinates (x, y)
65* **Metadata Storage**: Each chunk stores custom metadata for game state
66* **Role-based Access**: Permissions control who can mint, burn, and modify chunks
67
68## How Chunks Work
69
701. **World Creation**: Admins create worlds with biome, name, slug, and seed
712. **Chunk Minting**: Authorized users mint chunks at specific coordinates
723. **Metadata Management**: Chunk owners can update metadata for game state
734. **Ownership Transfer**: Chunks can be transferred between users as NFTs
74
75## Config
76
77`
78 output += ufmt.Sprintf("* **List Limit**: %d\n", listLimit)
79 output += ufmt.Sprintf("* **Batch Limit**: %d\n", batchLimit)
80
81 output += `
82## Stats
83
84`
85 output += ufmt.Sprintf("* **Total Chunks**: %d\n", TokenCount())
86 output += ufmt.Sprintf("* **Total Worlds**: %d\n", GetTotalWorldSize())
87
88 output += `
89## Quick Links
90
91* [Browse Chunks](/r/akkadia/chunk:worlds) (select a world first)
92
93## Search by Owner
94
95<gno-form path="chunks">
96 <gno-input name="owner" placeholder="Enter owner address" />
97</gno-form>
98`
99
100 return output
101}
102
103func renderChunkForm() string {
104 return `# View Chunk
105
106[← Home](/r/akkadia/chunk:)
107
108<gno-form path="chunk">
109 <gno-input name="id" placeholder="Enter chunk ID (e.g., 1:0_0)" />
110</gno-form>
111`
112}
113
114func renderChunk(iUrl string, id string) string {
115 tokenID := grc721.TokenID(id)
116 meta := GetMetadata(tokenID)
117 if meta == nil {
118 return ufmt.Sprintf("# Chunk Not Found\n\n[← Home](/r/akkadia/chunk:)\n\nChunk `%s` does not exist.\n", id)
119 }
120
121 u, _ := url.Parse(iUrl)
122 query := u.Query()
123 worldIDParam := query.Get("worldId")
124 pageParam := query.Get("page")
125
126 output := ufmt.Sprintf("# Chunk %s\n\n", id)
127 output += ufmt.Sprintf("[[View on Explorer](%s/m/explorer/chunk?id=%s)]\n\n", admin.GetExplorerURL(), id)
128
129 if worldIDParam != "" {
130 backLink := "/r/akkadia/chunk:worlds/" + worldIDParam + "/chunks"
131 if pageParam != "" {
132 backLink += "?page=" + pageParam
133 }
134 output += ufmt.Sprintf("[← Back to World %s](%s)\n\n", worldIDParam, backLink)
135 } else {
136 output += "[← Home](/r/akkadia/chunk:)\n\n"
137 }
138
139 owner := OwnerOf(tokenID)
140 output += ufmt.Sprintf("* **Owner**: [%s](/r/akkadia/chunk:chunks?owner=%s) [[View on Explorer](%s/m/explorer/player?address=%s)]\n", owner, owner, admin.GetExplorerURL(), owner)
141
142 worldID := meta["worldId"]
143 output += ufmt.Sprintf("* **World**: [%s](/r/akkadia/chunk:worlds/%s/chunks)\n", worldID, worldID)
144 output += ufmt.Sprintf("* **Coordinates**: (%s, %s)\n", meta["x"], meta["y"])
145 output += ufmt.Sprintf("* **World Type**: %s\n", meta["worldType"])
146 output += ufmt.Sprintf("* **Hash**: %s\n", meta["hash"])
147
148 return output
149}
150
151// renderChunksList renders a list of chunks for a specific world with pagination
152// Requires worldId query parameter for efficient pagination
153func renderChunksList(iUrl string) string {
154 u, _ := url.Parse(iUrl)
155 worldIdStr := u.Query().Get("worldId")
156
157 if worldIdStr == "" {
158 return `# All Chunks
159
160Please select a world to view chunks:
161
162[Browse Worlds](/r/akkadia/chunk:worlds)
163`
164 }
165
166 worldID, err := strconv.ParseUint(worldIdStr, 10, 32)
167 if err != nil {
168 return "404\n"
169 }
170
171 output := ufmt.Sprintf("# Chunks in World %d\n\n", worldID)
172 output += "[← Back to Worlds](/r/akkadia/chunk:worlds)\n\n"
173
174 chunkCount := GetWorldMetadataSize(uint32(worldID))
175 if chunkCount == 0 {
176 output += "*No chunks in this world.*\n"
177 return output
178 }
179
180 output += ufmt.Sprintf("**Total Chunks**: %d\n\n", chunkCount)
181
182 pages, err2 := pager.New(iUrl, chunkCount, pager.WithPageSize(10))
183 if err2 != nil {
184 return output + "Invalid page"
185 }
186
187 currentPage := (pages.Offset() / pages.PageSize()) + 1
188 chunks := ListMetadataByWorld(uint32(worldID), currentPage, pages.PageSize())
189
190 for _, chunk := range chunks {
191 key := chunk["id"]
192 output += ufmt.Sprintf("### [%s](/r/akkadia/chunk:chunk?id=%s) [[View on Explorer](%s/m/explorer/chunk?id=%s)]\n\n", key, key, admin.GetExplorerURL(), key)
193 owner := OwnerOf(grc721.TokenID(key))
194 output += ufmt.Sprintf("* **Owner**: %s [[View on Explorer](%s/m/explorer/player?address=%s)] [[Chunks by Owner](/r/akkadia/chunk:chunks?owner=%s)]\n", owner, admin.GetExplorerURL(), owner, owner)
195 for k, v := range chunk {
196 output += ufmt.Sprintf("* **%s**: %s\n", k, v)
197 }
198 output += "\n---\n\n"
199 }
200
201 if pages.HasPages() {
202 output += pager.Picker(pages)
203 }
204
205 return output
206}
207
208// renderChunksByWorld renders chunks belonging to a specific world with pagination
209func renderChunksByWorld(iUrl string, worldID uint32) string {
210 output := ufmt.Sprintf("# Chunks in World %d\n\n", worldID)
211 output += "[← Back to Worlds](/r/akkadia/chunk:worlds)\n\n"
212
213 chunkCount := GetWorldMetadataSize(worldID)
214 if chunkCount == 0 {
215 output += "*No chunks found in this world.*\n"
216 return output
217 }
218
219 output += ufmt.Sprintf("**Total Chunks**: %d\n\n", chunkCount)
220
221 pages, err := pager.New(iUrl, chunkCount, pager.WithPageSize(10))
222 if err != nil {
223 return output + "Invalid page"
224 }
225
226 currentPage := (pages.Offset() / pages.PageSize()) + 1
227 chunks := ListMetadataByWorld(worldID, currentPage, pages.PageSize())
228
229 for _, chunk := range chunks {
230 key := chunk["id"]
231 output += ufmt.Sprintf("### [%s](/r/akkadia/chunk:chunk?id=%s&worldId=%d&page=%d) [[View on Explorer](%s/m/explorer/chunk?id=%s)]\n\n", key, key, worldID, currentPage, admin.GetExplorerURL(), key)
232 owner := OwnerOf(grc721.TokenID(key))
233 output += ufmt.Sprintf("* **Owner**: %s [[View on Explorer](%s/m/explorer/player?address=%s)] [[Chunks by Owner](/r/akkadia/chunk:chunks?owner=%s)]\n", owner, admin.GetExplorerURL(), owner, owner)
234 for k, v := range chunk {
235 output += ufmt.Sprintf("* **%s**: %s\n", k, v)
236 }
237 output += "\n---\n\n"
238 }
239
240 if pages.HasPages() {
241 output += pager.Picker(pages)
242 }
243
244 return output
245}
246
247// renderChunksByOwner renders chunks owned by a specific address with pagination
248func renderChunksByOwner(iUrl string, owner string) string {
249 output := ufmt.Sprintf("# Chunks by %s\n\n", owner)
250 output += "[← Home](/r/akkadia/chunk:)\n\n"
251
252 ownerAddr := address(owner)
253 chunkCount := GetOwnerTokenSize(ownerAddr)
254
255 if chunkCount == 0 {
256 output += "*No chunks found for this owner.*\n"
257 return output
258 }
259
260 output += ufmt.Sprintf("**Total Chunks**: %d\n\n", chunkCount)
261
262 pages, err := pager.New(iUrl, chunkCount, pager.WithPageSize(10))
263 if err != nil {
264 return output + "Invalid page"
265 }
266
267 currentPage := (pages.Offset() / pages.PageSize()) + 1
268 ownerChunks := ListMetadataByOwner(ownerAddr, currentPage, pages.PageSize())
269
270 for _, chunk := range ownerChunks {
271 tid := chunk["id"]
272 output += ufmt.Sprintf("### [%s](/r/akkadia/chunk:chunk?id=%s) [[View on Explorer](%s/m/explorer/chunk?id=%s)]\n\n", tid, tid, admin.GetExplorerURL(), tid)
273 for k, v := range chunk {
274 output += ufmt.Sprintf("* **%s**: %s\n", k, v)
275 }
276 output += "\n---\n\n"
277 }
278
279 if pages.HasPages() {
280 output += pager.Picker(pages)
281 }
282
283 return output
284}
285
286// renderWorldsList renders a list of all worlds with pagination
287func renderWorldsList(iUrl string) string {
288 output := `# All System Worlds
289
290[← Home](/r/akkadia/chunk:)
291
292`
293 if GetTotalWorldSize() == 0 {
294 output += "*No worlds created yet.*\n"
295 return output
296 }
297
298 pages, err := pager.New(iUrl, GetTotalWorldSize(), pager.WithPageSize(10))
299 if err != nil {
300 return output + "Invalid page"
301 }
302
303 worlds.ReverseIterateByOffset(pages.Offset(), pages.PageSize(), func(key string, value interface{}) bool {
304 world := value.(map[string]string)
305 worldIDStr := world["id"]
306 worldID, _ := strconv.ParseUint(worldIDStr, 10, 32)
307 chunkCount := GetWorldMetadataSize(uint32(worldID))
308
309 output += ufmt.Sprintf("* **ID**: %s\n", worldIDStr)
310 output += ufmt.Sprintf("* **Name**: %s\n", world["name"])
311 output += ufmt.Sprintf("* **Biome**: %s\n", world["biome"])
312 output += ufmt.Sprintf("* **Seed**: %s\n", world["seed"])
313 output += ufmt.Sprintf("* **Slug**: `%s` [[View on Explorer](%s/m/explorer/primary-realm?slug=%s)]\n", world["slug"], admin.GetExplorerURL(), world["slug"])
314 output += ufmt.Sprintf("* **Chunks**: %d [[View Chunks](/r/akkadia/chunk:worlds/%s/chunks)]\n", chunkCount, worldIDStr)
315 output += "\n---\n\n"
316 return false
317 })
318
319 if pages.HasPages() {
320 output += pager.Picker(pages)
321 }
322
323 return output
324}