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,554 @@
1
+ /* src/app/tabs/debug/DebugTab.js */
2
+ /* kai-planner debug: inspect redux store samples + full-search (top 50 hits) */
3
+
4
+ const React = require("react");
5
+ const { getReduxStateFromEnvWindow } = require("../../../services/player/getReduxStateFromEnvWindow");
6
+ const { getTokyoWeekdayKey } = require("../../../services/utils/tokyoTime");
7
+
8
+ // ---------- utils ----------
9
+ function safeStringify(obj, maxLen = 12000) {
10
+ let s;
11
+ try {
12
+ s = JSON.stringify(
13
+ obj,
14
+ (k, v) => {
15
+ if (Array.isArray(v) && v.length > 300) {
16
+ return { __array__: true, length: v.length, head: v.slice(0, 30) };
17
+ }
18
+ return v;
19
+ },
20
+ 2
21
+ );
22
+ } catch (e) {
23
+ s = String(obj);
24
+ }
25
+ if (s.length > maxLen) return s.slice(0, maxLen) + `\n...<truncated ${s.length - maxLen} chars>`;
26
+ return s;
27
+ }
28
+
29
+ function summarizeMap(mapObj, sampleN) {
30
+ const isObj = mapObj && typeof mapObj === "object" && !Array.isArray(mapObj);
31
+ const keys = isObj ? Object.keys(mapObj) : [];
32
+ const firstKeys = keys.slice(0, 50);
33
+ const samples = [];
34
+ for (let i = 0; i < Math.min(sampleN, keys.length); i++) {
35
+ const k = keys[i];
36
+ samples.push({ key: k, value: mapObj[k] });
37
+ }
38
+ return { keysCount: keys.length, firstKeys, samples, keys };
39
+ }
40
+
41
+ function pickCategory(state, cat) {
42
+ const constState = state?.const || {};
43
+ const infoState = state?.info || {};
44
+
45
+ switch (cat) {
46
+ case "master_ships":
47
+ return { title: "1) store.const.$ships (master ships)", data: constState.$ships };
48
+ case "master_equips":
49
+ return { title: "2) store.const.$equips (master equips)", data: constState.$equips };
50
+ case "player_ships":
51
+ return { title: "3) store.info.ships (player ships)", data: infoState.ships };
52
+ case "player_equips":
53
+ return { title: "4) store.info.equips (player equips)", data: infoState.equips };
54
+ case "master_useitems":
55
+ return { title: "5) store.const.$useitems (master useitems)", data: constState.$useitems };
56
+ case "player_items":
57
+ return {
58
+ title: "6) store.info.items (player items) [fallback: info.useitems]",
59
+ data: infoState.items || infoState.useitems || null,
60
+ };
61
+ case "raw_state":
62
+ return { title: "RAW) full redux state (danger)", data: state };
63
+ default:
64
+ return { title: "Unknown", data: null };
65
+ }
66
+ }
67
+
68
+ function findMatchesInMap(mapObj, query, mode, limit = 50) {
69
+ const isObj = mapObj && typeof mapObj === "object" && !Array.isArray(mapObj);
70
+ if (!isObj) return { matches: [], scanned: 0 };
71
+
72
+ const q = (query || "").trim().toLowerCase();
73
+ if (!q) return { matches: [], scanned: Object.keys(mapObj).length };
74
+
75
+ const keys = Object.keys(mapObj);
76
+ const matches = [];
77
+ let scanned = 0;
78
+
79
+ for (const k of keys) {
80
+ scanned++;
81
+ const keyStr = String(k).toLowerCase();
82
+ const keyHit = keyStr.includes(q);
83
+
84
+ let jsonHit = false;
85
+ if (mode !== "key") {
86
+ const jsonStr = safeStringify(mapObj[k], 50000).toLowerCase();
87
+ jsonHit = jsonStr.includes(q);
88
+ }
89
+
90
+ const ok = mode === "key" ? keyHit : mode === "json" ? jsonHit : keyHit || jsonHit;
91
+ if (ok) {
92
+ matches.push({ key: k, value: mapObj[k] });
93
+ if (matches.length >= limit) break;
94
+ }
95
+ }
96
+
97
+ return { matches, scanned };
98
+ }
99
+
100
+ // ---------- component ----------
101
+ class DebugTab extends React.Component {
102
+ constructor(props) {
103
+ super(props);
104
+ this.state = {
105
+ category: "master_equips",
106
+ sampleN: 2,
107
+ showRaw: false,
108
+
109
+ // manual search (applied only when clicking Refresh)
110
+ searchText: "",
111
+ searchMode: "both", // "key" | "json" | "both"
112
+
113
+ lastUpdated: null,
114
+ output: "",
115
+ meta: "",
116
+ copyStatus: "", // ✅ 新增
117
+ };
118
+ this.validateStaticData = this.validateStaticData.bind(this);
119
+ this.validateArrangementIndex = this.validateArrangementIndex.bind(this);
120
+ this.validateDailyViewModel = this.validateDailyViewModel.bind(this);
121
+ this.refresh = this.refresh.bind(this);
122
+ }
123
+
124
+ componentDidMount() {
125
+ this.refresh();
126
+ }
127
+
128
+ refresh() {
129
+ const w = this.props.envWindow;
130
+
131
+ const debug = {
132
+ hasEnvWindow: !!w,
133
+ ctxKeys: this.props.envCtx ? Object.keys(this.props.envCtx) : [],
134
+ hasGetStore: !!(w && typeof w.getStore === "function"),
135
+ hasStore: !!(w && w.store),
136
+ hasApp: !!(w && w.app),
137
+ hasWindowGetStore: typeof window !== "undefined" && typeof window.getStore === "function",
138
+ };
139
+
140
+ const state = getReduxStateFromEnvWindow(w);
141
+
142
+ if (!state) {
143
+ this.setState({
144
+ meta: "DEBUG:\n" + safeStringify(debug),
145
+ output: "STATE IS NULL (cannot read redux state).",
146
+ lastUpdated: new Date().toLocaleString(),
147
+ });
148
+ return;
149
+ }
150
+
151
+ debug.stateTopKeys = Object.keys(state || {});
152
+ debug.hasConst = !!state.const;
153
+ debug.hasInfo = !!state.info;
154
+ debug.counts = {
155
+ masterShips: state.const && state.const.$ships ? Object.keys(state.const.$ships).length : 0,
156
+ masterEquips: state.const && state.const.$equips ? Object.keys(state.const.$equips).length : 0,
157
+ masterUseitems: state.const && state.const.$useitems ? Object.keys(state.const.$useitems).length : 0,
158
+ playerShips: state.info && state.info.ships ? Object.keys(state.info.ships).length : 0,
159
+ playerEquips: state.info && state.info.equips ? Object.keys(state.info.equips).length : 0,
160
+ playerItems:
161
+ state.info && (state.info.items || state.info.useitems)
162
+ ? Object.keys(state.info.items || state.info.useitems).length
163
+ : 0,
164
+ };
165
+
166
+ const { title, data } = pickCategory(state, this.state.category);
167
+
168
+ let meta = `DEBUG:\n${safeStringify(debug)}\n\n`;
169
+ meta += `Category: ${title}\n`;
170
+ meta += `Type: ${Array.isArray(data) ? "array" : typeof data}\n`;
171
+
172
+ // raw / non-object -> dump
173
+ if (this.state.showRaw || !data || typeof data !== "object" || Array.isArray(data)) {
174
+ this.setState({
175
+ meta: meta + "(RAW MODE)\n",
176
+ output: safeStringify({ category: title, data }),
177
+ lastUpdated: new Date().toLocaleString(),
178
+ });
179
+ return;
180
+ }
181
+
182
+ // summary + samples
183
+ const sum = summarizeMap(data, Number(this.state.sampleN) || 2);
184
+ meta += `Keys: ${sum.keysCount}\n`;
185
+ meta += `FirstKeys(50): ${safeStringify(sum.firstKeys)}\n`;
186
+ meta += `Samples: ${sum.samples.length}\n`;
187
+
188
+ // manual full scan search
189
+ const q = (this.state.searchText || "").trim();
190
+ const mode = this.state.searchMode;
191
+
192
+ if (q) {
193
+ const { matches, scanned } = findMatchesInMap(data, q, mode, 50);
194
+ meta += `Search(manual): "${q}" mode=${mode} -> matched=${matches.length} (showing up to 50)\n`;
195
+
196
+ const out = {
197
+ category: title,
198
+ keysCount: sum.keysCount,
199
+ firstKeys: sum.firstKeys,
200
+ samples: sum.samples,
201
+ search: { q, mode, limit: 50, scannedKeys: scanned, matched: matches.length },
202
+ matches,
203
+ };
204
+
205
+ this.setState({
206
+ meta,
207
+ output: safeStringify(out),
208
+ lastUpdated: new Date().toLocaleString(),
209
+ });
210
+ return;
211
+ }
212
+
213
+ const out = {
214
+ category: title,
215
+ keysCount: sum.keysCount,
216
+ firstKeys: sum.firstKeys,
217
+ samples: sum.samples,
218
+ search: null,
219
+ matches: [],
220
+ };
221
+
222
+ this.setState({
223
+ meta,
224
+ output: safeStringify(out),
225
+ lastUpdated: new Date().toLocaleString(),
226
+ });
227
+ }
228
+
229
+ validateStaticData() {
230
+ try {
231
+ const { loadStaticData } = require("../../../data/loaders/loadStaticData");
232
+
233
+ const data = loadStaticData();
234
+
235
+ const summary = {
236
+ equipBaseCost_count: data.equipBaseCost.length,
237
+ improvementArrangement_count: data.improvementArrangement.length,
238
+ improvementConsumeStep_count: data.improvementConsumeStep.length,
239
+ improvementConsumeItem_count: data.improvementConsumeItem.length,
240
+ improvementUpgradeTarget_count: data.improvementUpgradeTarget.length,
241
+ improvementUpgradeCost_count: data.improvementUpgradeCost.length,
242
+ material_count: data.material.length,
243
+ equipmentUpgradePath_count: data.equipmentUpgradePath.length,
244
+
245
+ // 核心验证:equipment_path 是否已经是 number[]
246
+ equipmentUpgradePath_sample: data.equipmentUpgradePath.slice(0, 3).map(x => ({
247
+ from: x.from_equipment_id,
248
+ to: x.to_equipment_id,
249
+ equipment_path_raw: x.equipment_path_raw,
250
+ equipment_path_parsed: x.equipment_path,
251
+ parsed_type: Array.isArray(x.equipment_path)
252
+ ? typeof x.equipment_path[0]
253
+ : "NOT_ARRAY"
254
+ }))
255
+ };
256
+
257
+ this.setState({
258
+ meta: "STATIC DATA VALIDATION\n",
259
+ output: JSON.stringify(summary, null, 2),
260
+ lastUpdated: new Date().toLocaleString()
261
+ });
262
+
263
+ } catch (err) {
264
+ this.setState({
265
+ meta: "STATIC DATA VALIDATION ERROR\n",
266
+ output: String(err.stack || err),
267
+ lastUpdated: new Date().toLocaleString()
268
+ });
269
+ }
270
+ }
271
+
272
+ validateArrangementIndex() {
273
+ try {
274
+ const { loadStaticData } = require("../../../data/loaders/loadStaticData");
275
+ const { buildArrangementIndex, WEEKDAY_KEYS, NULL_ROUTE, queryArrangement } = require("../../../data/indexes/buildArrangementIndex");
276
+
277
+ const data = loadStaticData();
278
+ const { index, stats } = buildArrangementIndex(data.improvementArrangement);
279
+
280
+ // 抽样:取前 3 个 equipId,展示每个 weekday 的 routeKind 分布与条数
281
+ const equipIds = Object.keys(index).slice(0, 3).map((x) => Number(x));
282
+
283
+ const sample = equipIds.map((equipId) => {
284
+ const equipBucket = index[String(equipId)] || {};
285
+ const perDay = {};
286
+
287
+ for (const wk of WEEKDAY_KEYS) {
288
+ const wkBucket = equipBucket[wk] || {};
289
+ const routeKinds = Object.keys(wkBucket);
290
+ if (routeKinds.length === 0) continue;
291
+
292
+ perDay[wk] = routeKinds.map((rk) => ({
293
+ routeKind: rk === NULL_ROUTE ? null : rk,
294
+ count: (wkBucket[rk] || []).length,
295
+ first: (wkBucket[rk] && wkBucket[rk][0]) ? {
296
+ equipment_id: wkBucket[rk][0].equipment_id,
297
+ secretary_id: wkBucket[rk][0].secretary_id,
298
+ secretary_variant: wkBucket[rk][0].secretary_variant ?? null,
299
+ note: wkBucket[rk][0].note ?? null
300
+ } : null
301
+ }));
302
+ }
303
+
304
+ // 再做一个确定性查询 demo:用 monday + route_kind=null
305
+ const demoRows = queryArrangement(index, equipId, "monday", null).slice(0, 3);
306
+
307
+ return {
308
+ equipId,
309
+ weekdays: perDay,
310
+ demo_query: { equipId, weekday: "monday", route_kind: null, hit: demoRows.length, head3: demoRows }
311
+ };
312
+ });
313
+
314
+ const out = {
315
+ arrangement_index_validation: {
316
+ stats,
317
+ sample,
318
+ notes: [
319
+ "index[equipId][weekdayKey][routeKindKey] -> rows[]",
320
+ "route_kind null is stored under __NULL__ and only matches null/empty route_kind",
321
+ "rows keep original note/secretary_variant for later secretary display formatting"
322
+ ]
323
+ }
324
+ };
325
+
326
+ this.setState({
327
+ meta: "ARRANGEMENT INDEX VALIDATION\n",
328
+ output: JSON.stringify(out, null, 2),
329
+ lastUpdated: new Date().toLocaleString()
330
+ });
331
+ } catch (err) {
332
+ this.setState({
333
+ meta: "ARRANGEMENT INDEX VALIDATION ERROR\n",
334
+ output: String(err && (err.stack || err)),
335
+ lastUpdated: new Date().toLocaleString()
336
+ });
337
+ }
338
+ }
339
+
340
+ validateDailyViewModel() {
341
+ try {
342
+ const { loadStaticData } = require("../../../data/loaders/loadStaticData");
343
+ const { buildArrangementIndex } = require("../../../data/indexes/buildArrangementIndex");
344
+ const { buildDailyViewModel } = require("../../../services/daily/buildDailyViewModel");
345
+
346
+ // 1) static
347
+ const staticData = loadStaticData();
348
+
349
+ // 2) arrangement index
350
+ const { index: arrangementIndex, stats: arrangementStats } = buildArrangementIndex(staticData.improvementArrangement);
351
+
352
+ // 3) poi master equips + ships
353
+ // 你项目里已有 core/poi/buildPoiIndexes.js 的话优先用它;
354
+ // 如果还没接入,就从当前 redux state 里拿个兜底
355
+ const w = this.props.envWindow;
356
+ const state = getReduxStateFromEnvWindow(w);
357
+
358
+ const masterEquipsById = (state && state.const && state.const.$equips) ? state.const.$equips : {};
359
+ const masterShipsRaw = (state && state.const && state.const.$ships) ? state.const.$ships : {};
360
+
361
+ // build ship indexes needed by secretaryName()
362
+ const shipBySortno = {};
363
+ const shipByApiId = {};
364
+ for (const k of Object.keys(masterShipsRaw || {})) {
365
+ const s = masterShipsRaw[k];
366
+ if (!s) continue;
367
+ if (s.api_sortno != null) shipBySortno[String(s.api_sortno)] = s;
368
+ if (s.api_id != null) shipByApiId[String(s.api_id)] = s;
369
+ }
370
+
371
+ // 4) choose weekday by shared Tokyo helper
372
+ const weekdayKey = getTokyoWeekdayKey();
373
+
374
+ const vm = buildDailyViewModel({
375
+ staticData,
376
+ arrangementIndex,
377
+ poi: { masterEquipsById, shipBySortno, shipByApiId },
378
+ weekdayKey,
379
+ mergeRouteKinds: true
380
+ });
381
+
382
+ // 输出摘要 + 抽样前5行(看 secretaryText / baseCost / phases)
383
+ const sample = vm.rows.slice(0, 5).map((r) => ({
384
+ equipId: r.equipId,
385
+ equipName: r.equipName,
386
+ baseCost: r.baseCost,
387
+ secretaryText: r.secretaryText,
388
+ phase0: r.phases[0],
389
+ phase1: r.phases[1]
390
+ }));
391
+
392
+ const out = {
393
+ daily_view_model_validation: {
394
+ weekdayKey,
395
+ arrangementStats,
396
+ rowCount: vm.rows.length,
397
+ sample
398
+ }
399
+ };
400
+
401
+ this.setState({
402
+ meta: "DAILY VIEW MODEL VALIDATION\n",
403
+ output: JSON.stringify(out, null, 2),
404
+ lastUpdated: new Date().toLocaleString()
405
+ });
406
+ } catch (err) {
407
+ this.setState({
408
+ meta: "DAILY VIEW MODEL VALIDATION ERROR\n",
409
+ output: String(err && (err.stack || err)),
410
+ lastUpdated: new Date().toLocaleString()
411
+ });
412
+ }
413
+ }
414
+
415
+ render() {
416
+ const rowStyle = { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" };
417
+ const labelStyle = { opacity: 0.8 };
418
+
419
+ const preStyle = {
420
+ background: "#0b1220",
421
+ color: "#e5e7eb",
422
+ border: "1px solid rgba(255,255,255,0.12)",
423
+ padding: 12,
424
+ borderRadius: 8,
425
+ whiteSpace: "pre-wrap",
426
+ wordBreak: "break-word",
427
+ maxHeight: "70vh",
428
+ overflow: "auto",
429
+ fontFamily:
430
+ "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
431
+ };
432
+
433
+ return React.createElement(
434
+ "div",
435
+ null,
436
+ React.createElement(
437
+ "div",
438
+ { style: { ...rowStyle, marginBottom: 10 } },
439
+ React.createElement("span", { style: labelStyle }, "Category:"),
440
+ React.createElement(
441
+ "select",
442
+ {
443
+ value: this.state.category,
444
+ onChange: (e) => this.setState({ category: e.target.value }, this.refresh),
445
+ },
446
+ React.createElement("option", { value: "master_ships" }, "1) Master Ships ($ships)"),
447
+ React.createElement("option", { value: "master_equips" }, "2) Master Equips ($equips)"),
448
+ React.createElement("option", { value: "player_ships" }, "3) Player Ships (ships)"),
449
+ React.createElement("option", { value: "player_equips" }, "4) Player Equips (equips)"),
450
+ React.createElement("option", { value: "master_useitems" }, "5) Master Useitems ($useitems)"),
451
+ React.createElement("option", { value: "player_items" }, "6) Player Items (items/useitems)"),
452
+ React.createElement("option", { value: "raw_state" }, "RAW) Full redux state (danger)")
453
+ ),
454
+
455
+ React.createElement("span", { style: labelStyle }, "Sample N:"),
456
+ React.createElement("input", {
457
+ type: "number",
458
+ min: 1,
459
+ max: 10,
460
+ value: this.state.sampleN,
461
+ style: { width: 60 },
462
+ onChange: (e) => this.setState({ sampleN: e.target.value }),
463
+ }),
464
+
465
+ React.createElement("span", { style: labelStyle }, "Search:"),
466
+ React.createElement("input", {
467
+ type: "text",
468
+ value: this.state.searchText,
469
+ placeholder: "type then Refresh (full scan, top50)",
470
+ style: { width: 260 },
471
+ onChange: (e) => this.setState({ searchText: e.target.value }),
472
+ }),
473
+ React.createElement(
474
+ "select",
475
+ {
476
+ value: this.state.searchMode,
477
+ onChange: (e) => this.setState({ searchMode: e.target.value }),
478
+ },
479
+ React.createElement("option", { value: "both" }, "key+json"),
480
+ React.createElement("option", { value: "key" }, "key"),
481
+ React.createElement("option", { value: "json" }, "json")
482
+ ),
483
+
484
+ React.createElement(
485
+ "label",
486
+ { style: { display: "inline-flex", gap: 6, alignItems: "center" } },
487
+ React.createElement("input", {
488
+ type: "checkbox",
489
+ checked: this.state.showRaw,
490
+ onChange: (e) => this.setState({ showRaw: e.target.checked }, this.refresh),
491
+ }),
492
+ React.createElement("span", null, "Show raw object")
493
+ ),
494
+
495
+ React.createElement("button", { onClick: this.refresh }, "Refresh"),
496
+ React.createElement(
497
+ "button",
498
+ {
499
+ onClick: this.validateStaticData,
500
+ style: { marginLeft: 8 }
501
+ },
502
+ "Validate Static Data"
503
+ ),
504
+ React.createElement(
505
+ "button",
506
+ {
507
+ onClick: this.validateArrangementIndex,
508
+ style: { marginLeft: 8 }
509
+ },
510
+ "Validate Arrangement Index"
511
+ ),
512
+ React.createElement(
513
+ "button",
514
+ { onClick: this.validateDailyViewModel, style: { marginLeft: 8 } },
515
+ "Validate Daily ViewModel"
516
+ ),
517
+ React.createElement(
518
+ "button",
519
+ {
520
+ onClick: () => {
521
+ const text = (this.state.meta || "") + "\n" + (this.state.output || "");
522
+ try {
523
+ if (typeof window !== "undefined" && window.require) {
524
+ const { clipboard } = window.require("electron");
525
+ clipboard.writeText(text);
526
+ this.setState({ copyStatus: "Copied!" });
527
+ setTimeout(() => this.setState({ copyStatus: "" }), 2000);
528
+ return;
529
+ }
530
+ } catch {}
531
+
532
+ this.setState({ copyStatus: "Copy failed" });
533
+ setTimeout(() => this.setState({ copyStatus: "" }), 2000);
534
+ },
535
+ },
536
+ "Copy"
537
+ ),
538
+ React.createElement(
539
+ "span",
540
+ { style: { opacity: 0.7, marginLeft: 6 } },
541
+ this.state.copyStatus || ""
542
+ ),
543
+ React.createElement(
544
+ "span",
545
+ { style: { opacity: 0.7, marginLeft: 6 } },
546
+ this.state.lastUpdated ? `Last: ${this.state.lastUpdated}` : ""
547
+ )
548
+ ),
549
+ React.createElement("pre", { style: preStyle }, this.state.meta + "\n" + this.state.output)
550
+ );
551
+ }
552
+ }
553
+
554
+ module.exports = DebugTab;