poi-plugin-kai-planner 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +48 -0
  2. package/index.js +35 -0
  3. package/package.json +25 -0
  4. package/src/app/KaiPlannerApp.js +211 -0
  5. package/src/app/tabs/daily/DailyTab.js +439 -0
  6. package/src/app/tabs/debug/DebugTab.js +554 -0
  7. package/src/app/tabs/wishlist/CreatePlanForm.js +185 -0
  8. package/src/app/tabs/wishlist/WishlistTab.js +704 -0
  9. package/src/app/tabs/wishlist/components/MouseComboBox.js +185 -0
  10. package/src/app/tabs/wishlist/components/WishlistExpandedDetail.js +170 -0
  11. package/src/app/tabs/wishlist/components/WishlistTable.js +253 -0
  12. package/src/core/poi/secretaryName.js +64 -0
  13. package/src/data/indexes/buildArrangementIndex.js +103 -0
  14. package/src/data/loaders/loadStaticData.js +182 -0
  15. package/src/data/static/data_manifest.json +15 -0
  16. package/src/data/static/equip_base_cost.json +5000 -0
  17. package/src/data/static/equipment_upgrade_path.json +3555 -0
  18. package/src/data/static/improvement_arrangement.json +15677 -0
  19. package/src/data/static/improvement_consume_item.json +8933 -0
  20. package/src/data/static/improvement_consume_step.json +9284 -0
  21. package/src/data/static/improvement_upgrade_cost.json +4766 -0
  22. package/src/data/static/improvement_upgrade_target.json +2641 -0
  23. package/src/data/static/material.json +90 -0
  24. package/src/services/common/secretaryDisplay.js +42 -0
  25. package/src/services/daily/buildDailyViewModel.js +402 -0
  26. package/src/services/planner/buildUpgradePath.js +79 -0
  27. package/src/services/planner/calcImproveSteps.js +104 -0
  28. package/src/services/planner/calcRemainingPlan.js +169 -0
  29. package/src/services/planner/calcRoutePlan.js +85 -0
  30. package/src/services/planner/calcUpgradeStep.js +85 -0
  31. package/src/services/planner/detectCurrentPosition.js +57 -0
  32. package/src/services/planner/summarizeShortage.js +76 -0
  33. package/src/services/player/countPlayerEquipByMasterId.js +27 -0
  34. package/src/services/player/getEquipOwnerShip.js +66 -0
  35. package/src/services/player/getPlayerData.js +14 -0
  36. package/src/services/player/getPlayerItemCountByUseitemId.js +45 -0
  37. package/src/services/player/getReduxStateFromEnvWindow.js +12 -0
  38. package/src/services/player/resolveMaterialKeyToUseitemId.js +54 -0
  39. package/src/services/static/indexes/buildConsumeIndexes.js +28 -0
  40. package/src/services/static/indexes/buildPathIndexes.js +29 -0
  41. package/src/services/static/indexes/buildUpgradeIndexes.js +57 -0
  42. package/src/services/static/version/dataSourceConfig.js +25 -0
  43. package/src/services/static/version/dataUpdateManager.js +176 -0
  44. package/src/services/static/version/versionStore.js +205 -0
  45. package/src/services/utils/toInt.js +11 -0
  46. package/src/services/utils/tokyoTime.js +58 -0
  47. package/src/services/wishlist/buildWishlistViewModel.js +56 -0
  48. package/src/services/wishlist/dropdownInteraction.js +30 -0
  49. package/src/services/wishlist/wishlistActions.js +485 -0
  50. package/src/storage/userPlans/fileStore.js +106 -0
  51. package/src/storage/userPlans/localStorageStore.js +50 -0
  52. package/src/storage/userPlans/migrate.js +60 -0
  53. package/src/storage/userPlans/planStore.js +107 -0
  54. package/src/storage/userPlans/storeAdapter.js +77 -0
