package chunk
import (
"net/url"
"strconv"
"strings"
"gno.land/p/demo/tokens/grc721"
"gno.land/p/jeronimoalbi/pager"
"gno.land/p/nt/ufmt"
"gno.land/r/akkadia/admin"
)
// Render
func Render(iUrl string) string {
u, err := url.Parse(iUrl)
if err != nil {
return "404\n"
}
path := u.Path
query := u.Query()
switch {
case path == "":
return renderHome()
case path == "chunk":
id := query.Get("id")
if id == "" {
return renderChunkForm()
}
return renderChunk(iUrl, id)
case path == "chunks":
owner := query.Get("owner")
if owner != "" {
return renderChunksByOwner(iUrl, owner)
}
return renderChunksList(iUrl)
case path == "worlds":
return renderWorldsList(iUrl)
case strings.HasPrefix(path, "worlds/") && strings.HasSuffix(path, "/chunks"):
worldIDStr := strings.TrimPrefix(path, "worlds/")
worldIDStr = strings.TrimSuffix(worldIDStr, "/chunks")
worldID, err := strconv.ParseUint(worldIDStr, 10, 32)
if err != nil {
return "404\n"
}
return renderChunksByWorld(iUrl, uint32(worldID))
default:
return "404\n"
}
}
func renderHome() string {
output := `# Chunk
Welcome to the Chunk management system on Akkadia.
## What is Chunk?
Chunk is an NFT-based land ownership system where each chunk represents a unique piece of land in the game world:
* **GRC721 NFT**: Each chunk is a unique token with ownership and transferability
* **World-based**: Chunks belong to specific worlds with coordinates (x, y)
* **Metadata Storage**: Each chunk stores custom metadata for game state
* **Role-based Access**: Permissions control who can mint, burn, and modify chunks
## How Chunks Work
1. **World Creation**: Admins create worlds with biome, name, slug, and seed
2. **Chunk Minting**: Authorized users mint chunks at specific coordinates
3. **Metadata Management**: Chunk owners can update metadata for game state
4. **Ownership Transfer**: Chunks can be transferred between users as NFTs
## Config
`
output += ufmt.Sprintf("* **List Limit**: %d\n", listLimit)
output += ufmt.Sprintf("* **Batch Limit**: %d\n", batchLimit)
output += `
## Stats
`
output += ufmt.Sprintf("* **Total Chunks**: %d\n", TokenCount())
output += ufmt.Sprintf("* **Total Worlds**: %d\n", GetTotalWorldSize())
output += `
## Quick Links
* [Browse Chunks](/r/akkadia/chunk:worlds) (select a world first)
## Search by Owner
`
return output
}
func renderChunkForm() string {
return `# View Chunk
[← Home](/r/akkadia/chunk:)
`
}
func renderChunk(iUrl string, id string) string {
tokenID := grc721.TokenID(id)
meta := GetMetadata(tokenID)
if meta == nil {
return ufmt.Sprintf("# Chunk Not Found\n\n[← Home](/r/akkadia/chunk:)\n\nChunk `%s` does not exist.\n", id)
}
u, _ := url.Parse(iUrl)
query := u.Query()
worldIDParam := query.Get("worldId")
pageParam := query.Get("page")
output := ufmt.Sprintf("# Chunk %s\n\n", id)
output += ufmt.Sprintf("[[View on Explorer](%s/m/explorer/chunk?id=%s)]\n\n", admin.GetExplorerURL(), id)
if worldIDParam != "" {
backLink := "/r/akkadia/chunk:worlds/" + worldIDParam + "/chunks"
if pageParam != "" {
backLink += "?page=" + pageParam
}
output += ufmt.Sprintf("[← Back to World %s](%s)\n\n", worldIDParam, backLink)
} else {
output += "[← Home](/r/akkadia/chunk:)\n\n"
}
owner := OwnerOf(tokenID)
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)
worldID := meta["worldId"]
output += ufmt.Sprintf("* **World**: [%s](/r/akkadia/chunk:worlds/%s/chunks)\n", worldID, worldID)
output += ufmt.Sprintf("* **Coordinates**: (%s, %s)\n", meta["x"], meta["y"])
output += ufmt.Sprintf("* **World Type**: %s\n", meta["worldType"])
output += ufmt.Sprintf("* **Hash**: %s\n", meta["hash"])
return output
}
// renderChunksList renders a list of chunks for a specific world with pagination
// Requires worldId query parameter for efficient pagination
func renderChunksList(iUrl string) string {
u, _ := url.Parse(iUrl)
worldIdStr := u.Query().Get("worldId")
if worldIdStr == "" {
return `# All Chunks
Please select a world to view chunks:
[Browse Worlds](/r/akkadia/chunk:worlds)
`
}
worldID, err := strconv.ParseUint(worldIdStr, 10, 32)
if err != nil {
return "404\n"
}
output := ufmt.Sprintf("# Chunks in World %d\n\n", worldID)
output += "[← Back to Worlds](/r/akkadia/chunk:worlds)\n\n"
chunkCount := GetWorldMetadataSize(uint32(worldID))
if chunkCount == 0 {
output += "*No chunks in this world.*\n"
return output
}
output += ufmt.Sprintf("**Total Chunks**: %d\n\n", chunkCount)
pages, err2 := pager.New(iUrl, chunkCount, pager.WithPageSize(10))
if err2 != nil {
return output + "Invalid page"
}
currentPage := (pages.Offset() / pages.PageSize()) + 1
chunks := ListMetadataByWorld(uint32(worldID), currentPage, pages.PageSize())
for _, chunk := range chunks {
key := chunk["id"]
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)
owner := OwnerOf(grc721.TokenID(key))
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)
for k, v := range chunk {
output += ufmt.Sprintf("* **%s**: %s\n", k, v)
}
output += "\n---\n\n"
}
if pages.HasPages() {
output += pager.Picker(pages)
}
return output
}
// renderChunksByWorld renders chunks belonging to a specific world with pagination
func renderChunksByWorld(iUrl string, worldID uint32) string {
output := ufmt.Sprintf("# Chunks in World %d\n\n", worldID)
output += "[← Back to Worlds](/r/akkadia/chunk:worlds)\n\n"
chunkCount := GetWorldMetadataSize(worldID)
if chunkCount == 0 {
output += "*No chunks found in this world.*\n"
return output
}
output += ufmt.Sprintf("**Total Chunks**: %d\n\n", chunkCount)
pages, err := pager.New(iUrl, chunkCount, pager.WithPageSize(10))
if err != nil {
return output + "Invalid page"
}
currentPage := (pages.Offset() / pages.PageSize()) + 1
chunks := ListMetadataByWorld(worldID, currentPage, pages.PageSize())
for _, chunk := range chunks {
key := chunk["id"]
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)
owner := OwnerOf(grc721.TokenID(key))
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)
for k, v := range chunk {
output += ufmt.Sprintf("* **%s**: %s\n", k, v)
}
output += "\n---\n\n"
}
if pages.HasPages() {
output += pager.Picker(pages)
}
return output
}
// renderChunksByOwner renders chunks owned by a specific address with pagination
func renderChunksByOwner(iUrl string, owner string) string {
output := ufmt.Sprintf("# Chunks by %s\n\n", owner)
output += "[← Home](/r/akkadia/chunk:)\n\n"
ownerAddr := address(owner)
chunkCount := GetOwnerTokenSize(ownerAddr)
if chunkCount == 0 {
output += "*No chunks found for this owner.*\n"
return output
}
output += ufmt.Sprintf("**Total Chunks**: %d\n\n", chunkCount)
pages, err := pager.New(iUrl, chunkCount, pager.WithPageSize(10))
if err != nil {
return output + "Invalid page"
}
currentPage := (pages.Offset() / pages.PageSize()) + 1
ownerChunks := ListMetadataByOwner(ownerAddr, currentPage, pages.PageSize())
for _, chunk := range ownerChunks {
tid := chunk["id"]
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)
for k, v := range chunk {
output += ufmt.Sprintf("* **%s**: %s\n", k, v)
}
output += "\n---\n\n"
}
if pages.HasPages() {
output += pager.Picker(pages)
}
return output
}
// renderWorldsList renders a list of all worlds with pagination
func renderWorldsList(iUrl string) string {
output := `# All System Worlds
[← Home](/r/akkadia/chunk:)
`
if GetTotalWorldSize() == 0 {
output += "*No worlds created yet.*\n"
return output
}
pages, err := pager.New(iUrl, GetTotalWorldSize(), pager.WithPageSize(10))
if err != nil {
return output + "Invalid page"
}
worlds.ReverseIterateByOffset(pages.Offset(), pages.PageSize(), func(key string, value interface{}) bool {
world := value.(map[string]string)
worldIDStr := world["id"]
worldID, _ := strconv.ParseUint(worldIDStr, 10, 32)
chunkCount := GetWorldMetadataSize(uint32(worldID))
output += ufmt.Sprintf("* **ID**: %s\n", worldIDStr)
output += ufmt.Sprintf("* **Name**: %s\n", world["name"])
output += ufmt.Sprintf("* **Biome**: %s\n", world["biome"])
output += ufmt.Sprintf("* **Seed**: %s\n", world["seed"])
output += ufmt.Sprintf("* **Slug**: `%s` [[View on Explorer](%s/m/explorer/primary-realm?slug=%s)]\n", world["slug"], admin.GetExplorerURL(), world["slug"])
output += ufmt.Sprintf("* **Chunks**: %d [[View Chunks](/r/akkadia/chunk:worlds/%s/chunks)]\n", chunkCount, worldIDStr)
output += "\n---\n\n"
return false
})
if pages.HasPages() {
output += pager.Picker(pages)
}
return output
}