Inventory implementation
This commit is contained in:
parent
16ab79b3a8
commit
6c2782f18d
@ -25,5 +25,11 @@ func Get(ctx *aero.Context) string {
|
||||
return ctx.Error(http.StatusInternalServerError, "Error fetching inventory data", err)
|
||||
}
|
||||
|
||||
// TEST
|
||||
inventory.AddItem("anime-support-ticket", 35)
|
||||
inventory.AddItem("pro-account-24", 20)
|
||||
inventory.AddItem("anime-support-ticket", 15)
|
||||
inventory.AddItem("pro-account-24", 10)
|
||||
|
||||
return ctx.HTML(components.Inventory(inventory))
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
component Inventory(inventory *arn.Inventory)
|
||||
ShopTabs
|
||||
.inventory
|
||||
each slot in inventory.Slots
|
||||
.inventory-slot
|
||||
span= slot.ItemID
|
||||
p Coming soon.
|
||||
for index, slot := range inventory.Slots
|
||||
if slot.ItemID == ""
|
||||
.inventory-slot.mountable(draggable="false", data-index=index)
|
||||
else
|
||||
.inventory-slot.mountable(title=slot.Item().Name, draggable="true", data-index=index)
|
||||
Icon(slot.Item().Icon)
|
||||
if slot.Quantity > 1
|
||||
.inventory-slot-quantity= slot.Quantity
|
@ -0,0 +1,35 @@
|
||||
inventory-slot-size = 64px
|
||||
|
||||
.inventory
|
||||
display grid
|
||||
grid-gap 0.25rem
|
||||
grid-template-columns repeat(auto-fit, inventory-slot-size)
|
||||
grid-auto-rows inventory-slot-size
|
||||
justify-content center
|
||||
width 100%
|
||||
max-width 450px
|
||||
margin 0 auto
|
||||
|
||||
.inventory-slot
|
||||
ui-element
|
||||
position relative
|
||||
display flex
|
||||
align-items center
|
||||
justify-content center
|
||||
font-size 2rem
|
||||
|
||||
.icon
|
||||
margin 0
|
||||
pointer-events none
|
||||
|
||||
.inventory-slot-quantity
|
||||
position absolute
|
||||
bottom 0.25rem
|
||||
right 0.25rem
|
||||
font-size 0.8rem
|
||||
line-height 1em
|
||||
opacity 0.5
|
||||
pointer-events none
|
||||
|
||||
.drag-enter
|
||||
border-style dashed
|
@ -2,6 +2,7 @@ package shop
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/animenotifier/arn"
|
||||
|
||||
@ -18,7 +19,15 @@ func Get(ctx *aero.Context) string {
|
||||
return ctx.Error(http.StatusUnauthorized, "Not logged in", nil)
|
||||
}
|
||||
|
||||
items := arn.AllItems()
|
||||
items, err := arn.AllItems()
|
||||
|
||||
if err != nil {
|
||||
return ctx.Error(http.StatusInternalServerError, "Error fetching shop item data", err)
|
||||
}
|
||||
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return items[i].Order < items[j].Order
|
||||
})
|
||||
|
||||
return ctx.HTML(components.Shop(user, items))
|
||||
}
|
||||
|
104
patches/add-shop-items/add-shop-items.go
Normal file
104
patches/add-shop-items/add-shop-items.go
Normal file
@ -0,0 +1,104 @@
|
||||
package main
|
||||
|
||||
import "github.com/animenotifier/arn"
|
||||
|
||||
var items = []*arn.Item{
|
||||
&arn.Item{
|
||||
ID: "pro-account-3",
|
||||
Name: "PRO Account (1 season)",
|
||||
Price: 900,
|
||||
Description: `PRO account for 1 anime season (3 months).
|
||||
|
||||
Includes:
|
||||
|
||||
* Special highlight on the forums
|
||||
* Customizable cover image for your profile
|
||||
* Custom title for profile and forums
|
||||
* Your suggestions will have a high priority
|
||||
* Access to the VIP channel on Discord`,
|
||||
Icon: "star",
|
||||
Rarity: arn.ItemRaritySuperior,
|
||||
Order: 1,
|
||||
Consumable: true,
|
||||
},
|
||||
&arn.Item{
|
||||
ID: "pro-account-6",
|
||||
Name: "PRO Account (2 seasons)",
|
||||
Price: 1600,
|
||||
Description: `PRO account for 2 anime seasons (6 months).
|
||||
|
||||
Includes:
|
||||
|
||||
* Special highlight on the forums
|
||||
* Customizable cover image for your profile
|
||||
* Custom title for profile and forums
|
||||
* Your suggestions will have a high priority
|
||||
* Access to the VIP channel on Discord`,
|
||||
Icon: "star",
|
||||
Rarity: arn.ItemRarityRare,
|
||||
Order: 2,
|
||||
Consumable: true,
|
||||
},
|
||||
&arn.Item{
|
||||
ID: "pro-account-12",
|
||||
Name: "PRO Account (4 seasons)",
|
||||
Price: 3000,
|
||||
Description: `PRO account for 4 anime seasons (12 months).
|
||||
|
||||
Includes:
|
||||
|
||||
* Special highlight on the forums
|
||||
* Customizable cover image for your profile
|
||||
* Custom title for profile and forums
|
||||
* Your suggestions will have a high priority
|
||||
* Access to the VIP channel on Discord`,
|
||||
Icon: "star",
|
||||
Rarity: arn.ItemRarityUnique,
|
||||
Order: 3,
|
||||
Consumable: true,
|
||||
},
|
||||
&arn.Item{
|
||||
ID: "pro-account-24",
|
||||
Name: "PRO Account (8 seasons)",
|
||||
Price: 5900,
|
||||
Description: `PRO account for 8 anime seasons (24 months).
|
||||
|
||||
Includes:
|
||||
|
||||
* Special highlight on the forums
|
||||
* Customizable cover image for your profile
|
||||
* Custom title for profile and forums
|
||||
* Your suggestions will have a high priority
|
||||
* Access to the VIP channel on Discord`,
|
||||
Icon: "star",
|
||||
Rarity: arn.ItemRarityLegendary,
|
||||
Order: 4,
|
||||
Consumable: true,
|
||||
},
|
||||
&arn.Item{
|
||||
ID: "anime-support-ticket",
|
||||
Name: "Anime Support Ticket",
|
||||
Price: 100,
|
||||
Description: `Support the makers of your favourite anime by using an anime support ticket.
|
||||
Anime Notifier uses 8% of the money to handle the transaction fees while the remaining 92% go directly
|
||||
to the studios involved in the creation of your favourite anime.`,
|
||||
Icon: "ticket",
|
||||
Rarity: arn.ItemRarityRare,
|
||||
Order: 5,
|
||||
Consumable: false,
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
for _, item := range items {
|
||||
item.Save()
|
||||
}
|
||||
}
|
||||
|
||||
//- ShopItem("PRO Account", "6 months", "1600", "star", strings.Replace(strings.Replace(proAccountMarkdown, "3 months", "6 months", 1), "1 anime season", "2 anime seasons", 1))
|
||||
//- ShopItem("PRO Account", "1 year", "3000", "star", strings.Replace(strings.Replace(proAccountMarkdown, "3 months", "12 months", 1), "1 anime season", "4 anime seasons", 1))
|
||||
//- ShopItem("PRO Account", "2 years", "5900", "star", strings.Replace(strings.Replace(proAccountMarkdown, "3 months", "24 months", 1), "1 anime season", "8 anime seasons", 1))
|
||||
//- ShopItem("Anime Support Ticket", "", "100", "ticket", "Support the makers of your favourite anime by using an anime support ticket. Anime Notifier uses 8% of the money to handle the transaction fees while the remaining 92% go directly to the studios involved in the creation of your favourite anime.")
|
||||
//- ShopItem("Artwork Support Ticket", "", "100", "ticket", "Support the makers of your favourite artwork by using an artwork support ticket. Anime Notifier uses 8% of the money to handle the transaction fees while the remaining 92% go directly to the creator.")
|
||||
//- ShopItem("Soundtrack Support Ticket", "", "100", "ticket", "Support the makers of your favourite soundtrack by using a soundtrack support ticket. Anime Notifier uses 8% of the money to handle the transaction fees while the remaining 92% go directly to the creator.")
|
||||
//- ShopItem("AMV Support Ticket", "", "100", "ticket", "Support the makers of your favourite AMV by using an AMV support ticket. Anime Notifier uses 8% of the money to handle the transaction fees while the remaining 92% go directly to the creator.")
|
@ -1,6 +1,6 @@
|
||||
import * as actions from "./Actions"
|
||||
import { displayAiringDate, displayDate } from "./DateView"
|
||||
import { findAll, delay, canUseWebP } from "./Utils"
|
||||
import { findAll, delay, canUseWebP, swapElements } from "./Utils"
|
||||
import { Application } from "./Application"
|
||||
import { Diff } from "./Diff"
|
||||
import { MutationQueue } from "./MutationQueue"
|
||||
@ -139,6 +139,7 @@ export class AnimeNotifier {
|
||||
Promise.resolve().then(() => this.setSelectBoxValue()),
|
||||
Promise.resolve().then(() => this.assignActions()),
|
||||
Promise.resolve().then(() => this.updatePushUI()),
|
||||
Promise.resolve().then(() => this.dragAndDrop()),
|
||||
Promise.resolve().then(() => this.countUp())
|
||||
])
|
||||
|
||||
@ -154,22 +155,6 @@ export class AnimeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
async updatePushUI() {
|
||||
if(!this.pushManager.pushSupported || !this.app.currentPath.includes("/settings")) {
|
||||
return
|
||||
}
|
||||
|
||||
let subscription = await this.pushManager.subscription()
|
||||
|
||||
if(subscription) {
|
||||
this.app.find("enable-notifications").style.display = "none"
|
||||
this.app.find("disable-notifications").style.display = "flex"
|
||||
} else {
|
||||
this.app.find("enable-notifications").style.display = "flex"
|
||||
this.app.find("disable-notifications").style.display = "none"
|
||||
}
|
||||
}
|
||||
|
||||
onIdle() {
|
||||
// Service worker
|
||||
this.registerServiceWorker()
|
||||
@ -261,6 +246,68 @@ export class AnimeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
dragAndDrop() {
|
||||
for(let element of findAll("inventory-slot")) {
|
||||
if(element.draggable) {
|
||||
element.addEventListener("dragstart", e => {
|
||||
let element = e.target as HTMLElement
|
||||
e.dataTransfer.setData("text", element.dataset.index)
|
||||
}, false)
|
||||
}
|
||||
|
||||
element.addEventListener("dragenter", e => {
|
||||
let element = e.target as HTMLElement
|
||||
element.classList.add("drag-enter")
|
||||
}, false)
|
||||
|
||||
element.addEventListener("dragleave", e => {
|
||||
let element = e.target as HTMLElement
|
||||
element.classList.remove("drag-enter")
|
||||
}, false)
|
||||
|
||||
element.addEventListener("dragover", e => {
|
||||
e.preventDefault()
|
||||
}, false)
|
||||
|
||||
element.addEventListener("drop", e => {
|
||||
let inventory = e.toElement.parentElement
|
||||
let fromIndex = e.dataTransfer.getData("text")
|
||||
let fromElement = inventory.childNodes[fromIndex] as HTMLElement
|
||||
let toElement = e.toElement as HTMLElement
|
||||
let toIndex = toElement.dataset.index
|
||||
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
|
||||
if(fromElement === toElement || fromIndex === toIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
toElement.classList.remove("drag-enter")
|
||||
swapElements(fromElement, toElement)
|
||||
|
||||
fromElement.dataset.index = toIndex
|
||||
toElement.dataset.index = fromIndex
|
||||
}, false)
|
||||
}
|
||||
}
|
||||
|
||||
async updatePushUI() {
|
||||
if(!this.pushManager.pushSupported || !this.app.currentPath.includes("/settings")) {
|
||||
return
|
||||
}
|
||||
|
||||
let subscription = await this.pushManager.subscription()
|
||||
|
||||
if(subscription) {
|
||||
this.app.find("enable-notifications").style.display = "none"
|
||||
this.app.find("disable-notifications").style.display = "flex"
|
||||
} else {
|
||||
this.app.find("enable-notifications").style.display = "flex"
|
||||
this.app.find("disable-notifications").style.display = "none"
|
||||
}
|
||||
}
|
||||
|
||||
countUp() {
|
||||
for(let element of findAll("count-up")) {
|
||||
let final = parseInt(element.innerText)
|
||||
|
@ -25,3 +25,26 @@ export function canUseWebP(): boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function swapElements(a: Node, b: Node) {
|
||||
let parent = b.parentNode
|
||||
let bNext = b.nextSibling
|
||||
|
||||
// Special case for when a is the next sibling of b
|
||||
if(bNext === a) {
|
||||
// Just put a before b
|
||||
parent.insertBefore(a, b)
|
||||
} else {
|
||||
// Insert b right before a
|
||||
a.parentNode.insertBefore(b, a)
|
||||
|
||||
// Now insert a where b was
|
||||
if(bNext) {
|
||||
// If there was an element after b, then insert a right before that
|
||||
parent.insertBefore(a, bNext)
|
||||
} else {
|
||||
// Otherwise just append it as the last child
|
||||
parent.appendChild(a)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user