feat(webui): edit suit invigorations #2478

Merged
Sainan merged 12 commits from nyaoouo/SpaceNinjaServer:ImplEditSuitInvigorationUpgradeController into main 2025-07-14 20:33:38 -07:00
4 changed files with 219 additions and 0 deletions
Showing only changes of commit 8c2b4f2c48 - Show all commits

View File

@ -0,0 +1,34 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
import { RequestHandler } from "express";
const DEFAULT_UPGRADE_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
export const editSuitInvigorationUpgradeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const { oid, data } = req.body as {
oid: string;
data?: {
DefensiveUpgrade: string;
OffensiveUpgrade: string;
UpgradesExpiry?: number;
};
};
const inventory = await getInventory(accountId);
const suit = inventory.Suits.id(oid)!;
if (data) {
suit.DefensiveUpgrade = data.DefensiveUpgrade;
suit.OffensiveUpgrade = data.OffensiveUpgrade;
if (data.UpgradesExpiry) {
suit.UpgradesExpiry = new Date(data.UpgradesExpiry);
} else {
suit.UpgradesExpiry = new Date(Date.now() + DEFAULT_UPGRADE_EXPIRY_MS);
}
} else {
suit.DefensiveUpgrade = undefined;
suit.OffensiveUpgrade = undefined;
suit.UpgradesExpiry = undefined;
}
await inventory.save();
res.end();
};

View File

@ -14,6 +14,7 @@ import { addMissingMaxRankModsController } from "@/src/controllers/custom/addMis
import { webuiFileChangeDetectedController } from "@/src/controllers/custom/webuiFileChangeDetectedController";
import { completeAllMissionsController } from "@/src/controllers/custom/completeAllMissionsController";
import { addMissingHelminthBlueprintsController } from "@/src/controllers/custom/addMissingHelminthBlueprintsController";
import { editSuitInvigorationUpgradeController } from "@/src/controllers/custom/editSuitInvigorationUpgradeController";
import { createAccountController } from "@/src/controllers/custom/createAccountController";
import { createMessageController } from "@/src/controllers/custom/createMessageController";
@ -45,6 +46,7 @@ customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController);
customRouter.get("/webuiFileChangeDetected", webuiFileChangeDetectedController);
customRouter.get("/completeAllMissions", completeAllMissionsController);
customRouter.get("/addMissingHelminthBlueprints", addMissingHelminthBlueprintsController);
customRouter.post("/editSuitInvigorationUpgrade", editSuitInvigorationUpgradeController);
Sainan marked this conversation as resolved Outdated

Really? Don't see what's wrong here?

Really? Don't see what's wrong here?
customRouter.post("/createAccount", createAccountController);
customRouter.post("/createMessage", createMessageController);

View File

@ -977,6 +977,59 @@
</div>
<div class="toast-container position-fixed bottom-0 end-0 p-3"></div>
</div>
<div id="editSuitInvigorationForm" class="card" style="display: none;">
Sainan marked this conversation as resolved Outdated

What is this shit? We have a detailedView, this is completely misplaced.

What is this shit? We have a detailedView, this is completely misplaced.

detailed view dont pass detail of item to view, cannot auto fillin data

detailed view dont pass detail of item to view, cannot auto fillin data

Damn, all the other features on the detailed view must be magic then. Purely guessing the data to fill in. What a miracle that it guesses correctly.

Damn, all the other features on the detailed view must be magic then. Purely guessing the data to fill in. What a miracle that it guesses correctly.

i found it, but i want to remind u that part is id="archonShards-card" =_=

i found it, but i want to remind u that part is `id="archonShards-card"` =_=

And id="modularParts-card" and id="valenceBonus-card" is under id="detailedView-route"

