feat(webui): add Mods card (#252)

This commit is contained in:
Sainan 2024-06-01 12:57:27 +02:00 committed by GitHub
parent ef220ca6d7
commit f83f80c991
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 282 additions and 60 deletions

View File

@ -1,16 +1,19 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { MinItem, warframes, weapons, items } from "@/src/services/itemDataService"; import { MinItem, warframes, weapons, items } from "@/src/services/itemDataService";
import badItems from "@/static/json/exclude-mods.json";
interface ListedItem { interface ListedItem {
uniqueName: string; uniqueName: string;
name: string; name: string;
fusionLimit?: number;
} }
function reduceItems(items: MinItem[]): ListedItem[] { function reduceItems(items: MinItem[]): ListedItem[] {
return items.map((item: MinItem): ListedItem => { return items.map((item: MinItem): ListedItem => {
return { return {
uniqueName: item.uniqueName, uniqueName: item.uniqueName,
name: item.name name: item.name,
fusionLimit: (item as any).fusionLimit
}; };
}); });
} }
@ -19,7 +22,11 @@ const getItemListsController: RequestHandler = (_req, res) => {
res.json({ res.json({
warframes: reduceItems(warframes), warframes: reduceItems(warframes),
weapons: reduceItems(weapons.filter(item => item.productCategory != "OperatorAmps")), weapons: reduceItems(weapons.filter(item => item.productCategory != "OperatorAmps")),
miscitems: reduceItems(items.filter(item => item.category == "Misc" || item.category == "Resources")) miscitems: reduceItems(
items.filter(item => item.category == "Misc" || item.category == "Resources" || item.category == "Fish")
),
mods: reduceItems(items.filter(item => item.category == "Mods" || item.category == "Arcanes")),
badItems
}); });
}; };

View File

