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
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # poi-plugin-kai-planner
2
+
3
+ 用于 POI 的改修规划插件(每日改修 + 改修心愿清单)。
4
+
5
+ ## 当前发布机制(2026-02)
6
+
7
+ - 插件代码仓库:私有(本仓库)。
8
+ - 玩家安装方式:通过 npm 安装 poi 插件包。
9
+ - 远端静态数据:独立公开仓库 `PlannerRemoteRawData`(GitHub Raw)。
10
+ - 调试页开关:`package.json -> poiPlugin.enableDebug`(发布建议 `false`)。
11
+ - 发布包控制:
12
+ - `package.json.files` 白名单
13
+ - `.npmignore` 保险排除
14
+ - `npm run release:check` 发版前检查
15
+
16
+ ## 功能概览
17
+
18
+ - 每日改修(GMT+9)
19
+ - 改修心愿清单(计划创建、实时缺口、进度跟踪、起点重绑)
20
+ - 支持行内编辑计划优先级与目标星级(点击“编辑”进入,点击“保存”生效)
21
+ - 目标星级编辑限制:当起点装备实时状态与目标装备同 `sortno` 时,不可低于起点当前星级
22
+ - 展开区步骤展示按实时状态区分“已完成步骤 / 未完成步骤”
23
+ - Debug(可选加载,默认可关闭)
24
+
25
+ ## 本地数据
26
+
27
+ - 计划存储优先写入:
28
+ - `<plugin_root>/runtime_data/userPlans/plans.v1.json`
29
+ - 文件存储不可用时回退 `localStorage`(`kai_planner_user_plans_v1`)。
30
+ - 远端静态数据缓存:
31
+ - `runtime_data/static_data/meta.json`
32
+ - `runtime_data/static_data/cache/*.json`
33
+
34
+ ## 常用命令
35
+
36
+ - `npm test`
37
+ - `npm run release:check`
38
+ - `npm run release:pack`
39
+
40
+ ## 文档导航
41
+
42
+ - 架构说明:[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
43
+ - 数据版本策略:[docs/DATA_VERSIONING.md](docs/DATA_VERSIONING.md)
44
+ - 计划存储策略:[docs/STORAGE_POLICY.md](docs/STORAGE_POLICY.md)
45
+ - Wishlist 状态字典:[docs/WISHLIST_STATE_FIELDS.md](docs/WISHLIST_STATE_FIELDS.md)
46
+ - npm/POI 发布流程:[docs/RELEASE_POI_NPM.md](docs/RELEASE_POI_NPM.md)
47
+ - 私有仓库更新流程:[docs/PRIVATE_REPO_UPDATE.md](docs/PRIVATE_REPO_UPDATE.md)
48
+ - 远端静态数据更新流程:[docs/REMOTE_DATA_UPDATE.md](docs/REMOTE_DATA_UPDATE.md)
package/index.js ADDED
@@ -0,0 +1,35 @@
1
+ /* poi-plugin-kai-planner/index.js */
2
+ /* Entry: render src/app/KaiPlannerApp.js */
3
+
4
+ const React = require("react");
5
+ const { WindowEnv } = require("views/components/etc/window-env");
6
+
7
+ const { KaiPlannerApp } = require("./src/app/KaiPlannerApp");
8
+
9
+ function KaiPlannerAppWithEnv() {
10
+ return React.createElement(WindowEnv.Consumer, null, (ctx) => {
11
+ const w =
12
+ (ctx && ctx.window) ||
13
+ (ctx && ctx.env && ctx.env.window) ||
14
+ (ctx && ctx.remoteWindow) ||
15
+ null;
16
+
17
+ return React.createElement(KaiPlannerApp, { envWindow: w, envCtx: ctx });
18
+ });
19
+ }
20
+
21
+ module.exports = {
22
+ reactClass: KaiPlannerAppWithEnv,
23
+ windowMode: true,
24
+
25
+ pluginDidLoad() {
26
+ if (typeof window !== "undefined" && window.log) {
27
+ window.log("[kai-planner] loaded");
28
+ }
29
+ },
30
+ pluginWillUnload() {
31
+ if (typeof window !== "undefined" && window.log) {
32
+ window.log("[kai-planner] unloaded");
33
+ }
34
+ },
35
+ };
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "poi-plugin-kai-planner",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "private": false,
6
+ "files": [
7
+ "index.js",
8
+ "src/**"
9
+ ],
10
+ "scripts": {
11
+ "test": "node --test tests",
12
+ "release:check": "npm test && npm pack --dry-run",
13
+ "release:pack": "npm pack"
14
+ },
15
+ "poiPlugin": {
16
+ "title": "改修规划",
17
+ "description": "管理个人装备与改修计划。",
18
+ "icon": "tasks",
19
+ "enableDebug": false,
20
+ "dataSource": {
21
+ "manifestRawUrl": "https://raw.githubusercontent.com/auluu/PlannerRemoteRawData/refs/heads/main/data_manifest.json",
22
+ "rawBaseUrl": "https://raw.githubusercontent.com/auluu/PlannerRemoteRawData/refs/heads/main"
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,211 @@
1
+ // src/app/KaiPlannerApp.js
2
+ const React = require("react");
3
+ const { DailyTab } = require("./tabs/daily/DailyTab");
4
+ const { checkAndUpdateData } = require("../services/static/version/dataUpdateManager");
5
+ const { readMeta } = require("../services/static/version/versionStore");
6
+
7
+ const placeholderStyle = {
8
+ padding: 12,
9
+ background: "#111827",
10
+ color: "#e5e7eb",
11
+ borderRadius: 8,
12
+ };
13
+
14
+ function pad2(n) {
15
+ return String(n).padStart(2, "0");
16
+ }
17
+
18
+ function formatSimpleTime(iso) {
19
+ if (!iso) return "-";
20
+ const d = new Date(iso);
21
+ if (!Number.isFinite(d.getTime())) return "-";
22
+ return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(
23
+ d.getMinutes()
24
+ )}:${pad2(d.getSeconds())}`;
25
+ }
26
+
27
+ function readBundledManifestUpdatedAt() {
28
+ try {
29
+ // eslint-disable-next-line global-require
30
+ const m = require("../data/static/data_manifest.json");
31
+ if (m && m.updated_at) return String(m.updated_at);
32
+ } catch {}
33
+ return null;
34
+ }
35
+
36
+ function readEnableDebugFlag() {
37
+ try {
38
+ // eslint-disable-next-line global-require
39
+ const pkg = require("../../package.json");
40
+ const poiPlugin = pkg && pkg.poiPlugin ? pkg.poiPlugin : {};
41
+ if (poiPlugin.enableDebug == null) return true;
42
+ return !!poiPlugin.enableDebug;
43
+ } catch {
44
+ return true;
45
+ }
46
+ }
47
+
48
+ class KaiPlannerApp extends React.Component {
49
+ constructor(props) {
50
+ super(props);
51
+ this.state = {
52
+ tab: "daily", // daily | wishlist | debug
53
+ wishlistLoadError: null,
54
+ debugLoadError: null,
55
+ checkingUpdate: false,
56
+ lastUpdateAtText: "-",
57
+ updateTip: "",
58
+ };
59
+ this._WishlistTab = null;
60
+ this._DebugTab = null;
61
+ this.enableDebug = readEnableDebugFlag();
62
+ this.handleCheckUpdate = this.handleCheckUpdate.bind(this);
63
+ }
64
+
65
+ componentDidMount() {
66
+ this.syncUpdateMeta();
67
+ this.handleCheckUpdate(false);
68
+ }
69
+
70
+ syncUpdateMeta() {
71
+ try {
72
+ const meta = readMeta();
73
+ const fallback = readBundledManifestUpdatedAt();
74
+ this.setState({
75
+ lastUpdateAtText: formatSimpleTime((meta && meta.lastUpdateAt) || fallback),
76
+ });
77
+ } catch {}
78
+ }
79
+
80
+ async handleCheckUpdate(force) {
81
+ if (this.state.checkingUpdate) return;
82
+ this.setState({ checkingUpdate: true, updateTip: force ? "正在检查..." : "" });
83
+ try {
84
+ const res = await checkAndUpdateData({ force: !!force });
85
+ this.syncUpdateMeta();
86
+ if (force) {
87
+ if (res && res.ok && res.updated) this.setState({ updateTip: `已更新到数据版本 ${res.remoteVersion}` });
88
+ else if (res && res.ok && !res.updated) this.setState({ updateTip: "当前已是最新数据" });
89
+ else if (res && res.reason === "data_source_not_configured") this.setState({ updateTip: "未配置远端数据源" });
90
+ else this.setState({ updateTip: `检查失败${res && res.error ? `:${res.error}` : ""}` });
91
+ }
92
+ } catch (e) {
93
+ if (force) this.setState({ updateTip: `检查失败:${String(e && (e.stack || e))}` });
94
+ } finally {
95
+ this.setState({ checkingUpdate: false });
96
+ }
97
+ }
98
+
99
+ render() {
100
+ const tab = !this.enableDebug && this.state.tab === "debug" ? "daily" : this.state.tab;
101
+
102
+ const tabButtonStyle = (key) => ({
103
+ padding: "6px 10px",
104
+ borderRadius: 8,
105
+ cursor: "pointer",
106
+ border: "1px solid #374151",
107
+ background: tab === key ? "#1f2937" : "transparent",
108
+ color: "#e5e7eb",
109
+ marginRight: 8,
110
+ fontSize: 12,
111
+ });
112
+
113
+ let content = null;
114
+ if (tab === "daily") {
115
+ content = React.createElement(DailyTab, {
116
+ envWindow: this.props.envWindow,
117
+ envCtx: this.props.envCtx,
118
+ });
119
+ } else if (tab === "wishlist") {
120
+ if (!this._WishlistTab && !this.state.wishlistLoadError) {
121
+ try {
122
+ // eslint-disable-next-line global-require
123
+ this._WishlistTab = require("./tabs/wishlist/WishlistTab");
124
+ } catch (e) {
125
+ this.setState({ wishlistLoadError: String(e && e.stack ? e.stack : e) });
126
+ }
127
+ }
128
+
129
+ if (this.state.wishlistLoadError) {
130
+ content = React.createElement(
131
+ "div",
132
+ { style: { ...placeholderStyle, whiteSpace: "pre-wrap" } },
133
+ "【改修心愿清单】模块加载失败(已隔离,不影响 Daily/Debug)\n\n错误信息:\n" + this.state.wishlistLoadError
134
+ );
135
+ } else if (this._WishlistTab) {
136
+ content = React.createElement(this._WishlistTab, {
137
+ envWindow: this.props.envWindow,
138
+ envCtx: this.props.envCtx,
139
+ });
140
+ } else {
141
+ content = React.createElement("div", { style: placeholderStyle }, "【改修心愿清单】加载中...");
142
+ }
143
+ } else if (tab === "debug") {
144
+ if (!this._DebugTab && !this.state.debugLoadError) {
145
+ try {
146
+ // eslint-disable-next-line global-require
147
+ this._DebugTab = require("./tabs/debug/DebugTab");
148
+ } catch (e) {
149
+ this.setState({ debugLoadError: String(e && e.stack ? e.stack : e) });
150
+ }
151
+ }
152
+
153
+ if (this.state.debugLoadError) {
154
+ content = React.createElement(
155
+ "div",
156
+ { style: { ...placeholderStyle, whiteSpace: "pre-wrap" } },
157
+ "【Debug】模块加载失败(已隔离,不影响 Daily/Wishlist)\n\n错误信息:\n" + this.state.debugLoadError
158
+ );
159
+ } else if (this._DebugTab) {
160
+ content = React.createElement(this._DebugTab, {
161
+ envWindow: this.props.envWindow,
162
+ envCtx: this.props.envCtx,
163
+ });
164
+ } else {
165
+ content = React.createElement("div", { style: placeholderStyle }, "【Debug】加载中...");
166
+ }
167
+ } else {
168
+ content = React.createElement("div", { style: placeholderStyle }, "未知标签页");
169
+ }
170
+
171
+ return React.createElement(
172
+ "div",
173
+ { style: { padding: 12 } },
174
+ React.createElement(
175
+ "div",
176
+ { style: { marginBottom: 12, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 } },
177
+ React.createElement(
178
+ "div",
179
+ null,
180
+ React.createElement("button", { style: tabButtonStyle("daily"), onClick: () => this.setState({ tab: "daily" }) }, "每日改修"),
181
+ React.createElement(
182
+ "button",
183
+ { style: tabButtonStyle("wishlist"), onClick: () => this.setState({ tab: "wishlist" }) },
184
+ "改修心愿清单"
185
+ ),
186
+ this.enableDebug
187
+ ? React.createElement(
188
+ "button",
189
+ { style: tabButtonStyle("debug"), onClick: () => this.setState({ tab: "debug" }) },
190
+ "Debug"
191
+ )
192
+ : null
193
+ ),
194
+ React.createElement(
195
+ "div",
196
+ { style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", justifyContent: "flex-end" } },
197
+ React.createElement("span", { style: { fontSize: 12, opacity: 0.8 } }, `上一次数据源更新:${this.state.lastUpdateAtText}`),
198
+ React.createElement(
199
+ "button",
200
+ { style: tabButtonStyle("__check__"), onClick: () => this.handleCheckUpdate(true), disabled: this.state.checkingUpdate },
201
+ this.state.checkingUpdate ? "检查中..." : "检查更新"
202
+ ),
203
+ this.state.updateTip ? React.createElement("span", { style: { fontSize: 12, opacity: 0.85 } }, this.state.updateTip) : null
204
+ )
205
+ ),
206
+ content
207
+ );
208
+ }
209
+ }
210
+
211
+ module.exports = { KaiPlannerApp };