返回提交历史
Modified
src/controllers/custom/getItemListsController.ts
+11
-1
Added
src/controllers/custom/setBoosterController.ts
+45
-0
Modified
src/routes/custom.ts
+2
-0
Modified
static/webui/index.html
+15
-0
Modified
static/webui/script.js
+131
-0
Modified
static/webui/translations/de.js
+1
-0
Modified
static/webui/translations/en.js
+1
-0
Modified
static/webui/translations/es.js
+1
-0
Modified
static/webui/translations/fr.js
+1
-0
Modified
static/webui/translations/ru.js
+1
-0
Modified
static/webui/translations/zh.js
+1
-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
代码差异
11 个文件
+210
-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
@@ -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
};
@@ -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);
@@ -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>
@@ -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
}
@@ -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`,
@@ -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`,
@@ -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`,
@@ -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`,
@@ -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: `Завершить все квесты`,
@@ -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: `完成所有任务`,