@ -45,16 +45,32 @@
></button> ></button>
</div> </div>
<div class="offcanvas-body"> <div class="offcanvas-body">
<ul> <div class="navbar p-0">
<li> <ul class="navbar-nav justify-content-end">
<a href="/webui/inventory" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" <li class="nav-item">
>Inventory</a <a
class="nav-link"
href="/webui/inventory"
data-bs-dismiss="offcanvas"
data-bs-target="#sidebar"
> >
Inventory
</a>
</li>
<li class="nav-item">
<a
class="nav-link"
href="/webui/mods"
data-bs-dismiss="offcanvas"
data-bs-target="#sidebar"
>
Mods
</a>
</li> </li>
<li><a href="/webui/mods" data-bs-dismiss="offcanvas" data-bs-target="#sidebar">Mods</a></li>
</ul> </ul>
</div> </div>
</div> </div>
</div>
<div> <div>
<p id="refresh-note" class="mb-4"> <p id="refresh-note" class="mb-4">
Note: Changes made here will only be reflected in-game when the game re-downloads your inventory. Note: Changes made here will only be reflected in-game when the game re-downloads your inventory.
@ -117,17 +133,27 @@
</div> </div>
</div> </div>
<div data-route="/webui/mods" data-title="Mods | OpenWF WebUI"> <div data-route="/webui/mods" data-title="Mods | OpenWF WebUI">
<div class="row">
<div class="col-xxl-6">
<div class="card mb-4"> <div class="card mb-4">
<h5 class="card-header">Add Riven</h5> <h5 class="card-header">Add Riven</h5>
<form class="card-body" onsubmit="doAcquireRiven();return false;"> <form class="card-body" onsubmit="doAcquireRiven();return false;">
<select class="form-control mb-3" id="addriven-type"> <select class="form-control mb-3" id="addriven-type">
<option value="LotusArchgunRandomModRare">LotusArchgunRandomModRare</option> <option value="LotusArchgunRandomModRare">LotusArchgunRandomModRare</option>
<option value="LotusModularMeleeRandomModRare">LotusModularMeleeRandomModRare</option> <option value="LotusModularMeleeRandomModRare">
<option value="LotusModularPistolRandomModRare">LotusModularPistolRandomModRare</option> LotusModularMeleeRandomModRare
</option>
<option value="LotusModularPistolRandomModRare">
LotusModularPistolRandomModRare
</option>
<option value="LotusPistolRandomModRare">LotusPistolRandomModRare</option> <option value="LotusPistolRandomModRare">LotusPistolRandomModRare</option>
<option value="LotusRifleRandomModRare" selected>LotusRifleRandomModRare</option> <option value="LotusRifleRandomModRare" selected>
LotusRifleRandomModRare
</option>
<option value="LotusShotgunRandomModRare">LotusShotgunRandomModRare</option> <option value="LotusShotgunRandomModRare">LotusShotgunRandomModRare</option>
<option value="PlayerMeleeWeaponRandomModRare">PlayerMeleeWeaponRandomModRare</option> <option value="PlayerMeleeWeaponRandomModRare">
PlayerMeleeWeaponRandomModRare
</option>
</select> </select>
<textarea <textarea
id="addriven-fingerprint" id="addriven-fingerprint"
@ -135,10 +161,12 @@
placeholder="Fingerprint" placeholder="Fingerprint"
></textarea> ></textarea>
<button class="btn btn-primary" style="margin-right: 5px" type="submit">Add</button> <button class="btn btn-primary" style="margin-right: 5px" type="submit">Add</button>
<a href="https://riven.builds.wf/" target="_blank">Need help with the fingerprint?</a> <a href="https://riven.builds.wf/" target="_blank">
Need help with the fingerprint?
</a>
</form> </form>
</div> </div>
<div class="card mb-4 col-xxl-6"> <div class="card mb-4">
<h5 class="card-header">Rivens</h5> <h5 class="card-header">Rivens</h5>
<div class="card-body"> <div class="card-body">
<table class="table table-hover w-100"> <table class="table table-hover w-100">
@ -147,11 +175,28 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-xxl-6">
<div class="card mb-4">
<h5 class="card-header">Mods</h5>
<div class="card-body">
<table class="table table-hover w-100">
<tbody id="mods-list"></tbody>
</table>
<form class="input-group" onsubmit="doAcquireMod();return false;">
<input class="form-control" id="mod-to-acquire" list="datalist-mods" />
<button class="btn btn-primary" type="submit">Add</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
<datalist id="datalist-warframes"></datalist> <datalist id="datalist-warframes"></datalist>
<datalist id="datalist-weapons"></datalist> <datalist id="datalist-weapons"></datalist>
<datalist id="datalist-miscitems"></datalist> <datalist id="datalist-miscitems"></datalist>
<datalist id="datalist-mods"></datalist>
<script src="libs/jquery-3.6.0.min.js"></script> <script src="libs/jquery-3.6.0.min.js"></script>
<script src="libs/whirlpool-js.min.js"></script> <script src="libs/whirlpool-js.min.js"></script>
<script src="libs/single.js"></script> <script src="libs/single.js"></script>

View File