@@ -0,0 +1,439 @@
1
+ /* src/app/tabs/daily/DailyTab.js */
2
+
3
+ const React = require("react");
4
+
5
+ const { loadStaticData } = require("../../../data/loaders/loadStaticData");
6
+ const { buildArrangementIndex } = require("../../../data/indexes/buildArrangementIndex");
7
+ const { buildDailyViewModel } = require("../../../services/daily/buildDailyViewModel");
8
+ const { getReduxStateFromEnvWindow } = require("../../../services/player/getReduxStateFromEnvWindow");
9
+ const {
10
+ TOKYO_WEEK_KEYS,
11
+ TOKYO_WEEK_LABEL_ZH,
12
+ formatTokyoDateTimeWithWeekday,
13
+ getTokyoWeekdayKey,
14
+ } = require("../../../services/utils/tokyoTime");
15
+
16
+ function buildShipIndexes(masterShipsRaw) {
17
+ const shipBySortno = {};
18
+ const shipByApiId = {};
19
+ for (const k of Object.keys(masterShipsRaw || {})) {
20
+ const s = masterShipsRaw[k];
21
+ if (!s) continue;
22
+ if (s.api_sortno != null) shipBySortno[String(s.api_sortno)] = s;
23
+ if (s.api_id != null) shipByApiId[String(s.api_id)] = s;
24
+ }
25
+ return { shipBySortno, shipByApiId };
26
+ }
27
+
28
+ function SectionTitle({ children }) {
29
+ return React.createElement("div", { style: { fontWeight: 800, margin: "10px 0 6px" } }, children);
30
+ }
31
+
32
+ function ItemList({ title, items }) {
33
+ if (!items || items.length === 0) return null;
34
+ return React.createElement(
35
+ "div",
36
+ { style: { marginTop: 6 } },
37
+ React.createElement("div", { style: { opacity: 0.85, marginBottom: 4 } }, title),
38
+ React.createElement(
39
+ "ul",
40
+ { style: { margin: 0, paddingLeft: 18 } },
41
+ items.map((it, idx) =>
42
+ React.createElement("li", { key: idx }, `${it.name} × ${it.count}`)
43
+ )
44
+ )
45
+ );
46
+ }
47
+
48
+ function PhaseBlock({ phase }) {
49
+ const label = phase.phase === 0 ? "阶段0(0→1…5→6)" : "阶段1(6→7…9→MAX)";
50
+ const dev = phase.dev || { min: 0, max: 0 };
51
+ const screw = phase.screw || { min: 0, max: 0 };
52
+
53
+ const extras = (phase.items && phase.items.extras) ? phase.items.extras : [];
54
+
55
+ return React.createElement(
56
+ "div",
57
+ { style: { border: "1px solid rgba(255,255,255,0.12)", borderRadius: 8, padding: 10, marginTop: 10 } },
58
+ React.createElement("div", { style: { display: "flex", justifyContent: "space-between", gap: 12, flexWrap: "wrap" } },
59
+ React.createElement("div", { style: { fontWeight: 800 } }, label),
60
+ React.createElement("div", { style: { opacity: 0.9 } },
61
+ `资材(开发): ${dev.min}/${dev.max},螺丝: ${screw.min}/${screw.max}`
62
+ )
63
+ ),
64
+ ItemList({ title: "通用消耗(每次)", items: phase.items ? phase.items.common : [] }),
65
+ extras.length > 0
66
+ ? React.createElement(
67
+ "div",
68
+ { style: { marginTop: 6 } },
69
+ React.createElement("div", { style: { opacity: 0.85, marginBottom: 4 } }, "独立消耗(特定星级区间,叠加在通用消耗上)"),
70
+ extras.map((ex, i) =>
71
+ React.createElement(
72
+ "div",
73
+ { key: i, style: { marginTop: 6, padding: "8px 10px", background: "rgba(255,255,255,0.05)", borderRadius: 8 } },
74
+ React.createElement("div", { style: { fontWeight: 700, marginBottom: 4 } }, `${ex.star_from}→${ex.star_to}`),
75
+ React.createElement(
76
+ "ul",
77
+ { style: { margin: 0, paddingLeft: 18 } },
78
+ (ex.items || []).map((it, idx) =>
79
+ React.createElement("li", { key: idx }, `${it.name} × ${it.count}`)
80
+ )
81
+ )
82
+ )
83
+ )
84
+ )
85
+ : null
86
+ );
87
+ }
88
+
89
+ function UpgradeBlock({ upgrades, upgradeMeta }) {
90
+ if (!upgradeMeta || upgradeMeta.hasAnyUpgradeConfig === false) {
91
+ return React.createElement(
92
+ "div",
93
+ { style: { marginTop: 10, opacity: 0.8 } },
94
+ "该装备【任意日子都不可进化】(没有进化配置)"
95
+ );
96
+ }
97
+
98
+ if (!upgrades || upgrades.length === 0) {
99
+ return React.createElement(
100
+ "div",
101
+ { style: { marginTop: 10, opacity: 0.8 } },
102
+ "该装备【今天不可进化】(今日可用秘书舰不符合进化要求)"
103
+ );
104
+ }
105
+
106
+ return React.createElement(
107
+ "div",
108
+ null,
109
+ upgrades.map((u, idx) =>
110
+ React.createElement(
111
+ "div",
112
+ { key: idx, style: { border: "1px solid rgba(255,255,255,0.12)", borderRadius: 8, padding: 10, marginTop: 10 } },
113
+ React.createElement("div", { style: { display: "flex", justifyContent: "space-between", gap: 12, flexWrap: "wrap" } },
114
+ React.createElement("div", { style: { fontWeight: 900 } }, `进化:→ ${u.upgradeName}(${u.upgradeId})`),
115
+ React.createElement("div", { style: { opacity: 0.9 } }, `资材(开发): ${u.dev.min}/${u.dev.max},螺丝: ${u.screw.min}/${u.screw.max}`)
116
+ ),
117
+ React.createElement("div", { style: { marginTop: 6, opacity: 0.9 } }, `指定秘书舰(今日可用):${u.secretaryText || "不可"}`),
118
+ ItemList({ title: "进化消耗", items: u.items || [] })
119
+ )
120
+ )
121
+ );
122
+ }
123
+
124
+ function upgradeStatusText(upgradeMeta) {
125
+ if (!upgradeMeta || upgradeMeta.hasAnyUpgradeConfig === false) return "不可进化";
126
+ if (upgradeMeta.canUpgradeToday) return "今日可进化";
127
+ return "今日不可进化";
128
+ }
129
+
130
+ class DailyTab extends React.Component {
131
+ constructor(props) {
132
+ super(props);
133
+ this.state = {
134
+ tokyoText: formatTokyoDateTimeWithWeekday(),
135
+ weekdayKey: getTokyoWeekdayKey(),
136
+ expanded: {}, // equipId -> bool
137
+ vm: null,
138
+ error: null,
139
+ search: "",
140
+ };
141
+
142
+ this.refresh = this.refresh.bind(this);
143
+ this.setWeekday = this.setWeekday.bind(this);
144
+ this.toggleRow = this.toggleRow.bind(this);
145
+ }
146
+
147
+ componentDidMount() {
148
+ this.refresh();
149
+ this._timer = setInterval(() => {
150
+ this.setState({ tokyoText: formatTokyoDateTimeWithWeekday() });
151
+ }, 1000);
152
+ }
153
+
154
+ componentWillUnmount() {
155
+ if (this._timer) clearInterval(this._timer);
156
+ }
157
+
158
+ setWeekday(weekdayKey) {
159
+ this.setState({ weekdayKey }, this.refresh);
160
+ }
161
+
162
+ toggleRow(equipId) {
163
+ const k = String(equipId);
164
+ const next = { ...(this.state.expanded || {}) };
165
+ next[k] = !next[k];
166
+ this.setState({ expanded: next });
167
+ }
168
+
169
+ refresh() {
170
+ try {
171
+ const staticData = loadStaticData();
172
+ const { index: arrangementIndex } = buildArrangementIndex(staticData.improvementArrangement);
173
+
174
+ const state = getReduxStateFromEnvWindow(this.props.envWindow);
175
+ if (!state || !state.const) {
176
+ this.setState({
177
+ error: "Redux state unavailable. 请先进入母港,确保 /api_start2 已加载。",
178
+ vm: null,
179
+ });
180
+ return;
181
+ }
182
+
183
+ const masterEquipsById = state.const.$equips || {};
184
+ const masterShipsRaw = state.const.$ships || {};
185
+ const { shipBySortno, shipByApiId } = buildShipIndexes(masterShipsRaw);
186
+
187
+ const vm = buildDailyViewModel({
188
+ staticData,
189
+ arrangementIndex,
190
+ poi: { masterEquipsById, shipBySortno, shipByApiId },
191
+ weekdayKey: this.state.weekdayKey,
192
+ });
193
+
194
+ this.setState({ vm, error: null });
195
+ } catch (e) {
196
+ this.setState({ error: String(e && (e.stack || e)), vm: null });
197
+ }
198
+ }
199
+
200
+ render() {
201
+ const wrapperStyle = {
202
+ height: "calc(100vh - 140px)",
203
+ display: "flex",
204
+ flexDirection: "column",
205
+ color: "#e5e7eb",
206
+ fontFamily: "system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial",
207
+ fontSize: 12,
208
+ lineHeight: 1.4,
209
+ };
210
+
211
+ const stickyHeaderStyle = {
212
+ position: "sticky",
213
+ top: 0,
214
+ zIndex: 5,
215
+ padding: "12px 12px 10px",
216
+ background: "rgba(16,24,40,0.92)",
217
+ backdropFilter: "blur(6px)",
218
+ borderBottom: "1px solid rgba(255,255,255,0.08)",
219
+ };
220
+
221
+ const headerRowStyle = { display: "flex", justifyContent: "space-between", gap: 12, alignItems: "center", flexWrap: "wrap" };
222
+ const tabBarStyle = { display: "flex", gap: 8, flexWrap: "wrap", marginTop: 10 };
223
+ const tabStyle = (active) => ({
224
+ padding: "6px 10px",
225
+ borderRadius: 10,
226
+ border: "1px solid rgba(255,255,255,0.12)",
227
+ background: active ? "rgba(255,255,255,0.12)" : "transparent",
228
+ cursor: "pointer",
229
+ userSelect: "none",
230
+ });
231
+
232
+ const toolbarStyle = { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" };
233
+
234
+ const inputStyle = {
235
+ width: 240,
236
+ padding: "6px 10px",
237
+ borderRadius: 8,
238
+ border: "1px solid rgba(255,255,255,0.12)",
239
+ background: "rgba(0,0,0,0.15)",
240
+ color: "#e5e7eb",
241
+ outline: "none",
242
+ };
243
+
244
+ const scrollAreaStyle = {
245
+ flex: 1,
246
+ overflowY: "auto",
247
+ padding: 12,
248
+ };
249
+
250
+ const tableStyle = {
251
+ width: "100%",
252
+ borderCollapse: "collapse",
253
+ borderSpacing: 0,
254
+ overflow: "hidden",
255
+ border: "1px solid rgba(255,255,255,0.12)",
256
+ borderRadius: 10,
257
+ background: "rgba(0,0,0,0.06)",
258
+ };
259
+
260
+ const thStyle = { textAlign: "left", padding: "10px 10px", borderBottom: "1px solid rgba(255,255,255,0.12)", opacity: 0.9 };
261
+ const tdStyle = { padding: "10px 10px", borderBottom: "1px solid rgba(255,255,255,0.08)", verticalAlign: "top" };
262
+ const smallMuted = { opacity: 0.75, fontSize: 11 };
263
+
264
+ const vm = this.state.vm;
265
+ const q = (this.state.search || "").trim().toLowerCase();
266
+ const rows = vm && vm.rows ? (q ? vm.rows.filter((r) => (r.equipName || "").toLowerCase().includes(q)) : vm.rows) : [];
267
+
268
+ return React.createElement(
269
+ "div",
270
+ { style: wrapperStyle },
271
+
272
+ React.createElement(
273
+ "div",
274
+ { style: stickyHeaderStyle },
275
+ React.createElement(
276
+ "div",
277
+ { style: headerRowStyle },
278
+ React.createElement("div", null,
279
+ React.createElement("div", { style: { fontSize: 16, fontWeight: 900 } }, "每日改修"),
280
+ React.createElement("div", { style: smallMuted }, `当前时间(GMT+9):${this.state.tokyoText}`)
281
+ ),
282
+ React.createElement("div", { style: toolbarStyle },
283
+ React.createElement("input", {
284
+ value: this.state.search,
285
+ placeholder: "搜索装备名称...",
286
+ onChange: (e) => this.setState({ search: e.target.value }),
287
+ style: inputStyle,
288
+ }),
289
+ React.createElement("button", { onClick: this.refresh }, "Refresh")
290
+ )
291
+ ),
292
+
293
+ React.createElement(
294
+ "div",
295
+ { style: tabBarStyle },
296
+ TOKYO_WEEK_KEYS.slice(1).map((k) =>
297
+ React.createElement("div", {
298
+ key: k,
299
+ style: tabStyle(this.state.weekdayKey === k),
300
+ onClick: () => this.setWeekday(k),
301
+ }, TOKYO_WEEK_LABEL_ZH[k])
302
+ ),
303
+ React.createElement("div", {
304
+ key: "sunday",
305
+ style: tabStyle(this.state.weekdayKey === "sunday"),
306
+ onClick: () => this.setWeekday("sunday"),
307
+ }, TOKYO_WEEK_LABEL_ZH["sunday"])
308
+ )
309
+ ),
310
+
311
+ React.createElement(
312
+ "div",
313
+ { style: scrollAreaStyle },
314
+
315
+ this.state.error
316
+ ? React.createElement("pre", {
317
+ style: {
318
+ marginTop: 0,
319
+ padding: 12,
320
+ background: "#0b1220",
321
+ border: "1px solid rgba(255,255,255,0.12)",
322
+ borderRadius: 10,
323
+ whiteSpace: "pre-wrap",
324
+ wordBreak: "break-word",
325
+ }
326
+ }, this.state.error)
327
+ : null,
328
+
329
+ !this.state.error && vm
330
+ ? React.createElement(
331
+ React.Fragment,
332
+ null,
333
+ React.createElement("div", { style: { margin: "0 0 10px", opacity: 0.8 } },
334
+ `今日可改修装备:${rows.length} 条` + (q ? `(搜索:${this.state.search})` : "")
335
+ ),
336
+
337
+ React.createElement(
338
+ "table",
339
+ { style: tableStyle },
340
+ React.createElement(
341
+ "thead",
342
+ null,
343
+ React.createElement(
344
+ "tr",
345
+ null,
346
+ React.createElement("th", { style: thStyle }, "装备名称"),
347
+ React.createElement("th", { style: thStyle }, "基础消耗(油/弹/钢/铝)"),
348
+ React.createElement("th", { style: thStyle }, `${TOKYO_WEEK_LABEL_ZH[this.state.weekdayKey]} - 秘书舰`),
349
+ React.createElement("th", { style: thStyle }, "可进化")
350
+ )
351
+ ),
352
+ React.createElement(
353
+ "tbody",
354
+ null,
355
+ rows.map((r) => {
356
+ const exp = !!this.state.expanded[String(r.equipId)];
357
+ const base = r.baseCost || {};
358
+ const baseText = `${base.fuel || 0}/${base.ammo || 0}/${base.steel || 0}/${base.bauxite || 0}` + (base.missing ? "(缺数据)" : "");
359
+ const icon = exp ? "▾" : "▸";
360
+
361
+ const onRowClick = () => this.toggleRow(r.equipId);
362
+
363
+ return React.createElement(
364
+ React.Fragment,
365
+ { key: r.equipId },
366
+
367
+ React.createElement(
368
+ "tr",
369
+ {
370
+ onClick: onRowClick,
371
+ style: { cursor: "pointer" },
372
+ title: "点击展开/收起",
373
+ },
374
+ React.createElement("td", { style: tdStyle },
375
+ React.createElement("div", { style: { display: "flex", gap: 8, alignItems: "center" } },
376
+ React.createElement(
377
+ "button",
378
+ {
379
+ onClick: (e) => { e.preventDefault(); e.stopPropagation(); onRowClick(); },
380
+ style: {
381
+ width: 28,
382
+ height: 28,
383
+ borderRadius: 8,
384
+ border: "1px solid rgba(255,255,255,0.12)",
385
+ background: "rgba(255,255,255,0.06)",
386
+ color: "#e5e7eb",
387
+ cursor: "pointer",
388
+ },
389
+ title: "展开/收起",
390
+ },
391
+ icon
392
+ ),
393
+ React.createElement("div", null,
394
+ React.createElement("div", { style: { fontWeight: 800 } }, r.equipName),
395
+ React.createElement("div", { style: smallMuted }, `equipId: ${r.equipId}`)
396
+ )
397
+ )
398
+ ),
399
+ React.createElement("td", { style: tdStyle }, baseText),
400
+ React.createElement("td", { style: tdStyle }, r.secretaryText || "不可"),
401
+ React.createElement("td", { style: tdStyle }, upgradeStatusText(r.upgradeMeta))
402
+ ),
403
+
404
+ exp
405
+ ? React.createElement(
406
+ "tr",
407
+ { key: `${r.equipId}_detail` },
408
+ React.createElement(
409
+ "td",
410
+ { style: { ...tdStyle, paddingTop: 0 }, colSpan: 4 },
411
+ React.createElement(SectionTitle, null, "改修消耗明细(每次)"),
412
+ React.createElement(PhaseBlock, { phase: r.phases[0] }),
413
+ React.createElement(PhaseBlock, { phase: r.phases[1] }),
414
+
415
+ r.upgradeMeta && r.upgradeMeta.hasAnyUpgradeConfig
416
+ ? React.createElement(
417
+ React.Fragment,
418
+ null,
419
+ React.createElement(SectionTitle, null, "进化消耗(今日可进化)"),
420
+ React.createElement(UpgradeBlock, { upgrades: r.upgrades || [], upgradeMeta: r.upgradeMeta })
421
+ )
422
+ : null
423
+ )
424
+ )
425
+ : null
426
+ );
427
+ })
428
+ )
429
+ )
430
+ )
431
+ : null
432
+ )
433
+ );
434
+ }
435
+ }
436
+
437
+ module.exports = {
438
+ DailyTab,
439
+ };