And `id="modularParts-card"` and `id="valenceBonus-card"` is under `id="detailedView-route"`
<h5 class="card-header">Edit Suit Invigoration Upgrades</h5>
<div class="card-body">
<form onsubmit="submitSuitInvigorationUpgrade(event)">
<input type="hidden" id="invigoration-oid" />
<div class="mb-3">
<label for="invigoration-offensive" class="form-label">Offensive Upgrade</label>
<select class="form-select" id="invigoration-offensive">
<option value="">None</option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPowerStrength">Power Strength</option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPowerRange">Power Range</option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPowerDuration">Power Duration</option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationMeleeDamage">Melee Damage</option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPrimaryDamage">Primary Damage</option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationSecondaryDamage">Secondary Damage</option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPrimaryCritChance">Primary Crit Chance</option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationSecondaryCritChance">Secondary Crit Chance</option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationMeleeCritChance">Melee Crit Chance</option>
</select>
</div>
<div class="mb-3">
<label for="invigoration-defensive" class="form-label">Defensive Upgrade</label>
<select class="form-select" id="invigoration-defensive">
<option value="">None</option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationPowerEfficiency">Power Efficiency</option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationMovementSpeed">Movement Speed</option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationParkourSpeed">Parkour Speed</option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationHealth">Health</option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationEnergy">Energy</option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationStatusResistance">Status Resistance</option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationReloadSpeed">Reload Speed</option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationHealthRegen">Health Regen</option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationArmor">Armor</option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationJumps">Jumps</option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationEnergyRegen">Energy Regen</option>
</select>
</div>
<div class="mb-3">
<label for="invigoration-expiry" class="form-label">Upgrades Expiry (optional)</label>
<input type="datetime-local" class="form-control" id="invigoration-expiry" />
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Apply Upgrades</button>
<button type="button" class="btn btn-secondary" onclick="hideSuitInvigorationForm()">Cancel</button>
<button type="button" class="btn btn-danger" onclick="clearSuitInvigorationUpgrades()">Clear Upgrades</button>
</div>
</form>
</div>
</div>
<datalist id="datalist-Suits"></datalist>
<datalist id="datalist-SpaceSuits"></datalist>
<datalist id="datalist-LongGuns"></datalist>

View File

