Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}