diff --git a/packages/r/pierre115/gnopub/README.md b/packages/r/pierre115/gnopub/README.md new file mode 100644 index 0000000..c1e52d3 --- /dev/null +++ b/packages/r/pierre115/gnopub/README.md @@ -0,0 +1,26 @@ +## GnoPUB ๐Ÿ“บ + +# Intro + +I aimed to recreate an advertising space similar to the Sandbox project, but encountered several challenges when trying to paste and integrate images onto the clickable surface. + +For now, the concept focuses on building an advertising grid where individual slots can be bought and sold. Once a plot is purchased, it becomes a link to the buyerโ€™s advertisement, which can be configured using the `UpdateCell` function. +Each ad is tied to a specific cell, allowing users to own multiple spaces for different promotional content. + +# Tutorial + +### Step 1 +Buy a cell with the function `BuyCell` +### Step 2 +Set the ads and the label with the function `UpdateCell` on yours cells +### Step 3 +Sell your cells with the function `SellCell` + +# RoadMap + + - Add determined price (`n` / `a`) * `price` + +Where `n is the number of cells` and `a is the number of available cells` + + - Add number of clicks + diff --git a/packages/r/pierre115/gnopub/gnomod.toml b/packages/r/pierre115/gnopub/gnomod.toml new file mode 100644 index 0000000..dd0e863 --- /dev/null +++ b/packages/r/pierre115/gnopub/gnomod.toml @@ -0,0 +1,2 @@ +module = "gno.land/r/pierre115/gnopub" +gno = "0.9" diff --git a/packages/r/pierre115/gnopub/grid.gno b/packages/r/pierre115/gnopub/grid.gno new file mode 100644 index 0000000..d2c4473 --- /dev/null +++ b/packages/r/pierre115/gnopub/grid.gno @@ -0,0 +1,146 @@ +package gnopub + +import ( + "chain/runtime" + + "gno.land/p/nt/ownable" + "gno.land/p/nt/ufmt" +) + +var ( + OwnableAddress = ownable.NewWithAddress("g1e7r5eglptfejknps3xv2kxv6pc8wvd3wwtw6tg") + + colors = []string{"#ffffff", "#f44336", "#2196f3", "#4caf50", "#ff9800"} // blanc, rouge, bleu, vert, orange + + spacingCount = 23 + + defaultlink = ufmt.Sprintf("/r/leon/hor:hall") + + grid Grid +) + +const ( + cellSize = 100 + defaultcolor = "#ffffff" +) + +// Cell structure init +func init() { + grid = Grid{ + Width: 5, + Height: 5, + Cells: make(map[string]Cell), + } +} + +// Function to validate the coordinates +func getCoordinate(x, y int) string { + if x < 0 || x >= grid.Width || y < 0 || y >= grid.Height { + return ufmt.Sprintf("Invalid coordinate : X=%d, Y=%d", x, y) + } + return "" +} + +// Function to sell a cell +func SellCell(_ realm, x, y int) string { + if err := getCoordinate(x, y); err != "" { + return err + } + + key := ufmt.Sprintf("%d,%d", x, y) + caller := runtime.PreviousRealm().Address() + cell, exists := grid.Cells[key] + + if exists && cell.Owner == caller { + grid.Cells[key] = Cell{ + X: x, + Y: y, + Color: "", + Owner: "", + Url: "", + Label: "", + } + return ufmt.Sprintf("The cell was successfully sold.", x, y) + } + + return "You are not the owner of this cell." +} + +// Function to update the Ads and the Label of yours cells +func UpdateCell(_ realm, x, y int, url, label string) string { + if err := getCoordinate(x, y); err != "" { + return err + } + + key := ufmt.Sprintf("%d,%d", x, y) + caller := runtime.PreviousRealm().Address() + cell, exists := grid.Cells[key] + + if exists && cell.Owner == caller { + grid.Cells[key] = Cell{ + X: x, + Y: y, + Color: cell.Color, + Owner: cell.Owner, + Url: url, + Label: label, + } + return ufmt.Sprintf("The cell Url was successfully Updated!") + } + + return "You are not the owner of this cell." +} + +// Cell clicked function if the cells is not owned +func BuyCell(_ realm, x, y int) string { + if err := getCoordinate(x, y); err != "" { + return err + } + + key := ufmt.Sprintf("%d,%d", x, y) + caller := runtime.PreviousRealm().Address() + + // If the cells is not owned, buy it + cell, exists := grid.Cells[key] + if !exists || cell.Owner == "" { + grid.Cells[key] = Cell{ + X: x, + Y: y, + Color: colors[1], // red at first + Owner: caller, + Url: "", + Label: "", + } + return ufmt.Sprintf("The cell was successfully (%d,%d) purchased!", x, y) + } + + // if it is the person who own it, change the color + if cell.Owner == caller { + currentColorIndex := 0 + for i, c := range colors { + if c == cell.Color { + currentColorIndex = i + break + } + } + nextColorIndex := (currentColorIndex + 1) % len(colors) + cell.Color = colors[nextColorIndex] + grid.Cells[key] = cell + return ufmt.Sprintf("The cells (%d,%d) was updated! New color: %s", x, y, getColorName(cell.Color)) + } + + return "This cell is owned." +} + +// Reset grid (admin only) +func Reset() string { + if !isAuthorized(runtime.PreviousRealm().Address()) { + panic("unauthorized") + } + grid.Cells = make(map[string]Cell) + return "The grid was successfully reset." +} + +func isAuthorized(addr address) bool { + return addr == OwnableAddress.Owner() +} diff --git a/packages/r/pierre115/gnopub/grid_test.gno b/packages/r/pierre115/gnopub/grid_test.gno new file mode 100644 index 0000000..0556b16 --- /dev/null +++ b/packages/r/pierre115/gnopub/grid_test.gno @@ -0,0 +1,27 @@ +package gnopub + +import ( + "strings" + "testing" +) + +// Test for basic grid rendering +func TestRenderGrid(t *testing.T) { + got := renderGrid() + + if got == "" { + t.Fatal("RenderGrid output is empty") + } + + if !strings.Contains(got, "svg") { + t.Error("expected at least one SVG element in render output") + } + + if !strings.Contains(got, "BuyCell") { + t.Error("expected 'BuyCells' tutorial step in render output") + } + + if !strings.Contains(got, "available") { + t.Error("expected 'available' label for unowned cells") + } +} diff --git a/packages/r/pierre115/gnopub/helpers.gno b/packages/r/pierre115/gnopub/helpers.gno new file mode 100644 index 0000000..20608cf --- /dev/null +++ b/packages/r/pierre115/gnopub/helpers.gno @@ -0,0 +1,16 @@ +package gnopub + +func getColorName(color string) string { + switch color { + case "#f44336": + return "Red" + case "#2196f3": + return "Blue" + case "#4caf50": + return "Green" + case "#ff9800": + return "Orange" + default: + return "White" + } +} diff --git a/packages/r/pierre115/gnopub/render.gno b/packages/r/pierre115/gnopub/render.gno new file mode 100644 index 0000000..6b9b92d --- /dev/null +++ b/packages/r/pierre115/gnopub/render.gno @@ -0,0 +1,115 @@ +package gnopub + +import ( + "chain/runtime" + "strings" + + "gno.land/p/leon/svgbtn" + "gno.land/p/moul/txlink" + "gno.land/p/nt/ufmt" +) + +func Render(path string) string { + out := "# GnoADS ๐Ÿ“บ\n\n" + out += "### ADS Grid \n\n" + + // Path handling + if path == "stats" { + return out + getStats() + "\n\n[โ† Retour](/r/gnopixel)" + } + + // Main grid + out += renderGrid() + "\n\n" + + // Tutorial + out += "## Tuto ๐Ÿ“š\n\n" + + out += "### Step 1: Buy a cell\n\n" + out += "Click on a white cell in the grid or use the BuyCell function:\n\n" + out += svgbtn.Button(120, 35, "#4caf50", "#ffffff", "Buy Cell (0,0)", "/r/pierre115/gnoads$help&func=BuyCell&x=0&y=0") + "\n\n" + out += "Replace x and y with your desired coordinates.\n\n" + + out += "### Step 2: Update your cell\n\n" + out += "Set the ads URL and label for your cell:\n\n" + out += svgbtn.Button(120, 35, "#4caf50", "#ffffff", "Update Cell (0,0)", "/r/pierre115/gnoads$help&func=UpdateCell&x=0&y=0&url=https://github.com/gnolang/hackerspace/issues/122&label=test") + "\n\n" + out += "- `x`, `y`: coordinates of your cell\n" + out += "- `url`: your advertisement URL (must start with http)\n" + out += "- `label`: text to display\n\n" + + out += "### Step 3: Change color\n\n" + out += "Click again on your owned cell to cycle through colors:\n\n" + out += svgbtn.Button(120, 35, "#4caf50", "#ffffff", "Change Color (0,0)", "/r/pierre115/gnoads$help&func=BuyCell&x=0&y=0") + "\n\n" + + out += "### Step 4: Sell your cell\n\n" + out += "Release your cell back to the grid:\n\n" + out += svgbtn.Button(120, 35, "#4caf50", "#ffffff", "Sell Cell (0,0)", "/r/pierre115/gnoads$help&func=SellCell&x=0&y=0") + "\n\n" + + // Utility buttons + out += "---\n\n" + out += "## Admin Pannel ๐Ÿ› ๏ธ\n\n" + out += svgbtn.DangerButton(100, 30, "Reset", "/r/gnopixel$help&func=reset") + "\n\n" + + // Stats + out += getStats() + + return out +} + +// Render the grid +func renderGrid() string { + out := "" + + // Grid cells + for y := 0; y < grid.Height; y++ { + for x := 0; x < grid.Width; x++ { + key := ufmt.Sprintf("%d,%d", x, y) + color := defaultcolor + link := defaultlink + // If the cell is owned, use its color + if cell, ok := grid.Cells[key]; ok { + color = cell.Color + caller := runtime.PreviousRealm().Address() + + if cell.Url != "" { + link = cell.Url + } + + // If the cell is owned by someone else, link to their pub if set + if cell.Owner != "" && cell.Owner != caller { + out += svgbtn.Button(cellSize, cellSize, color, "#333333", cell.Label, link) + out += strings.Repeat(" ", spacingCount) + continue + } + } + link = txlink.NewLink("BuyCell"). + AddArgs("x", ufmt.Sprintf("%d", x), "y", ufmt.Sprintf("%d", y)). + URL() + out += svgbtn.Button(cellSize, cellSize, color, "#333333", "available", link) + out += strings.Repeat(" ", spacingCount) + } + out += "\n\n" + } + + return out +} + +// Stats of the grid (per cells owners) +func getStats() string { + stats := make(map[address]int) + for _, cell := range grid.Cells { + if cell.Owner != "" { + stats[cell.Owner]++ + } + } + + out := "## Stats ๐Ÿ“Š:\n" + if len(stats) == 0 { + out += "No cells used.\n" + return out + } + + for owner, count := range stats { + out += ufmt.Sprintf("- %s: %d cell(s)\n", owner.String(), count) + } + return out +} diff --git a/packages/r/pierre115/gnopub/type.gno b/packages/r/pierre115/gnopub/type.gno new file mode 100644 index 0000000..294854f --- /dev/null +++ b/packages/r/pierre115/gnopub/type.gno @@ -0,0 +1,18 @@ +package gnopub + +// Cell structure +type Cell struct { + X int + Y int + Color string // Colors of the cells if owned + Owner address // Address of the owner + Url string // Url of the ads + Label string // Pseudo of the owner / name of the ads +} + +// Global grid variable +type Grid struct { + Width int + Height int + Cells map[string]Cell +}