@ -667,6 +667,21 @@ function updateInventory() {
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (category == "Suits") {
const a = document.createElement("a");
a.href = "#";
a.onclick = () => showSuitInvigorationForm(item);
a.innerHTML = `<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24">
<path d="M15.907 11.998 10.332 9.23a.9.9 0 0 1-.16-.037l-.018-.007v6.554c0 .017.008.034.01.051l2.388-2.974 3.355-.82Z"/>
<path d="m11.463 4.054 5.579 3.323A4.02 4.02 0 0 1 18.525 9c.332.668.47 1.414.398 2.155a3.07 3.07 0 0 1-.745 1.65 3.108 3.108 0 0 1-1.55.951c-.022.007-.045.005-.07.01-.062.03-.126.057-.191.08l-2.72.667-1.992 2.48c-.18.227-.41.409-.67.534.047.034.085.077.137.107a2.05 2.05 0 0 0 1.995.035c.592-.33 2.15-1.201 4.636-2.892l.28-.19c1.328-.895 3.616-2.442 3.967-4.215a9.94 9.94 0 0 0-1.713-4.154 10.027 10.027 0 0 0-3.375-2.989 10.107 10.107 0 0 0-8.802-.418c1.162.287 2.287.704 3.354 1.243Z"/>
<path d="M5.382 17.082v-6.457a3.7 3.7 0 0 1 .45-1.761 3.733 3.733 0 0 1 1.238-1.34 3.915 3.915 0 0 1 3.433-.245c.176.03.347.084.508.161l5.753 2.856c.082.05.161.105.236.165a2.128 2.128 0 0 0-.953-1.455l-5.51-3.284c-1.74-.857-3.906-1.523-5.244-1.097a9.96 9.96 0 0 0-2.5 3.496 9.895 9.895 0 0 0 .283 8.368 9.973 9.973 0 0 0 2.73 3.322 17.161 17.161 0 0 1-.424-2.729Z"/>
<path d="m19.102 16.163-.272.183c-2.557 1.74-4.169 2.64-4.698 2.935a4.083 4.083 0 0 1-2 .53 3.946 3.946 0 0 1-1.983-.535 3.788 3.788 0 0 1-1.36-1.361 3.752 3.752 0 0 1-.51-1.85 1.812 1.812 0 0 1-.043-.26V9.143c0-.024.009-.046.01-.07-.056.02-.11.043-.162.07a1.796 1.796 0 0 0-.787 1.516v6.377a10.67 10.67 0 0 0 1.113 4.27 10.11 10.11 0 0 0 8.505-.53 10.022 10.022 0 0 0 3.282-2.858 9.936 9.936 0 0 0 1.75-3.97 19.615 19.615 0 0 1-2.845 2.216Z"/>
</svg>`;
a.style.textDecoration = "none";
a.title = "";
td.appendChild(a);
}
let maxXP = Math.pow(uniqueLevelCaps[item.ItemType] ?? 30, 2) * 1000;
if (
category != "Suits" &&
@ -2862,3 +2877,118 @@ function handleModularPartsChange(event) {
});
}
}
function showSuitInvigorationForm(suitData) {
document.getElementById("invigoration-oid").value = suitData.ItemId.$oid;
// Auto-fill form with existing data
document.getElementById("invigoration-offensive").value = suitData?.OffensiveUpgrade || "";
document.getElementById("invigoration-defensive").value = suitData?.DefensiveUpgrade || "";
// Handle expiry date
if (suitData?.UpgradesExpiry) {
let expiryDate;
if (suitData.UpgradesExpiry.$date) {
// MongoDB format: { "$date": { "$numberLong": "1752933467151" } }
expiryDate = new Date(parseInt(suitData.UpgradesExpiry.$date.$numberLong));
} else if (typeof suitData.UpgradesExpiry === "number") {
// Timestamp format
expiryDate = new Date(suitData.UpgradesExpiry);
} else if (suitData.UpgradesExpiry instanceof Date) {
// Date object
expiryDate = suitData.UpgradesExpiry;
}
if (expiryDate && !isNaN(expiryDate.getTime())) {
// Format for datetime-local input (YYYY-MM-DDTHH:mm)
const year = expiryDate.getFullYear();
const month = String(expiryDate.getMonth() + 1).padStart(2, "0");
const day = String(expiryDate.getDate()).padStart(2, "0");
const hours = String(expiryDate.getHours()).padStart(2, "0");
const minutes = String(expiryDate.getMinutes()).padStart(2, "0");
document.getElementById("invigoration-expiry").value = `${year}-${month}-${day}T${hours}:${minutes}`;
Sainan marked this conversation as resolved Outdated

...and here.

...and here.
} else {
document.getElementById("invigoration-expiry").value = "";
}
} else {
document.getElementById("invigoration-expiry").value = "";
}
const form = document.getElementById("editSuitInvigorationForm");
form.style.display = "block";
form.style.position = "fixed";
form.style.top = "50%";
form.style.left = "50%";
form.style.transform = "translate(-50%, -50%)";
form.style.zIndex = "1050";
form.style.width = "400px";
Sainan marked this conversation as resolved Outdated

If you need godawful custom CSS, put it in the CSS file.

If you need godawful custom CSS, put it in the CSS file.
// Add backdrop
const backdrop = document.createElement("div");
backdrop.id = "invigoration-backdrop";
backdrop.style.position = "fixed";
backdrop.style.top = "0";
backdrop.style.left = "0";
backdrop.style.width = "100%";
backdrop.style.height = "100%";
backdrop.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
backdrop.style.zIndex = "1040";
backdrop.onclick = hideSuitInvigorationForm;
document.body.appendChild(backdrop);
}
function hideSuitInvigorationForm() {
document.getElementById("editSuitInvigorationForm").style.display = "none";
const backdrop = document.getElementById("invigoration-backdrop");
if (backdrop) {
backdrop.remove();
}
}
function submitSuitInvigorationUpgrade(event) {
event.preventDefault();
const oid = document.getElementById("invigoration-oid").value;
const offensiveUpgrade = document.getElementById("invigoration-offensive").value;
const defensiveUpgrade = document.getElementById("invigoration-defensive").value;
const expiry = document.getElementById("invigoration-expiry").value;
if (!offensiveUpgrade && !defensiveUpgrade) {
alert("Please select at least one upgrade type.");
return;
}
const data = {
OffensiveUpgrade: offensiveUpgrade,
DefensiveUpgrade: defensiveUpgrade
};
if (expiry) {
data.UpgradesExpiry = new Date(expiry).getTime();
}
editSuitInvigorationUpgrade(oid, data);
hideSuitInvigorationForm();
}
function clearSuitInvigorationUpgrades() {
const oid = document.getElementById("invigoration-oid").value;
editSuitInvigorationUpgrade(oid, null);
hideSuitInvigorationForm();
}
async function editSuitInvigorationUpgrade(oid, data) {
/* data?: {
DefensiveUpgrade: string;
OffensiveUpgrade: string;
UpgradesExpiry?: number;
}*/
$.post({
url: "/custom/editSuitInvigorationUpgrade?" + window.authz,
contentType: "application/json",
data: JSON.stringify({ oid, data })
}).done(function () {
updateInventory();
});
}