rework crafting checkout page
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
3fc26aebc7
commit
e2b621e809
|
@ -12,6 +12,7 @@ echo Setting env vars
|
|||
|
||||
# Set this to the name of the world you play on
|
||||
export world="excalibur"
|
||||
export datacenter="primal"
|
||||
|
||||
# Define the names of your retainers - this is used for the "checkmark" that's displayed
|
||||
# next to items that you already have listed on the market to help you avoid posting duplicate items
|
||||
|
|
|
@ -83,7 +83,13 @@ func staleItemsPageHandler(w http.ResponseWriter, _ *http.Request) {
|
|||
// When GET, produces a list of the items to crafting, suggested quantities to craft, and materials required
|
||||
func craftingCheckoutPageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" {
|
||||
type TableItem struct {
|
||||
|
||||
type Retainer struct {
|
||||
Name string
|
||||
ItemsToGet []inventorytools.Item
|
||||
}
|
||||
|
||||
type CraftingItem struct {
|
||||
ID int
|
||||
Name string
|
||||
Job string
|
||||
|
@ -91,16 +97,18 @@ func craftingCheckoutPageHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
type PageData struct {
|
||||
Title string
|
||||
CraftingItems []TableItem // The items that will be crafted
|
||||
CraftingMaterials []TableItem // The materials required to craft
|
||||
Title string
|
||||
CraftingItems []CraftingItem // The items that will be crafted
|
||||
Retainers []Retainer // items to get from retainers
|
||||
MarketItems []inventorytools.Item // items that need to be bought from market
|
||||
}
|
||||
|
||||
pageData := PageData{Title: "Crafting Checkout", CraftingItems: make([]TableItem, 0), CraftingMaterials: make([]TableItem, 0)}
|
||||
craftingMaterialMap := make(map[int]int)
|
||||
pageData := PageData{Title: "Crafting Checkout", CraftingItems: make([]CraftingItem, 0), Retainers: make([]Retainer, 0), MarketItems: make([]inventorytools.Item, 0)}
|
||||
|
||||
// Build list of crafting items, and how much materials are required
|
||||
craftingMaterialMap := make(map[int]int) // key item ID, value quantity required
|
||||
for _, craftingItemID := range craftingCheckoutItemIDs {
|
||||
craftingItem := TableItem{ID: craftingItemID, Name: items[craftingItemID].Name, Job: items[craftingItemID].Job, Quantity: (items[craftingItemID].Velocity / 2) + 1}
|
||||
craftingItem := CraftingItem{ID: craftingItemID, Name: items[craftingItemID].Name, Job: items[craftingItemID].Job, Quantity: (items[craftingItemID].Velocity / 2) + 1}
|
||||
for _, materialItem := range items[craftingItemID].CraftingMaterials {
|
||||
if _, ok := craftingMaterialMap[materialItem.ID]; ok {
|
||||
// we've seen it before, so we add to it
|
||||
|
@ -113,11 +121,57 @@ func craftingCheckoutPageHandler(w http.ResponseWriter, r *http.Request) {
|
|||
pageData.CraftingItems = append(pageData.CraftingItems, craftingItem)
|
||||
}
|
||||
|
||||
for id, quantity := range craftingMaterialMap {
|
||||
materialItem := TableItem{ID: id, Name: items[id].Name, Quantity: quantity}
|
||||
pageData.CraftingMaterials = append(pageData.CraftingMaterials, materialItem)
|
||||
// Subtract materials in character inventory
|
||||
for _, item := range inventories.CharacterBags {
|
||||
if _, ok := craftingMaterialMap[item.ID]; ok {
|
||||
craftingMaterialMap[item.ID] = craftingMaterialMap[item.ID] - item.Quantity
|
||||
if craftingMaterialMap[item.ID] <= 0 {
|
||||
delete(craftingMaterialMap, item.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Materials in retainer inventory and subtract
|
||||
for _, retainer := range inventories.Retainers {
|
||||
itemsToGet := make([]inventorytools.Item, 0)
|
||||
for _, item := range retainer.RetainerBags {
|
||||
if _, ok := craftingMaterialMap[item.ID]; ok {
|
||||
// This is an item we need that exists on the retainer
|
||||
if item.Quantity > craftingMaterialMap[item.ID] { // the retainer has more than we need, so adjust quantity down to what we need
|
||||
item.Quantity = craftingMaterialMap[item.ID]
|
||||
}
|
||||
|
||||
// offset quantity and map name from ID
|
||||
itemToGet := item
|
||||
itemToGet.Name = items[itemToGet.ID].Name
|
||||
itemToGet.SortedContainer -= 9999
|
||||
itemToGet.SortedSlotIndex += 1
|
||||
|
||||
itemsToGet = append(itemsToGet, itemToGet)
|
||||
craftingMaterialMap[item.ID] = craftingMaterialMap[item.ID] - item.Quantity
|
||||
if craftingMaterialMap[item.ID] <= 0 {
|
||||
delete(craftingMaterialMap, item.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(itemsToGet) > 0 {
|
||||
pageData.Retainers = append(pageData.Retainers, Retainer{Name: retainer.Name, ItemsToGet: itemsToGet})
|
||||
}
|
||||
}
|
||||
|
||||
// Materials to be bought from market is what's left over
|
||||
for itemID, itemQuantity := range craftingMaterialMap {
|
||||
itemToGet := inventorytools.Item{ID: itemID, Name: items[itemID].Name, Quantity: itemQuantity}
|
||||
pageData.MarketItems = append(pageData.MarketItems, itemToGet)
|
||||
}
|
||||
|
||||
/*
|
||||
for id, quantity := range craftingMaterialMap {
|
||||
materialItem := TableItem{ID: id, Name: items[id].Name, Quantity: quantity}
|
||||
pageData.CraftingMaterials = append(pageData.CraftingMaterials, materialItem)
|
||||
}
|
||||
*/
|
||||
|
||||
err := t.ExecuteTemplate(w, "crafting-checkout.html", pageData)
|
||||
if err != nil {
|
||||
log.Printf("failed to execute template: %v", err)
|
||||
|
@ -147,6 +201,27 @@ func craftingCheckoutPageHandler(w http.ResponseWriter, r *http.Request) {
|
|||
craftingCheckoutItemIDs = make([]int, 0)
|
||||
craftingCheckoutItemIDs = craftingCheckoutPageData.ItemIDs
|
||||
|
||||
go func() { // queue up datacenter price updates
|
||||
universalisPriceEndpoint = datacenter
|
||||
defer func() { universalisPriceEndpoint = world }() // set back to world when done
|
||||
|
||||
// build list of crafting supply item IDs to be refreshed for datacenter pricing
|
||||
itemsToRefresh := make(map[int]bool)
|
||||
for _, craftingItemID := range craftingCheckoutItemIDs {
|
||||
for _, craftingMaterial := range items[craftingItemID].CraftingMaterials {
|
||||
itemsToRefresh[craftingMaterial.ID] = true
|
||||
}
|
||||
}
|
||||
|
||||
// queue up datacenter price updates
|
||||
for itemID := range itemsToRefresh {
|
||||
priceUpdateQueue <- itemID
|
||||
updateRequestWg.Add(1)
|
||||
}
|
||||
|
||||
// wait for channel to empty
|
||||
updateRequestWg.Wait()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
36
main.go
36
main.go
|
@ -19,21 +19,22 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
items map[int]*MarketItem // hash map used to quickly guarantee uniqueness of MarketItems
|
||||
itemSlice []MarketItem // rank-ordered slice of the same data in the items map
|
||||
nameUpdateQueue chan int // requests sent to this channel use xivapi https://xivapi.com/
|
||||
priceUpdateQueue chan int // requests sent to this channel use universalis.app API https://universalis.app/docs/index.html
|
||||
updateRequestWg sync.WaitGroup // Used to wait until the queues are empty indicating all items have up to date names and prices
|
||||
t *template.Template // Used to cache HTML template parsing, done once at startup
|
||||
lastFullPriceUpdateTime time.Time // The last time the prices were updated from Universalis (both start and finish. Start if ongoing, finished when not running)
|
||||
startFullPriceUpdateTime time.Time // The last time the prices were started to be updated
|
||||
lastSortTime time.Time // The last time the itemSlice was sorted by metric
|
||||
lastUniversalisRequestTime time.Time // The last time any request was made to Universalis
|
||||
priceUpdateProgress int // an integer from 0-100 indicating percentage of any ongoing Universalis update. If 100, then the update is complete.
|
||||
world string // filter Universalis to this world name
|
||||
retainers []string // contains name of retainers
|
||||
craftingCheckoutItemIDs []int // The item IDs which were selected on the crafting checkout page, used for shopping list
|
||||
inventories inventorytools.Inventories
|
||||
items map[int]*MarketItem // hash map used to quickly guarantee uniqueness of MarketItems
|
||||
itemSlice []MarketItem // rank-ordered slice of the same data in the items map
|
||||
nameUpdateQueue chan int // requests sent to this channel use xivapi https://xivapi.com/
|
||||
priceUpdateQueue chan int // requests sent to this channel use universalis.app API https://universalis.app/docs/index.html
|
||||
updateRequestWg sync.WaitGroup // Used to wait until the queues are empty indicating all items have up to date names and prices
|
||||
t *template.Template // Used to cache HTML template parsing, done once at startup
|
||||
lastFullPriceUpdateTime time.Time // The last time the prices were updated from Universalis (both start and finish. Start if ongoing, finished when not running)
|
||||
startFullPriceUpdateTime time.Time // The last time the prices were started to be updated
|
||||
lastSortTime time.Time // The last time the itemSlice was sorted by metric
|
||||
lastUniversalisRequestTime time.Time // The last time any request was made to Universalis
|
||||
priceUpdateProgress int // an integer from 0-100 indicating percentage of any ongoing Universalis update. If 100, then the update is complete.
|
||||
world, datacenter, universalisPriceEndpoint string // filter Universalis to this world name
|
||||
|
||||
retainers []string // contains name of retainers
|
||||
craftingCheckoutItemIDs []int // The item IDs which were selected on the crafting checkout page, used for shopping list
|
||||
inventories inventorytools.Inventories
|
||||
)
|
||||
|
||||
// Object to represent a crafting material / ingredient in a recipe
|
||||
|
@ -142,6 +143,7 @@ func init() {
|
|||
// Validate that all required environment variables are set
|
||||
envVars := make(map[string]string)
|
||||
envVars["world"] = os.Getenv("world")
|
||||
envVars["datacenter"] = os.Getenv("datacenter")
|
||||
for key, value := range envVars {
|
||||
if value == "" {
|
||||
log.Fatalf("required environment variable '%s' is not set or has empty value", key)
|
||||
|
@ -158,6 +160,8 @@ func init() {
|
|||
}
|
||||
|
||||
world = envVars["world"]
|
||||
datacenter = envVars["datacenter"]
|
||||
universalisPriceEndpoint = world
|
||||
updateRequestWg = sync.WaitGroup{}
|
||||
|
||||
nameUpdateQueue = make(chan int, 8192)
|
||||
|
@ -282,7 +286,7 @@ func priceUpdateWorker() {
|
|||
// By this point we know the item name is not found in the cache
|
||||
// and this is the first request to populate it
|
||||
|
||||
resp, err := http.Get(fmt.Sprintf("https://universalis.app/api/%s/%d", world, id))
|
||||
resp, err := http.Get(fmt.Sprintf("https://universalis.app/api/%s/%d", universalisPriceEndpoint, id))
|
||||
if err != nil {
|
||||
log.Printf("request to get item ID '%d' price failed: %v", id, err)
|
||||
return
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
name: "{{ .Name }}",
|
||||
})
|
||||
{{ end }}
|
||||
{{ range .CraftingMaterials }}
|
||||
|
||||
{{ range .MarketItems }}
|
||||
itemList.push({
|
||||
id: {{ .ID }},
|
||||
name: "{{ .Name }}",
|
||||
|
@ -21,41 +22,6 @@
|
|||
console.log("setSelected state: " + stateID)
|
||||
$('#state-'+selectedItem).html(stateID)
|
||||
}
|
||||
|
||||
document.onkeydown = checkKeycode
|
||||
function checkKeycode(e) {
|
||||
var keycode;
|
||||
if (window.event)
|
||||
{keycode = window.event.keyCode;}
|
||||
else if (e)
|
||||
{keycode = e.which;}
|
||||
if (selectedItem == 0) { // there is no item selected
|
||||
return
|
||||
}
|
||||
console.log("keypressed: "+ keycode)
|
||||
if (keycode == 48) { // 0
|
||||
setSelected(0)
|
||||
} else if (keycode == 49) { //1
|
||||
setSelected(1)
|
||||
} else if (keycode == 50) { //2
|
||||
setSelected(2)
|
||||
} else if (keycode == 51) { //3
|
||||
setSelected(3)
|
||||
} else if (keycode == 52) { //4
|
||||
setSelected(4)
|
||||
} else if (keycode == 53) { //5
|
||||
setSelected(5)
|
||||
} else if (keycode == 54) { //6
|
||||
setSelected(6)
|
||||
} else if (keycode == 55) { //7
|
||||
setSelected(7)
|
||||
} else if (keycode == 56) { //8
|
||||
setSelected(8)
|
||||
} else if (keycode == 57) { //9
|
||||
setSelected(9)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
// Sets the clipboard to the item name and highlights the item row
|
||||
|
@ -77,7 +43,7 @@
|
|||
<p>This page lists the items that were selected for crafting before checkout, along with the shopping list of materials required for the craft</p>
|
||||
</div>
|
||||
|
||||
<h3 class="ui header">Items to Craft</h3>
|
||||
<h2 class="ui header">Items to Craft</h3>
|
||||
<table id="itemTable" class="ui compact celled striped table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -101,25 +67,45 @@
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 class="ui header">Materials Required</h3>
|
||||
<table id="craftingMaterialTable" class="ui compact celled striped table">
|
||||
<h2 class="ui header">Materials From Retainers</h2>
|
||||
{{ range .Retainers }}
|
||||
<h3 class="ui header">{{ .Name }}</h4>
|
||||
<table class="ui compact celled striped table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Item Name</th>
|
||||
<th>State</th>
|
||||
<th>Container</th>
|
||||
<th>Slot</th>
|
||||
<th>Quantity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="craftingMaterialTableBody">
|
||||
{{ range .CraftingMaterials }}
|
||||
{{ range .ItemsToGet }}
|
||||
<tr>
|
||||
<td><h4>{{ .Name }}</h4></td>
|
||||
<td>{{ .SortedContainer }}</td>
|
||||
<td>{{ .SortedSlotIndex }}</td>
|
||||
<td>{{ .Quantity }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ end }}
|
||||
|
||||
|
||||
<h2 class="ui header">Materials From Market</h2>
|
||||
<table class="ui compact celled striped table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Item Name</th>
|
||||
<th>Quantity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="craftingMaterialTableBody">
|
||||
{{ range .MarketItems }}
|
||||
<tr id="tr-{{ .ID }}">
|
||||
<td data-label="Item Name">
|
||||
<h4><button class="ui circular icon button" onclick="selectItem({{ .ID }})"><i class="icon copy outline"></i></button>{{ .Name }}</h4>
|
||||
</td>
|
||||
<td class="center aligned" data-label="State">
|
||||
<div id="state-{{ .ID }}"></div>
|
||||
</td>
|
||||
<td class="right aligned" data-label="Quanity">{{ .Quantity }}</td>
|
||||
<td><h4><button class="ui circular icon button" onclick="selectItem({{ .ID }})"><i class="icon copy outline"></i></button>{{ .Name }}</h4></td>
|
||||
<td>{{ .Quantity }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
|
|
Loading…
Reference in New Issue