XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFESpaceNinjaServer

feat(webui): boosters (#2140)

Co-authored-by: ny <64143453+nyaoouo@users.noreply.github.com> Co-authored-by: nyaoouo <64143453+nyaoouo@users.noreply.github.com> Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2140 Co-authored-by: nyaoouo <nyaoouo@noreply.localhost> Co-committed-by: nyaoouo <nyaoouo@noreply.localhost>

62d4b9f6
nyaoouo <nyaoouo@noreply.localhost>
提交于

代码差异

11 个文件 +210 -1
Modified src/controllers/custom/getItemListsController.ts +11 -1
@@ -3,6 +3,7 @@ import { getDict, getItemName, getString } from "@/src/services/itemDataService"
3 3 import {
4 4 ExportArcanes,
5 5 ExportAvionics,
6 ExportBoosters,
6 7 ExportCustoms,
7 8 ExportDrones,
8 9 ExportGear,
@@ -55,6 +56,7 @@ interface ItemLists {
55 56 KubrowPets: ListedItem[];
56 57 EvolutionProgress: ListedItem[];
57 58 mods: ListedItem[];
59 Boosters: ListedItem[];
58 60 }
59 61
60 62 const relicQualitySuffixes: Record<TRelicQuality, string> = {
@@ -86,7 +88,8 @@ const getItemListsController: RequestHandler = (req, response) => {
86 88 QuestKeys: [],
87 89 KubrowPets: [],
88 90 EvolutionProgress: [],
89 mods: []
91 mods: [],
92 Boosters: []
90 93 };
91 94 for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
92 95 res[item.productCategory].push({
@@ -296,6 +299,13 @@ const getItemListsController: RequestHandler = (req, response) => {
296 299 });
297 300 }
298 301
302 for (const item of Object.values(ExportBoosters)) {
303 res.Boosters.push({
304 uniqueName: item.typeName,
305 name: getString(item.name, lang)
306 });
307 }
308
299 309 response.json(res);
300 310 };
301 311
Added src/controllers/custom/setBoosterController.ts +45 -0
@@ -0,0 +1,45 @@
1 import { getAccountIdForRequest } from "@/src/services/loginService";
2 import { getInventory } from "@/src/services/inventoryService";
3 import { RequestHandler } from "express";
4 import { ExportBoosters } from "warframe-public-export-plus";
5
6 const I32_MAX = 0x7fffffff;
7
8 export const setBoosterController: RequestHandler = async (req, res) => {
9 const accountId = await getAccountIdForRequest(req);
10 const requests = req.body as { ItemType: string; ExpiryDate: number }[];
11 const inventory = await getInventory(accountId, "Boosters");
12 const boosters = inventory.Boosters;
13 if (
14 requests.some(request => {
15 if (typeof request.ItemType !== "string") return true;
16 if (Object.entries(ExportBoosters).find(([_, item]) => item.typeName === request.ItemType) === undefined)
17 return true;
18 if (typeof request.ExpiryDate !== "number") return true;
19 if (request.ExpiryDate < 0 || request.ExpiryDate > I32_MAX) return true;
20 return false;
21 })
22 ) {
23 res.status(400).send("Invalid ItemType provided.");
24 return;
25 }
26 const now = Math.floor(Date.now() / 1000);
27 for (const { ItemType, ExpiryDate } of requests) {
28 if (ExpiryDate < now) {
29 // remove expired boosters
30 const index = boosters.findIndex(item => item.ItemType === ItemType);
31 if (index !== -1) {
32 boosters.splice(index, 1);
33 }
34 } else {
35 const boosterItem = boosters.find(item => item.ItemType === ItemType);
36 if (boosterItem) {
37 boosterItem.ExpiryDate = ExpiryDate;
38 } else {
39 boosters.push({ ItemType, ExpiryDate });
40 }
41 }
42 }
43 await inventory.save();
44 res.end();
45 };
Modified src/routes/custom.ts +2 -0
@@ -23,6 +23,7 @@ import { setEvolutionProgressController } from "@/src/controllers/custom/setEvol
23 23
24 24 import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController";
25 25 import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController";
26 import { setBoosterController } from "../controllers/custom/setBoosterController";
26 27
27 28 const customRouter = express.Router();
28 29
@@ -46,6 +47,7 @@ customRouter.post("/addXp", addXpController);
46 47 customRouter.post("/import", importController);
47 48 customRouter.post("/manageQuests", manageQuestsController);
48 49 customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
50 customRouter.post("/setBooster", setBoosterController);
49 51
50 52 customRouter.get("/config", getConfigDataController);
51 53 customRouter.post("/config", updateConfigDataController);
Modified static/webui/index.html +15 -0
@@ -416,6 +416,20 @@
416 416 </div>
417 417 </div>
418 418 </div>
419 <div class="col-lg-6">
420 <div class="card mb-3" style="height: 400px;">
421 <h5 class="card-header" data-loc="inventory_Boosters"></h5>
422 <div class="card-body overflow-auto">
423 <form class="input-group mb-3" onsubmit="doAcquireBoosters();return false;">
424 <input class="form-control" id="acquire-type-Boosters" list="datalist-Boosters" />
425 <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
426 </form>
427 <table class="table table-hover w-100">
428 <tbody id="Boosters-list"></tbody>
429 </table>
430 </div>
431 </div>
432 </div>
419 433 </div>
420 434 <div class="card mb-3">
421 435 <h5 class="card-header" data-loc="general_bulkActions"></h5>
@@ -804,6 +818,7 @@
804 818 <datalist id="datalist-ModularParts-CATBROW_MUTAGEN"></datalist>
805 819 <datalist id="datalist-ModularParts-KUBROW_ANTIGEN"></datalist>
806 820 <datalist id="datalist-ModularParts-KUBROW_MUTAGEN"></datalist>
821 <datalist id="datalist-Boosters"></datalist>
807 822 <script src="/webui/libs/jquery-3.6.0.min.js"></script>
808 823 <script src="/webui/libs/whirlpool-js.min.js"></script>
809 824 <script src="/webui/libs/single.js"></script>
Modified static/webui/script.js +131 -0
@@ -1011,6 +1011,63 @@ function updateInventory() {
1011 1011 }
1012 1012 }
1013 1013 document.getElementById("changeSyndicate").value = data.SupportedSyndicate ?? "";
1014
1015 document.getElementById("Boosters-list").innerHTML = "";
1016 const now = Math.floor(Date.now() / 1000);
1017 data.Boosters.forEach(({ ItemType, ExpiryDate }) => {
1018 if (ExpiryDate < now) {
1019 // Booster has expired, skip it
1020 return;
1021 }
1022 const tr = document.createElement("tr");
1023 {
1024 const td = document.createElement("td");
1025 td.textContent = itemMap[ItemType]?.name ?? ItemType;
1026 tr.appendChild(td);
1027 }
1028 {
1029 const td = document.createElement("td");
1030 td.classList = "text-end text-nowrap";
1031 const timeString = formatDatetime("%Y-%m-%d %H:%M:%s", ExpiryDate * 1000);
1032 const inlineForm = document.createElement("form");
1033 const input = document.createElement("input");
1034
1035 inlineForm.style.display = "inline-block";
1036 inlineForm.onsubmit = function (event) {
1037 event.preventDefault();
1038 doChangeBoosterExpiry(ItemType, input);
1039 };
1040 input.type = "datetime-local";
1041 input.classList.add("form-control");
1042 input.classList.add("form-control-sm");
1043 input.value = timeString;
1044 let changed = false;
1045 input.onchange = function () {
1046 changed = true;
1047 };
1048 input.onblur = function () {
1049 if (changed) {
1050 doChangeBoosterExpiry(ItemType, input);
1051 }
1052 };
1053 inlineForm.appendChild(input);
1054
1055 td.appendChild(inlineForm);
1056
1057 const removeButton = document.createElement("a");
1058 removeButton.title = loc("code_remove");
1059 removeButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
1060 removeButton.href = "#";
1061 removeButton.onclick = function (event) {
1062 event.preventDefault();
1063 setBooster(ItemType, 0);
1064 };
1065 td.appendChild(removeButton);
1066
1067 tr.appendChild(td);
1068 }
1069 document.getElementById("Boosters-list").appendChild(tr);
1070 });
1014 1071 });
1015 1072 });
1016 1073 }
@@ -2027,3 +2084,77 @@ function handleModularSelection(category) {
2027 2084 });
2028 2085 });
2029 2086 }
2087
2088 function setBooster(ItemType, ExpiryDate, callback) {
2089 revalidateAuthz(() => {
2090 $.post({
2091 url: "/custom/setBooster?" + window.authz,
2092 contentType: "application/json",
2093 data: JSON.stringify([
2094 {
2095 ItemType,
2096 ExpiryDate
2097 }
2098 ])
2099 }).done(function () {
2100 updateInventory();
2101 if (callback) callback();
2102 });
2103 });
2104 }
2105
2106 function doAcquireBoosters() {
2107 const uniqueName = getKey(document.getElementById("acquire-type-Boosters"));
2108 if (!uniqueName) {
2109 $("#acquire-type-Boosters").addClass("is-invalid").focus();
2110 return;
2111 }
2112 const ExpiryDate = Date.now() / 1000 + 3 * 24 * 60 * 60; // default 3 days
2113 setBooster(uniqueName, ExpiryDate, () => {
2114 $("#acquire-type-Boosters").val("");
2115 updateInventory();
2116 });
2117 }
2118
2119 function doChangeBoosterExpiry(ItemType, ExpiryDateInput) {
2120 console.log("Changing booster expiry for", ItemType, "to", ExpiryDateInput.value);
2121 // cast local datetime string to unix timestamp
2122 const ExpiryDate = new Date(ExpiryDateInput.value).getTime() / 1000;
2123 if (isNaN(ExpiryDate)) {
2124 ExpiryDateInput.addClass("is-invalid").focus();
2125 return false;
2126 }
2127 setBooster(ItemType, ExpiryDate);
2128 return true;
2129 }
2130
2131 function formatDatetime(fmt, date) {
2132 if (typeof date === "number") date = new Date(date);
2133 return fmt.replace(/(%[yY]|%m|%[Dd]|%H|%h|%M|%[Ss]|%[Pp])/g, match => {
2134 switch (match) {
2135 case "%Y":
2136 return date.getFullYear().toString();
2137 case "%y":
2138 return date.getFullYear().toString().slice(-2);
2139 case "%m":
2140 return (date.getMonth() + 1).toString().padStart(2, "0");
2141 case "%D":
2142 case "%d":
2143 return date.getDate().toString().padStart(2, "0");
2144 case "%H":
2145 return date.getHours().toString().padStart(2, "0");
2146 case "%h":
2147 return (date.getHours() % 12).toString().padStart(2, "0");
2148 case "%M":
2149 return date.getMinutes().toString().padStart(2, "0");
2150 case "%S":
2151 case "%s":
2152 return date.getSeconds().toString().padStart(2, "0");
2153 case "%P":
2154 case "%p":
2155 return date.getHours() < 12 ? "am" : "pm";
2156 default:
2157 return match;
2158 }
2159 });
2160 }
Modified static/webui/translations/de.js +1 -0
@@ -98,6 +98,7 @@ dict = {
98 98 inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`,
99 99 inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`,
100 100 inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`,
101 inventory_Boosters: `[UNTRANSLATED] Boosters`,
101 102
102 103 quests_list: `Quests`,
103 104 quests_completeAll: `Alle Quests abschließen`,
Modified static/webui/translations/en.js +1 -0
@@ -97,6 +97,7 @@ dict = {
97 97 inventory_bulkRankUpSentinels: `Max Rank All Sentinels`,
98 98 inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`,
99 99 inventory_bulkRankUpEvolutionProgress: `Max Rank All Incarnon Evolution Progress`,
100 inventory_Boosters: `Boosters`,
100 101
101 102 quests_list: `Quests`,
102 103 quests_completeAll: `Complete All Quests`,
Modified static/webui/translations/es.js +1 -0
@@ -98,6 +98,7 @@ dict = {
98 98 inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`,
99 99 inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`,
100 100 inventory_bulkRankUpEvolutionProgress: `Maximizar todo el progreso de evolución Incarnon`,
101 inventory_Boosters: `[UNTRANSLATED] Boosters`,
101 102
102 103 quests_list: `Misiones`,
103 104 quests_completeAll: `Completar todas las misiones`,
Modified static/webui/translations/fr.js +1 -0
@@ -98,6 +98,7 @@ dict = {
98 98 inventory_bulkRankUpSentinels: `Toutes les Sentinelles au rang max`,
99 99 inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles au rang max`,
100 100 inventory_bulkRankUpEvolutionProgress: `Toutes les évolutions Incarnon au rang max`,
101 inventory_Boosters: `[UNTRANSLATED] Boosters`,
101 102
102 103 quests_list: `Quêtes`,
103 104 quests_completeAll: `Compléter toutes les quêtes`,
Modified static/webui/translations/ru.js +1 -0
@@ -98,6 +98,7 @@ dict = {
98 98 inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`,
99 99 inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`,
100 100 inventory_bulkRankUpEvolutionProgress: `Максимальный ранг всех эволюций Инкарнонов`,
101 inventory_Boosters: `[UNTRANSLATED] Boosters`,
101 102
102 103 quests_list: `Квесты`,
103 104 quests_completeAll: `Завершить все квесты`,
Modified static/webui/translations/zh.js +1 -0
@@ -98,6 +98,7 @@ dict = {
98 98 inventory_bulkRankUpSentinels: `所有守护升满级`,
99 99 inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`,
100 100 inventory_bulkRankUpEvolutionProgress: `所有灵化之源最大等级`,
101 inventory_Boosters: `加成器`,
101 102
102 103 quests_list: `任务`,
103 104 quests_completeAll: `完成所有任务`,