From 7c3ebad987b869427dd9d5be9c949f105b745934 Mon Sep 17 00:00:00 2001 From: ny <64143453+nyaoouo@users.noreply.github.com> Date: Tue, 10 Jun 2025 11:31:23 +0800 Subject: [PATCH 1/9] Init Commit --- .../custom/getItemListsController.ts | 12 +- .../custom/setBoosterController.ts | 37 +++++ src/routes/custom.ts | 2 + static/webui/index.html | 15 ++ static/webui/script.js | 140 ++++++++++++++++++ static/webui/translations/de.js | 1 + static/webui/translations/en.js | 1 + static/webui/translations/es.js | 1 + static/webui/translations/fr.js | 1 + static/webui/translations/ru.js | 1 + static/webui/translations/zh.js | 1 + 11 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 src/controllers/custom/setBoosterController.ts diff --git a/src/controllers/custom/getItemListsController.ts b/src/controllers/custom/getItemListsController.ts index 4f03ee94..a9f8095f 100644 --- a/src/controllers/custom/getItemListsController.ts +++ b/src/controllers/custom/getItemListsController.ts @@ -3,6 +3,7 @@ import { getDict, getItemName, getString } from "@/src/services/itemDataService" import { ExportArcanes, ExportAvionics, + ExportBoosters, ExportCustoms, ExportDrones, ExportGear, @@ -55,6 +56,7 @@ interface ItemLists { KubrowPets: ListedItem[]; EvolutionProgress: ListedItem[]; mods: ListedItem[]; + Boosters: ListedItem[]; } const relicQualitySuffixes: Record = { @@ -86,7 +88,8 @@ const getItemListsController: RequestHandler = (req, response) => { QuestKeys: [], KubrowPets: [], EvolutionProgress: [], - mods: [] + mods: [], + Boosters: [] }; for (const [uniqueName, item] of Object.entries(ExportWarframes)) { res[item.productCategory].push({ @@ -296,6 +299,13 @@ const getItemListsController: RequestHandler = (req, response) => { }); } + for (const item of Object.values(ExportBoosters)) { + res.Boosters.push({ + uniqueName: item.typeName, + name: getString(item.name, lang) + }); + } + response.json(res); }; diff --git a/src/controllers/custom/setBoosterController.ts b/src/controllers/custom/setBoosterController.ts new file mode 100644 index 00000000..f19a3093 --- /dev/null +++ b/src/controllers/custom/setBoosterController.ts @@ -0,0 +1,37 @@ +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { getInventory } from "@/src/services/inventoryService"; +import { RequestHandler } from "express"; +import { ExportBoosters } from "warframe-public-export-plus"; + +const I32_MAX = 0x7fffffff; + +export const setBoosterController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const requests = req.body as { ItemType: string; ExpiryDate: number }[]; + const inventory = await getInventory(accountId); + const boosters = inventory.Boosters; + if ( + requests.some(request => { + if (typeof request.ItemType !== "string") return true; + if (Object.entries(ExportBoosters).find(([_, item]) => item.typeName === request.ItemType) === undefined) + return true; + if (typeof request.ExpiryDate !== "number") return true; + if (request.ExpiryDate < 0 || request.ExpiryDate > I32_MAX) return true; + return false; + }) + ) { + res.status(400).send("Invalid ItemType provided."); + return; + } + // Remove if ExpiryDate lower than current time? + for (const { ItemType, ExpiryDate } of requests) { + const boosterItem = boosters.find(item => item.ItemType === ItemType); + if (boosterItem) { + boosterItem.ExpiryDate = ExpiryDate; + } else { + boosters.push({ ItemType, ExpiryDate }); + } + } + await inventory.save(); + res.end(); +}; diff --git a/src/routes/custom.ts b/src/routes/custom.ts index 8411d996..7d8c7c82 100644 --- a/src/routes/custom.ts +++ b/src/routes/custom.ts @@ -23,6 +23,7 @@ import { setEvolutionProgressController } from "@/src/controllers/custom/setEvol import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController"; import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController"; +import { setBoosterController } from "../controllers/custom/setBoosterController"; const customRouter = express.Router(); @@ -46,6 +47,7 @@ customRouter.post("/addXp", addXpController); customRouter.post("/import", importController); customRouter.post("/manageQuests", manageQuestsController); customRouter.post("/setEvolutionProgress", setEvolutionProgressController); +customRouter.post("/setBooster", setBoosterController); customRouter.get("/config", getConfigDataController); customRouter.post("/config", updateConfigDataController); diff --git a/static/webui/index.html b/static/webui/index.html index 1b42793e..643270a7 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -416,6 +416,20 @@ +
+
+
+
+
+ + +
+ + +
+
+
+
@@ -804,6 +818,7 @@ + diff --git a/static/webui/script.js b/static/webui/script.js index f4abcb9f..c0550a4f 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1011,6 +1011,78 @@ function updateInventory() { } } document.getElementById("changeSyndicate").value = data.SupportedSyndicate ?? ""; + + + document.getElementById("Boosters-list").innerHTML = ""; + const now = Math.floor(Date.now() / 1000); + data.Boosters.forEach(({ItemType, ExpiryDate}) => { + if (ExpiryDate < now) { + // Booster has expired, skip it + return; + } + const tr = document.createElement("tr"); + { + const td = document.createElement("td"); + td.textContent = itemMap[ItemType]?.name ?? ItemType; + tr.appendChild(td); + } + { + const td = document.createElement("td"); + td.classList = "text-end text-nowrap"; + const timeString = formatDatetime("%Y-%m-%d %H:%M:%s", ExpiryDate * 1000); + const inlineForm = document.createElement("form"); + const input = document.createElement("input"); + const a = document.createElement("a"); + + a.href = "#"; + a.onclick = (event)=>{ + event.preventDefault(); + if (inlineForm.style.display === "none") { + inlineForm.style.display = "inline"; + input.value = timeString; + a.style.display = "none"; + input.focus(); + } else { + inlineForm.style.display = "none"; + a.style.display = "inline"; + input.value = ""; + } + }; + a.textContent = timeString; + a.title = loc("code_changeExpiry"); + a.classList.add("text-decoration-none"); + td.appendChild(a); + + const submit = ()=>{ + if (doChangeBoosterExpiry(ItemType, input.value)){ + inlineForm.style.display = "none"; + input.value = ""; + a.style.display = "inline"; + } + }; + + inlineForm.style.display = "none"; + inlineForm.onsubmit = function (event) { + event.preventDefault(); + submit(); + }; + input.type = "datetime-local"; + input.classList.add("form-control"); + input.classList.add("form-control-sm"); + input.value = timeString; + input.onblur = function () { + if (inlineForm.style.display === "inline") { + submit(); + } + } + inlineForm.appendChild(input); + + td.appendChild(inlineForm); + + tr.appendChild(td); + } + document.getElementById("Boosters-list").appendChild(tr); + }) }); }); } @@ -2027,3 +2099,71 @@ function handleModularSelection(category) { }); }); } + +function setBooster(ItemType, ExpiryDate) { + revalidateAuthz(() => { + $.post({ + url: "/custom/setBooster?" + window.authz, + contentType: "application/json", + data: JSON.stringify([{ + ItemType, + ExpiryDate + }]) + }).done(function () { + updateInventory(); + }); + }); +} + +function doAcquireBoosters() { + const uniqueName = getKey(document.getElementById("acquire-type-Boosters")); + if (!uniqueName) { + $("#acquire-type-Boosters").addClass("is-invalid").focus(); + return; + } + const ExpiryDate = (Date.now() / 1000) + 3 * 24 * 60 * 60; // default 3 days + setBooster(uniqueName, ExpiryDate); +} + +function doChangeBoosterExpiry(ItemType, ExpiryDateInput) { + console.log("Changing booster expiry for", ItemType, "to", ExpiryDateInput); + // cast local datetime string to unix timestamp + const ExpiryDate = new Date(ExpiryDateInput).getTime() / 1000; + if (isNaN(ExpiryDate)) { + $("#expiry-date-" + ItemType).addClass("is-invalid").focus(); + return false; + } + setBooster(ItemType, ExpiryDate); + return true; +} + +function formatDatetime(fmt, date) { + if (typeof date === 'number') date = new Date(date); + return fmt.replace(/(%[yY]|%m|%[Dd]|%H|%h|%M|%[Ss]|%[Pp])/g, match => { + switch (match) { + case '%Y': + return date.getFullYear().toString(); + case '%y': + return date.getFullYear().toString().slice(-2); + case '%m': + return (date.getMonth() + 1).toString().padStart(2, '0'); + case '%D': + case '%d': + return date.getDate().toString().padStart(2, '0'); + case '%H': + return date.getHours().toString().padStart(2, '0'); + case '%h': + return (date.getHours() % 12).toString().padStart(2, '0'); + case '%M': + return date.getMinutes().toString().padStart(2, '0'); + case '%S': + case '%s': + return date.getSeconds().toString().padStart(2, '0'); + case '%P': + case '%p': + return date.getHours() < 12 ? 'am' : 'pm'; + default: + return match; + } + }) +} \ No newline at end of file diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index eb721391..4e1b8a2d 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -98,6 +98,7 @@ dict = { inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`, inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`, inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`, + inventory_Boosters: `[UNTRANSLATED] Boosters`, quests_list: `Quests`, quests_completeAll: `Alle Quests abschließen`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index cd718917..79cec6e7 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -97,6 +97,7 @@ dict = { inventory_bulkRankUpSentinels: `Max Rank All Sentinels`, inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`, inventory_bulkRankUpEvolutionProgress: `Max Rank All Incarnon Evolution Progress`, + inventory_Boosters: `Boosters`, quests_list: `Quests`, quests_completeAll: `Complete All Quests`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 652a850c..72d8453b 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -98,6 +98,7 @@ dict = { inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`, inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`, inventory_bulkRankUpEvolutionProgress: `Maximizar todo el progreso de evolución Incarnon`, + inventory_Boosters: `[UNTRANSLATED] Boosters`, quests_list: `Misiones`, quests_completeAll: `Completar todas las misiones`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 5a61111e..bc079ef0 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -98,6 +98,7 @@ dict = { inventory_bulkRankUpSentinels: `Toutes les Sentinelles au rang max`, inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles au rang max`, inventory_bulkRankUpEvolutionProgress: `Toutes les évolutions Incarnon au rang max`, + inventory_Boosters: `[UNTRANSLATED] Boosters`, quests_list: `Quêtes`, quests_completeAll: `Compléter toutes les quêtes`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 445c519a..c99ca154 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -98,6 +98,7 @@ dict = { inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`, inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`, inventory_bulkRankUpEvolutionProgress: `Максимальный ранг всех эволюций Инкарнонов`, + inventory_Boosters: `[UNTRANSLATED] Boosters`, quests_list: `Квесты`, quests_completeAll: `Завершить все квесты`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index d46ce77d..d6eb68b3 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -98,6 +98,7 @@ dict = { inventory_bulkRankUpSentinels: `所有守护升满级`, inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`, inventory_bulkRankUpEvolutionProgress: `所有灵化之源最大等级`, + inventory_Boosters: `加成器`, quests_list: `任务`, quests_completeAll: `完成所有任务`, -- 2.47.2 From ef8c81ee9e2f871fe17cd52d2e62e52d4ba29510 Mon Sep 17 00:00:00 2001 From: ny <64143453+nyaoouo@users.noreply.github.com> Date: Tue, 10 Jun 2025 11:38:38 +0800 Subject: [PATCH 2/9] Run Lint --- static/webui/script.js | 79 ++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/static/webui/script.js b/static/webui/script.js index c0550a4f..62abca5c 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1012,10 +1012,9 @@ function updateInventory() { } document.getElementById("changeSyndicate").value = data.SupportedSyndicate ?? ""; - document.getElementById("Boosters-list").innerHTML = ""; const now = Math.floor(Date.now() / 1000); - data.Boosters.forEach(({ItemType, ExpiryDate}) => { + data.Boosters.forEach(({ ItemType, ExpiryDate }) => { if (ExpiryDate < now) { // Booster has expired, skip it return; @@ -1035,7 +1034,7 @@ function updateInventory() { const a = document.createElement("a"); a.href = "#"; - a.onclick = (event)=>{ + a.onclick = event => { event.preventDefault(); if (inlineForm.style.display === "none") { inlineForm.style.display = "inline"; @@ -1052,9 +1051,9 @@ function updateInventory() { a.title = loc("code_changeExpiry"); a.classList.add("text-decoration-none"); td.appendChild(a); - - const submit = ()=>{ - if (doChangeBoosterExpiry(ItemType, input.value)){ + + const submit = () => { + if (doChangeBoosterExpiry(ItemType, input.value)) { inlineForm.style.display = "none"; input.value = ""; a.style.display = "inline"; @@ -1074,15 +1073,15 @@ function updateInventory() { if (inlineForm.style.display === "inline") { submit(); } - } + }; inlineForm.appendChild(input); - + td.appendChild(inlineForm); - + tr.appendChild(td); } document.getElementById("Boosters-list").appendChild(tr); - }) + }); }); }); } @@ -2105,10 +2104,12 @@ function setBooster(ItemType, ExpiryDate) { $.post({ url: "/custom/setBooster?" + window.authz, contentType: "application/json", - data: JSON.stringify([{ - ItemType, - ExpiryDate - }]) + data: JSON.stringify([ + { + ItemType, + ExpiryDate + } + ]) }).done(function () { updateInventory(); }); @@ -2121,7 +2122,7 @@ function doAcquireBoosters() { $("#acquire-type-Boosters").addClass("is-invalid").focus(); return; } - const ExpiryDate = (Date.now() / 1000) + 3 * 24 * 60 * 60; // default 3 days + const ExpiryDate = Date.now() / 1000 + 3 * 24 * 60 * 60; // default 3 days setBooster(uniqueName, ExpiryDate); } @@ -2130,7 +2131,9 @@ function doChangeBoosterExpiry(ItemType, ExpiryDateInput) { // cast local datetime string to unix timestamp const ExpiryDate = new Date(ExpiryDateInput).getTime() / 1000; if (isNaN(ExpiryDate)) { - $("#expiry-date-" + ItemType).addClass("is-invalid").focus(); + $("#expiry-date-" + ItemType) + .addClass("is-invalid") + .focus(); return false; } setBooster(ItemType, ExpiryDate); @@ -2138,32 +2141,32 @@ function doChangeBoosterExpiry(ItemType, ExpiryDateInput) { } function formatDatetime(fmt, date) { - if (typeof date === 'number') date = new Date(date); + if (typeof date === "number") date = new Date(date); return fmt.replace(/(%[yY]|%m|%[Dd]|%H|%h|%M|%[Ss]|%[Pp])/g, match => { switch (match) { - case '%Y': + case "%Y": return date.getFullYear().toString(); - case '%y': + case "%y": return date.getFullYear().toString().slice(-2); - case '%m': - return (date.getMonth() + 1).toString().padStart(2, '0'); - case '%D': - case '%d': - return date.getDate().toString().padStart(2, '0'); - case '%H': - return date.getHours().toString().padStart(2, '0'); - case '%h': - return (date.getHours() % 12).toString().padStart(2, '0'); - case '%M': - return date.getMinutes().toString().padStart(2, '0'); - case '%S': - case '%s': - return date.getSeconds().toString().padStart(2, '0'); - case '%P': - case '%p': - return date.getHours() < 12 ? 'am' : 'pm'; + case "%m": + return (date.getMonth() + 1).toString().padStart(2, "0"); + case "%D": + case "%d": + return date.getDate().toString().padStart(2, "0"); + case "%H": + return date.getHours().toString().padStart(2, "0"); + case "%h": + return (date.getHours() % 12).toString().padStart(2, "0"); + case "%M": + return date.getMinutes().toString().padStart(2, "0"); + case "%S": + case "%s": + return date.getSeconds().toString().padStart(2, "0"); + case "%P": + case "%p": + return date.getHours() < 12 ? "am" : "pm"; default: return match; } - }) -} \ No newline at end of file + }); +} -- 2.47.2 From 7957dc1e24398c9ae27ad9968d6d91728f241a8e Mon Sep 17 00:00:00 2001 From: nyaoouo <64143453+nyaoouo@users.noreply.github.com> Date: Tue, 10 Jun 2025 19:51:00 +0800 Subject: [PATCH 3/9] Remove booster if ExpiryDate lower than current time --- src/controllers/custom/setBoosterController.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/controllers/custom/setBoosterController.ts b/src/controllers/custom/setBoosterController.ts index f19a3093..ceb45e30 100644 --- a/src/controllers/custom/setBoosterController.ts +++ b/src/controllers/custom/setBoosterController.ts @@ -23,13 +23,21 @@ export const setBoosterController: RequestHandler = async (req, res) => { res.status(400).send("Invalid ItemType provided."); return; } - // Remove if ExpiryDate lower than current time? + const now = Math.floor(Date.now() / 1000); for (const { ItemType, ExpiryDate } of requests) { - const boosterItem = boosters.find(item => item.ItemType === ItemType); - if (boosterItem) { - boosterItem.ExpiryDate = ExpiryDate; + if (ExpiryDate > now) { + // remove expired boosters + const index = boosters.findIndex(item => item.ItemType === ItemType); + if (index !== -1) { + boosters.splice(index, 1); + } } else { - boosters.push({ ItemType, ExpiryDate }); + const boosterItem = boosters.find(item => item.ItemType === ItemType); + if (boosterItem) { + boosterItem.ExpiryDate = ExpiryDate; + } else { + boosters.push({ ItemType, ExpiryDate }); + } } } await inventory.save(); -- 2.47.2 From 58ea73a116e56f95249904d8642bcede2243e387 Mon Sep 17 00:00:00 2001 From: nyaoouo <64143453+nyaoouo@users.noreply.github.com> Date: Tue, 10 Jun 2025 21:12:49 +0800 Subject: [PATCH 4/9] fix logical error --- src/controllers/custom/setBoosterController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/custom/setBoosterController.ts b/src/controllers/custom/setBoosterController.ts index ceb45e30..4da583b0 100644 --- a/src/controllers/custom/setBoosterController.ts +++ b/src/controllers/custom/setBoosterController.ts @@ -25,7 +25,7 @@ export const setBoosterController: RequestHandler = async (req, res) => { } const now = Math.floor(Date.now() / 1000); for (const { ItemType, ExpiryDate } of requests) { - if (ExpiryDate > now) { + if (ExpiryDate < now) { // remove expired boosters const index = boosters.findIndex(item => item.ItemType === ItemType); if (index !== -1) { -- 2.47.2 From fe9f19af1c9b46b48f92027c5049ac8d00c16652 Mon Sep 17 00:00:00 2001 From: ny <64143453+nyaoouo@users.noreply.github.com> Date: Wed, 11 Jun 2025 12:32:07 +0800 Subject: [PATCH 5/9] add remove button and clear input after add booster --- static/webui/script.js | 29 +++++++++++++++++++++-------- static/webui/translations/de.js | 1 + static/webui/translations/en.js | 1 + static/webui/translations/es.js | 1 + static/webui/translations/fr.js | 1 + static/webui/translations/ru.js | 1 + static/webui/translations/zh.js | 1 + 7 files changed, 27 insertions(+), 8 deletions(-) diff --git a/static/webui/script.js b/static/webui/script.js index 62abca5c..9024c02e 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1033,6 +1033,17 @@ function updateInventory() { const input = document.createElement("input"); const a = document.createElement("a"); + const removeButton = document.createElement("a"); + removeButton.title = loc("code_remove"); + removeButton.innerHTML = ``; + removeButton.href = "#"; + removeButton.onclick = function (event) { + event.preventDefault(); + setBooster(ItemType, 0); + }; + removeButton.classList.add("me-2"); + td.appendChild(removeButton); + a.href = "#"; a.onclick = event => { event.preventDefault(); @@ -1053,7 +1064,7 @@ function updateInventory() { td.appendChild(a); const submit = () => { - if (doChangeBoosterExpiry(ItemType, input.value)) { + if (doChangeBoosterExpiry(ItemType, input)) { inlineForm.style.display = "none"; input.value = ""; a.style.display = "inline"; @@ -2099,7 +2110,7 @@ function handleModularSelection(category) { }); } -function setBooster(ItemType, ExpiryDate) { +function setBooster(ItemType, ExpiryDate, callback) { revalidateAuthz(() => { $.post({ url: "/custom/setBooster?" + window.authz, @@ -2112,6 +2123,7 @@ function setBooster(ItemType, ExpiryDate) { ]) }).done(function () { updateInventory(); + if (callback) callback(); }); }); } @@ -2123,17 +2135,18 @@ function doAcquireBoosters() { return; } const ExpiryDate = Date.now() / 1000 + 3 * 24 * 60 * 60; // default 3 days - setBooster(uniqueName, ExpiryDate); + setBooster(uniqueName, ExpiryDate, () => { + $("#acquire-type-Boosters").val(""); + updateInventory(); + }); } function doChangeBoosterExpiry(ItemType, ExpiryDateInput) { - console.log("Changing booster expiry for", ItemType, "to", ExpiryDateInput); + console.log("Changing booster expiry for", ItemType, "to", ExpiryDateInput.value); // cast local datetime string to unix timestamp - const ExpiryDate = new Date(ExpiryDateInput).getTime() / 1000; + const ExpiryDate = new Date(ExpiryDateInput.value).getTime() / 1000; if (isNaN(ExpiryDate)) { - $("#expiry-date-" + ItemType) - .addClass("is-invalid") - .focus(); + ExpiryDateInput.addClass("is-invalid").focus(); return false; } setBooster(ItemType, ExpiryDate); diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 4e1b8a2d..9fb692df 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -37,6 +37,7 @@ dict = { code_rankUp: `Rang erhöhen`, code_rankDown: `Rang verringern`, code_count: `Anzahl`, + code_changeExpiry: `[UNTRANSLATED] Change Expiry Time`, code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`, code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`, code_addModsConfirm: `Bist du sicher, dass du |COUNT| Mods zu deinem Account hinzufügen möchtest?`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 79cec6e7..3372d574 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -36,6 +36,7 @@ dict = { code_rankUp: `Rank up`, code_rankDown: `Rank down`, code_count: `Count`, + code_changeExpiry: `Change Expiry Time`, code_focusAllUnlocked: `All focus schools are already unlocked.`, code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that.`, code_addModsConfirm: `Are you sure you want to add |COUNT| mods to your account?`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 72d8453b..c1c7d549 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -37,6 +37,7 @@ dict = { code_rankUp: `Subir de rango`, code_rankDown: `Bajar de rango`, code_count: `Cantidad`, + code_changeExpiry: `[UNTRANSLATED] Change Expiry Time`, code_focusAllUnlocked: `Todas las escuelas de enfoque ya están desbloqueadas.`, code_focusUnlocked: `¡Desbloqueadas |COUNT| nuevas escuelas de enfoque! Se necesita una actualización del inventario para reflejar los cambios en el juego. Visitar la navegación debería ser la forma más sencilla de activarlo.`, code_addModsConfirm: `¿Estás seguro de que deseas agregar |COUNT| modificadores a tu cuenta?`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index bc079ef0..31bee75e 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -37,6 +37,7 @@ dict = { code_rankUp: `Monter de rang`, code_rankDown: `Baisser de rang`, code_count: `Quantité`, + code_changeExpiry: `[UNTRANSLATED] Change Expiry Time`, code_focusAllUnlocked: `Les écoles de Focus sont déjà déverrouillées.`, code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`, code_addModsConfirm: `Ajouter |COUNT| mods à l'inventaire ?`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index c99ca154..181f6fac 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -37,6 +37,7 @@ dict = { code_rankUp: `Повысить Ранг`, code_rankDown: `Понизить Ранг`, code_count: `Количество`, + code_changeExpiry: `[UNTRANSLATED] Change Expiry Time`, code_focusAllUnlocked: `Все школы фокуса уже разблокированы.`, code_focusUnlocked: `Разблокировано |COUNT| новых школ фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`, code_addModsConfirm: `Вы уверены, что хотите добавить |COUNT| модов на ваш аккаунт?`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index d6eb68b3..24561018 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -37,6 +37,7 @@ dict = { code_rankUp: `[UNTRANSLATED] Rank up`, code_rankDown: `[UNTRANSLATED] Rank down`, code_count: `数量`, + code_changeExpiry: `更改逾期时间`, code_focusAllUnlocked: `所有专精学派均已解锁。`, code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新。`, code_addModsConfirm: `确定要向账户添加 |COUNT| 张MOD吗?`, -- 2.47.2 From fe1d700c1f5ab016d91b169e15a28f111d3c43ea Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 11 Jun 2025 11:59:44 +0200 Subject: [PATCH 6/9] move delete button to the right --- static/webui/script.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/static/webui/script.js b/static/webui/script.js index 9024c02e..ad897f45 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1033,17 +1033,6 @@ function updateInventory() { const input = document.createElement("input"); const a = document.createElement("a"); - const removeButton = document.createElement("a"); - removeButton.title = loc("code_remove"); - removeButton.innerHTML = ``; - removeButton.href = "#"; - removeButton.onclick = function (event) { - event.preventDefault(); - setBooster(ItemType, 0); - }; - removeButton.classList.add("me-2"); - td.appendChild(removeButton); - a.href = "#"; a.onclick = event => { event.preventDefault(); @@ -1089,6 +1078,17 @@ function updateInventory() { td.appendChild(inlineForm); + const removeButton = document.createElement("a"); + removeButton.title = loc("code_remove"); + removeButton.innerHTML = ``; + removeButton.href = "#"; + removeButton.onclick = function (event) { + event.preventDefault(); + setBooster(ItemType, 0); + }; + removeButton.classList.add("me-2"); + td.appendChild(removeButton); + tr.appendChild(td); } document.getElementById("Boosters-list").appendChild(tr); -- 2.47.2 From 3c6ec5c5c997cb85406777ca443a201a6ab39f3f Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 11 Jun 2025 12:00:22 +0200 Subject: [PATCH 7/9] remove me-2 --- static/webui/script.js | 1 - 1 file changed, 1 deletion(-) diff --git a/static/webui/script.js b/static/webui/script.js index ad897f45..05a9a400 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1086,7 +1086,6 @@ function updateInventory() { event.preventDefault(); setBooster(ItemType, 0); }; - removeButton.classList.add("me-2"); td.appendChild(removeButton); tr.appendChild(td); -- 2.47.2 From 675c2a7b82514433b3a8f8cdd20f02417ed8a58a Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 11 Jun 2025 12:06:40 +0200 Subject: [PATCH 8/9] remove stupid semantics --- static/webui/script.js | 40 +++++++-------------------------- static/webui/translations/de.js | 1 - static/webui/translations/en.js | 1 - static/webui/translations/es.js | 1 - static/webui/translations/fr.js | 1 - static/webui/translations/ru.js | 1 - static/webui/translations/zh.js | 1 - 7 files changed, 8 insertions(+), 38 deletions(-) diff --git a/static/webui/script.js b/static/webui/script.js index 05a9a400..ee16ffdf 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1031,47 +1031,23 @@ function updateInventory() { const timeString = formatDatetime("%Y-%m-%d %H:%M:%s", ExpiryDate * 1000); const inlineForm = document.createElement("form"); const input = document.createElement("input"); - const a = document.createElement("a"); - a.href = "#"; - a.onclick = event => { - event.preventDefault(); - if (inlineForm.style.display === "none") { - inlineForm.style.display = "inline"; - input.value = timeString; - a.style.display = "none"; - input.focus(); - } else { - inlineForm.style.display = "none"; - a.style.display = "inline"; - input.value = ""; - } - }; - a.textContent = timeString; - a.title = loc("code_changeExpiry"); - a.classList.add("text-decoration-none"); - td.appendChild(a); - - const submit = () => { - if (doChangeBoosterExpiry(ItemType, input)) { - inlineForm.style.display = "none"; - input.value = ""; - a.style.display = "inline"; - } - }; - - inlineForm.style.display = "none"; + inlineForm.style.display = "inline-block"; inlineForm.onsubmit = function (event) { event.preventDefault(); - submit(); + doChangeBoosterExpiry(ItemType, input); }; input.type = "datetime-local"; input.classList.add("form-control"); input.classList.add("form-control-sm"); input.value = timeString; + let changed = false; + input.onchange = function () { + changed = true; + }; input.onblur = function () { - if (inlineForm.style.display === "inline") { - submit(); + if (changed) { + doChangeBoosterExpiry(ItemType, input); } }; inlineForm.appendChild(input); diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 9fb692df..4e1b8a2d 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -37,7 +37,6 @@ dict = { code_rankUp: `Rang erhöhen`, code_rankDown: `Rang verringern`, code_count: `Anzahl`, - code_changeExpiry: `[UNTRANSLATED] Change Expiry Time`, code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`, code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`, code_addModsConfirm: `Bist du sicher, dass du |COUNT| Mods zu deinem Account hinzufügen möchtest?`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 3372d574..79cec6e7 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -36,7 +36,6 @@ dict = { code_rankUp: `Rank up`, code_rankDown: `Rank down`, code_count: `Count`, - code_changeExpiry: `Change Expiry Time`, code_focusAllUnlocked: `All focus schools are already unlocked.`, code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that.`, code_addModsConfirm: `Are you sure you want to add |COUNT| mods to your account?`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index c1c7d549..72d8453b 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -37,7 +37,6 @@ dict = { code_rankUp: `Subir de rango`, code_rankDown: `Bajar de rango`, code_count: `Cantidad`, - code_changeExpiry: `[UNTRANSLATED] Change Expiry Time`, code_focusAllUnlocked: `Todas las escuelas de enfoque ya están desbloqueadas.`, code_focusUnlocked: `¡Desbloqueadas |COUNT| nuevas escuelas de enfoque! Se necesita una actualización del inventario para reflejar los cambios en el juego. Visitar la navegación debería ser la forma más sencilla de activarlo.`, code_addModsConfirm: `¿Estás seguro de que deseas agregar |COUNT| modificadores a tu cuenta?`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 31bee75e..bc079ef0 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -37,7 +37,6 @@ dict = { code_rankUp: `Monter de rang`, code_rankDown: `Baisser de rang`, code_count: `Quantité`, - code_changeExpiry: `[UNTRANSLATED] Change Expiry Time`, code_focusAllUnlocked: `Les écoles de Focus sont déjà déverrouillées.`, code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`, code_addModsConfirm: `Ajouter |COUNT| mods à l'inventaire ?`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 181f6fac..c99ca154 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -37,7 +37,6 @@ dict = { code_rankUp: `Повысить Ранг`, code_rankDown: `Понизить Ранг`, code_count: `Количество`, - code_changeExpiry: `[UNTRANSLATED] Change Expiry Time`, code_focusAllUnlocked: `Все школы фокуса уже разблокированы.`, code_focusUnlocked: `Разблокировано |COUNT| новых школ фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`, code_addModsConfirm: `Вы уверены, что хотите добавить |COUNT| модов на ваш аккаунт?`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 24561018..d6eb68b3 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -37,7 +37,6 @@ dict = { code_rankUp: `[UNTRANSLATED] Rank up`, code_rankDown: `[UNTRANSLATED] Rank down`, code_count: `数量`, - code_changeExpiry: `更改逾期时间`, code_focusAllUnlocked: `所有专精学派均已解锁。`, code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新。`, code_addModsConfirm: `确定要向账户添加 |COUNT| 张MOD吗?`, -- 2.47.2 From 6296f80987a610637f7fbae9f9a27ac3aeff34bb Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 11 Jun 2025 12:12:36 +0200 Subject: [PATCH 9/9] use inventory projection --- src/controllers/custom/setBoosterController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/custom/setBoosterController.ts b/src/controllers/custom/setBoosterController.ts index 4da583b0..28939614 100644 --- a/src/controllers/custom/setBoosterController.ts +++ b/src/controllers/custom/setBoosterController.ts @@ -8,7 +8,7 @@ const I32_MAX = 0x7fffffff; export const setBoosterController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); const requests = req.body as { ItemType: string; ExpiryDate: number }[]; - const inventory = await getInventory(accountId); + const inventory = await getInventory(accountId, "Boosters"); const boosters = inventory.Boosters; if ( requests.some(request => { -- 2.47.2