@ -76,33 +76,47 @@ single.on("route_load", function (event) {
} else { } else {
$("body").removeClass("logged-in"); $("body").removeClass("logged-in");
} }
$(".nav-link").removeClass("active");
const navLink = document.querySelector(".nav-link[href='" + event.route.paths[0] + "']");
if (navLink) {
navLink.classList.add("active");
}
}); });
window.itemListPromise = new Promise(resolve => { window.itemListPromise = new Promise(resolve => {
const req = $.get("/custom/getItemLists"); const req = $.get("/custom/getItemLists");
req.done(data => { req.done(data => {
const itemMap = {}; const itemMap = {
// Generics for rivens
"/Lotus/Weapons/Tenno/Archwing/Primary/ArchGun": { name: "Archgun" },
"/Lotus/Weapons/Tenno/Melee/PlayerMeleeWeapon": { name: "Melee" },
"/Lotus/Weapons/Tenno/Pistol/LotusPistol": { name: "Pistol" },
"/Lotus/Weapons/Tenno/Rifle/LotusRifle": { name: "Rifle" },
"/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": { name: "Shotgun" },
// Missing in data sources
"/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod": { name: "Traumatic Peculiar" },
"/Lotus/Weapons/Tenno/Grimoire/TnGrimoire": { name: "Grimoire" }
};
for (const [type, items] of Object.entries(data)) { for (const [type, items] of Object.entries(data)) {
if (type != "badItems") {
items.forEach(item => { items.forEach(item => {
if (item.uniqueName in data.badItems) {
item.name += " (Imposter)";
} else if (item.uniqueName.substr(0, 18) != "/Lotus/Types/Game/") {
const option = document.createElement("option"); const option = document.createElement("option");
option.setAttribute("data-key", item.uniqueName); option.setAttribute("data-key", item.uniqueName);
option.value = item.name; option.value = item.name;
document.getElementById("datalist-" + type).appendChild(option); document.getElementById("datalist-" + type).appendChild(option);
}
itemMap[item.uniqueName] = { ...item, type }; itemMap[item.uniqueName] = { ...item, type };
}); });
} }
}
resolve(itemMap); resolve(itemMap);
}); });
}); });
const rivenGenericCompatNames = {
"/Lotus/Weapons/Tenno/Archwing/Primary/ArchGun": "Archgun",
"/Lotus/Weapons/Tenno/Melee/PlayerMeleeWeapon": "Melee",
"/Lotus/Weapons/Tenno/Pistol/LotusPistol": "Pistol",
"/Lotus/Weapons/Tenno/Rifle/LotusRifle": "Rifle",
"/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": "Shotgun"
};
function updateInventory() { function updateInventory() {
const req = $.get("/api/inventory.php?" + window.authz); const req = $.get("/api/inventory.php?" + window.authz);
req.done(data => { req.done(data => {
@ -190,6 +204,7 @@ function updateInventory() {
}); });
document.getElementById("riven-list").innerHTML = ""; document.getElementById("riven-list").innerHTML = "";
document.getElementById("mods-list").innerHTML = "";
data.Upgrades.forEach(item => { data.Upgrades.forEach(item => {
if (item.ItemType.substr(0, 32) == "/Lotus/Upgrades/Mods/Randomized/") { if (item.ItemType.substr(0, 32) == "/Lotus/Upgrades/Mods/Randomized/") {
const rivenType = item.ItemType.substr(32); const rivenType = item.ItemType.substr(32);
@ -198,10 +213,7 @@ function updateInventory() {
const tr = document.createElement("tr"); const tr = document.createElement("tr");
{ {
const td = document.createElement("td"); const td = document.createElement("td");
td.textContent = td.textContent = itemMap[fingerprint.compat]?.name ?? fingerprint.compat;
itemMap[fingerprint.compat]?.name ??
rivenGenericCompatNames[fingerprint.compat] ??
fingerprint.compat;
td.textContent += " " + RivenParser.parseRiven(rivenType, fingerprint, 1).name; td.textContent += " " + RivenParser.parseRiven(rivenType, fingerprint, 1).name;
td.innerHTML += " <span title='Number of buffs'>▲ " + fingerprint.buffs.length + "</span>"; td.innerHTML += " <span title='Number of buffs'>▲ " + fingerprint.buffs.length + "</span>";
td.innerHTML += " <span title='Number of curses'>▼ " + fingerprint.curses.length + "</span>"; td.innerHTML += " <span title='Number of curses'>▼ " + fingerprint.curses.length + "</span>";
@ -245,6 +257,92 @@ function updateInventory() {
tr.appendChild(td); tr.appendChild(td);
} }
document.getElementById("riven-list").appendChild(tr); document.getElementById("riven-list").appendChild(tr);
} else {
const tr = document.createElement("tr");
const rank = parseInt(JSON.parse(item.UpgradeFingerprint).lvl);
const maxRank = itemMap[item.ItemType]?.fusionLimit ?? 5;
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
td.innerHTML += " <span title='Rank'>★ " + rank + "/" + maxRank + "</span>";
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end";
if (rank < maxRank) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setFingerprint(item.ItemType, item.ItemId, { lvl: maxRank });
};
a.textContent = "Max Rank";
td.appendChild(a);
const span = document.createElement("span");
span.innerHTML = " &middot; ";
td.appendChild(span);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
disposeOfGear("Upgrades", item.ItemId.$oid);
};
a.textContent = "Remove";
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("mods-list").appendChild(tr);
}
});
data.RawUpgrades.forEach(item => {
if (item.ItemCount > 0) {
const maxRank = itemMap[item.ItemType]?.fusionLimit ?? 5;
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
td.innerHTML += " <span title='Rank'>★ 0/" + maxRank + "</span>";
if (item.ItemCount > 1) {
td.innerHTML += " <span title='Count'>🗍 " + parseInt(item.ItemCount) + "</span>";
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end";
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setFingerprint(item.ItemType, item.LastAdded, { lvl: maxRank });
};
a.textContent = "Max Rank";
td.appendChild(a);
}
{
const span = document.createElement("span");
span.innerHTML = " &middot; ";
td.appendChild(span);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
disposeOfItems("Upgrades", item.ItemType, item.ItemCount);
};
a.textContent = "Remove";
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("mods-list").appendChild(tr);
} }
}); });
}); });
@ -352,6 +450,29 @@ function disposeOfGear(category, oid) {
}); });
} }
function disposeOfItems(category, type, count) {
const data = {
SellCurrency: "SC_RegularCredits",
SellPrice: 0,
Items: {}
};
data.Items[category] = [
{
String: type,
Count: count
}
];
revalidateAuthz(() => {
$.post({
url: "/api/sell.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify(data)
}).done(function () {
updateInventory();
});
});
}
function doAcquireMiscItems() { function doAcquireMiscItems() {
const uniqueName = getKey(document.getElementById("miscitem-type")); const uniqueName = getKey(document.getElementById("miscitem-type"));
if (!uniqueName) { if (!uniqueName) {
@ -445,3 +566,53 @@ function doAcquireRiven() {
$("#addriven-fingerprint").on("input", () => { $("#addriven-fingerprint").on("input", () => {
$("#addriven-fingerprint").removeClass("is-invalid"); $("#addriven-fingerprint").removeClass("is-invalid");
}); });
function setFingerprint(ItemType, ItemId, fingerprint) {
revalidateAuthz(() => {
$.post({
url: "/api/artifacts.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify({
Upgrade: {
ItemType,
ItemId,
UpgradeFingerprint: JSON.stringify(fingerprint)
},
LevelDiff: 0,
Cost: 0,
FusionPointCost: 0
})
}).done(function () {
updateInventory();
});
});
}
function doAcquireMod() {
const uniqueName = getKey(document.getElementById("mod-to-acquire"));
if (!uniqueName) {
$("#mod-to-acquire").addClass("is-invalid").focus();
return;
}
revalidateAuthz(() => {
$.post({
url: "/api/missionInventoryUpdate.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify({
RawUpgrades: [
{
ItemType: uniqueName,
ItemCount: 1
}
]
})
}).done(function () {
document.getElementById("mod-to-acquire").value = "";
updateInventory();
});
});
}
$("#mod-to-acquire").on("input", () => {
$("#mod-to-acquire").removeClass("is-invalid");
});

View File

@ -1,14 +1,13 @@
@media (min-width: 992px) { @media (min-width: 992px) {
body.logged-in #main-view { body.logged-in #main-view {
display: grid; display: flex;
grid-template-columns: 1fr 8fr;
gap: 1.5rem;
} }
body.logged-in #sidebar { body.logged-in #sidebar {
position: sticky; position: sticky;
top: 5rem; top: 5rem;
height: 100px; height: 100px;
margin-right: 3rem;
} }
body:not(.logged-in) #sidebar { body:not(.logged-in) #sidebar {