poi-plugin-compass 0.1.6 → 0.1.7
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.
- package/README.md +9 -0
- package/assets/compass.css +78 -8
- package/assets/icons/parameter/AA.svg +11 -0
- package/assets/icons/parameter/armor.svg +22 -0
- package/assets/icons/parameter/firepower.svg +14 -0
- package/assets/icons/parameter/torpedo.svg +22 -0
- package/data/kcnav-node-types-by-map.json +1 -1
- package/index.js +72 -18
- package/package.json +5 -4
- package/scripts/cache-kcnav-maps.js +11 -2
- package/storage.js +47 -0
package/README.md
CHANGED
|
@@ -49,3 +49,12 @@ npm install poi-plugin-compass
|
|
|
49
49
|
- 地图、规则和敌舰资料随插件提供;舰船头像在 poi 图片缓存不可用时可能请求游戏图片服务。
|
|
50
50
|
|
|
51
51
|
问题反馈:<https://github.com/RikaKagurasaka/poi-plugin-compass/issues>
|
|
52
|
+
|
|
53
|
+
### 设置存储
|
|
54
|
+
|
|
55
|
+
地图、阶段、舰队、显示偏好、路线覆盖和导出选择保存在 poi 数据目录下的
|
|
56
|
+
`compass/preferences.json`。首次使用新版时,自动从当前插件窗口的
|
|
57
|
+
`poi-plugin-compass.preferences.v1` localStorage 项导入;旧项保留作备份,
|
|
58
|
+
已有文件优先,不会再次被旧设置覆盖。设置通过临时文件写入后替换正式文件;
|
|
59
|
+
读取或迁移失败时保留原数据并在控制台报告错误,不会覆盖损坏文件。
|
|
60
|
+
从前曾在主窗口和独立窗口保存不同设置时,以首次成功迁移的窗口为准。
|
package/assets/compass.css
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
.poi-compass {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
height: 100%;
|
|
3
|
+
min-height: 0;
|
|
4
|
+
overflow: hidden;
|
|
4
5
|
color: var(--color-text, #f1f5f9);
|
|
5
6
|
background: var(--color-background, #20242b);
|
|
6
7
|
box-sizing: border-box;
|
|
7
8
|
font-size: 13px;
|
|
8
9
|
}
|
|
9
10
|
|
|
11
|
+
.compass-scroll-viewport {
|
|
12
|
+
height: 100%;
|
|
13
|
+
max-height: 100vh;
|
|
14
|
+
min-height: 0;
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
overflow-x: hidden;
|
|
17
|
+
overflow-y: auto;
|
|
18
|
+
overscroll-behavior: contain;
|
|
19
|
+
padding: 10px;
|
|
20
|
+
scrollbar-gutter: stable;
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
.compass-titlebar {
|
|
11
24
|
display: flex;
|
|
12
25
|
align-items: center;
|
|
@@ -57,7 +70,10 @@
|
|
|
57
70
|
.compass-los-icons { display: flex; gap: 6px; flex-shrink: 0; }
|
|
58
71
|
.compass-los-item { display: inline-flex; align-items: center; gap: 4px; }
|
|
59
72
|
.compass-los-value { color: #a9daf8; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
60
|
-
.compass-fleet-air { color: #91d9b2; font-weight: 650; font-variant-numeric: tabular-nums; white-space: nowrap; flex-shrink: 0; }
|
|
73
|
+
.compass-fleet-air { display: inline-flex; align-items: center; gap: 3px; color: #91d9b2; font-weight: 650; font-variant-numeric: tabular-nums; white-space: nowrap; flex-shrink: 0; }
|
|
74
|
+
.compass-fleet-air-icon,
|
|
75
|
+
.compass-fleet-air-icon img { display: inline-flex; width: 22px; height: 22px; align-items: center; justify-content: center; }
|
|
76
|
+
.compass-fleet-air-icon img { object-fit: contain; }
|
|
61
77
|
.compass-los-icon { position: relative; display: inline-flex; width: 28px; height: 28px; align-items: center; justify-content: center; }
|
|
62
78
|
.compass-los-icon img { width: 28px; height: 28px; }
|
|
63
79
|
.compass-los-coefficient { position: absolute; right: -1px; bottom: -3px; font-size: 14px; font-weight: 800; color: white; -webkit-text-stroke: 3px #151a22; paint-order: stroke fill; }
|
|
@@ -456,7 +472,8 @@
|
|
|
456
472
|
|
|
457
473
|
.compass-enemy-entry summary {
|
|
458
474
|
display: grid;
|
|
459
|
-
grid-template-columns: 58px 64px
|
|
475
|
+
grid-template-columns: 58px 64px max-content max-content;
|
|
476
|
+
justify-content: start;
|
|
460
477
|
align-items: center;
|
|
461
478
|
gap: 6px;
|
|
462
479
|
padding: 7px 2px;
|
|
@@ -493,19 +510,72 @@
|
|
|
493
510
|
overflow-wrap: anywhere;
|
|
494
511
|
}
|
|
495
512
|
|
|
496
|
-
.compass-enemy-entry { min-width:
|
|
513
|
+
.compass-enemy-entry { min-width: 540px; }
|
|
497
514
|
.compass-enemy-stats { margin-top: 8px; }
|
|
498
515
|
.compass-enemy-stat-row {
|
|
499
516
|
display: grid;
|
|
500
|
-
grid-template-columns: 40px repeat(5,
|
|
517
|
+
grid-template-columns: 40px repeat(5, 42px) minmax(150px, 1fr);
|
|
501
518
|
align-items: center;
|
|
502
|
-
gap:
|
|
519
|
+
gap: 6px;
|
|
503
520
|
padding: 4px 0;
|
|
504
521
|
font-variant-numeric: tabular-nums;
|
|
505
522
|
}
|
|
506
523
|
.compass-enemy-stat-row > span:not(:first-child):not(:last-child) { text-align: right; }
|
|
507
524
|
.compass-enemy-stat-row.is-escort-start { border-top: 1px solid #687587; margin-top: 5px; }
|
|
508
|
-
.compass-enemy-air { text-align:
|
|
525
|
+
.compass-enemy-air { text-align: left; white-space: nowrap; }
|
|
526
|
+
|
|
527
|
+
.compass-enemy-equips {
|
|
528
|
+
display: grid;
|
|
529
|
+
grid-template-columns: repeat(5, 28px);
|
|
530
|
+
align-items: center;
|
|
531
|
+
gap: 2px;
|
|
532
|
+
min-width: 0;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.compass-enemy-equip {
|
|
536
|
+
display: inline-flex;
|
|
537
|
+
width: 28px;
|
|
538
|
+
height: 28px;
|
|
539
|
+
align-items: center;
|
|
540
|
+
justify-content: center;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.compass-enemy-equip img {
|
|
544
|
+
display: block;
|
|
545
|
+
width: 28px;
|
|
546
|
+
height: 28px;
|
|
547
|
+
object-fit: contain;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.compass-enemy-equip.is-empty { background: transparent; }
|
|
551
|
+
|
|
552
|
+
.compass-enemy-stat {
|
|
553
|
+
display: inline-flex;
|
|
554
|
+
min-width: 0;
|
|
555
|
+
align-items: center;
|
|
556
|
+
gap: 2px;
|
|
557
|
+
white-space: nowrap;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.compass-enemy-stat-icon {
|
|
561
|
+
display: inline-block;
|
|
562
|
+
width: 18px;
|
|
563
|
+
height: 18px;
|
|
564
|
+
flex: 0 0 18px;
|
|
565
|
+
object-fit: contain;
|
|
566
|
+
vertical-align: middle;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.compass-enemy-stat-icon.fa {
|
|
570
|
+
color: #f1f5f9;
|
|
571
|
+
font-size: 16px;
|
|
572
|
+
line-height: 18px;
|
|
573
|
+
text-align: center;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.compass-enemy-stat-value {
|
|
577
|
+
font-variant-numeric: tabular-nums;
|
|
578
|
+
}
|
|
509
579
|
|
|
510
580
|
.compass-condition.is-unknown {
|
|
511
581
|
border-left-color: #e0af56;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Generator: Adobe Illustrator 16.0.0, Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
3
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
4
|
+
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
5
|
+
width="800px" height="800px" viewBox="0 0 800 800" enable-background="new 0 0 800 800" xml:space="preserve">
|
|
6
|
+
<rect x="125" y="400" fill="#F2F2F2" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" width="601" height="200"/>
|
|
7
|
+
<line fill="none" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" x1="125" y1="441" x2="726" y2="441"/>
|
|
8
|
+
<path fill="#F2F2F2" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" d="M726,400H164c0-27.88,11.47-54.16,31.75-77.25
|
|
9
|
+
L73,200h30l107.71,107.71C261.01,262.66,347.12,233,445,233C600.32,233,726,307.69,726,400z"/>
|
|
10
|
+
<path fill="none" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" d="M303,400c0-88.605,98.869-160.304,221.054-160.304"/>
|
|
11
|
+
</svg>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Generator: Adobe Illustrator 16.0.0, Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
3
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
4
|
+
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
5
|
+
width="800px" height="800px" viewBox="0 0 800 800" enable-background="new 0 0 800 800" xml:space="preserve">
|
|
6
|
+
<polygon fill="#F2F2F2" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" points="615,643.5 586,656.5 215,643.5
|
|
7
|
+
186,156.75 215,144 615,144 "/>
|
|
8
|
+
<rect x="186" y="157" fill="#F2F2F2" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" width="400" height="500"/>
|
|
9
|
+
<line fill="none" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" x1="586" y1="156.5" x2="615" y2="143.5"/>
|
|
10
|
+
<polygon fill="none" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" points="240.975,226 235.95,217 240.975,208
|
|
11
|
+
251.025,208 256.05,217 251.025,226 "/>
|
|
12
|
+
<polygon fill="none" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" points="240.975,606 235.95,597 240.975,588
|
|
13
|
+
251.025,588 256.05,597 251.025,606 "/>
|
|
14
|
+
<polygon fill="none" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" points="240.975,416 235.95,407 240.975,398
|
|
15
|
+
251.025,398 256.05,407 251.025,416 "/>
|
|
16
|
+
<polygon fill="none" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" points="521.975,226 516.95,217 521.975,208
|
|
17
|
+
532.025,208 537.05,217 532.025,226 "/>
|
|
18
|
+
<polygon fill="none" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" points="521.975,606 516.95,597 521.975,588
|
|
19
|
+
532.025,588 537.05,597 532.025,606 "/>
|
|
20
|
+
<polygon fill="none" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" points="521.975,416 516.95,407 521.975,398
|
|
21
|
+
532.025,398 537.05,407 532.025,416 "/>
|
|
22
|
+
</svg>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Generator: Adobe Illustrator 16.0.0, Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
3
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
4
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="800px"
|
|
5
|
+
height="800px" viewBox="0 0 800 800" enable-background="new 0 0 800 800" xml:space="preserve">
|
|
6
|
+
<g id="Layer_1">
|
|
7
|
+
</g>
|
|
8
|
+
<g id="Layer_2">
|
|
9
|
+
<polygon fill="#F2F2F2" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" points="748,424 748,547 708,547 708,661
|
|
10
|
+
92,661 92,547 52,547 52,424 92,424 92,335 368.566,335 552.831,139 626.871,139 442.606,335 490.197,335 674.461,139 748.51,139
|
|
11
|
+
564.246,335 708,335 708,424 ">
|
|
12
|
+
</polygon>
|
|
13
|
+
</g>
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Generator: Adobe Illustrator 16.0.0, Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
3
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
4
|
+
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
5
|
+
width="800px" height="800px" viewBox="0 0 800 800" enable-background="new 0 0 800 800" xml:space="preserve">
|
|
6
|
+
<g>
|
|
7
|
+
<polyline fill="#F2F2F2" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" points="339.943,481.163 420.951,562.173
|
|
8
|
+
337.583,681.722 278.595,622.733 220.395,564.534 339.701,480.924 "/>
|
|
9
|
+
<path fill="#F2F2F2" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" d="M339.155,480.377
|
|
10
|
+
c-32.604,32.605-83.143,119.773-60.56,142.356c22.583,22.583,109.752-27.955,142.356-60.561L627.014,356.11
|
|
11
|
+
c47.386-47.383,67.413-104.042,44.829-126.625c-22.581-22.583-79.239-2.554-126.626,44.83L339.155,480.377z"/>
|
|
12
|
+
<line fill="#F2F2F2" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" x1="545.217" y1="274.314" x2="627.014" y2="356.11"/>
|
|
13
|
+
</g>
|
|
14
|
+
<g>
|
|
15
|
+
<polyline fill="#F2F2F2" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" points="238.324,379.087 319.333,460.097
|
|
16
|
+
235.964,579.646 176.977,520.656 118.777,462.457 238.083,378.847 "/>
|
|
17
|
+
<path fill="#F2F2F2" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" d="M237.537,378.301
|
|
18
|
+
c-32.604,32.604-83.143,119.773-60.561,142.355c22.583,22.584,109.752-27.955,142.357-60.56l206.062-206.063
|
|
19
|
+
c47.385-47.385,67.412-104.043,44.83-126.626c-22.582-22.582-79.241-2.555-126.627,44.83L237.537,378.301z"/>
|
|
20
|
+
<line fill="#F2F2F2" stroke="#4D4D4D" stroke-width="10" stroke-miterlimit="10" x1="443.599" y1="172.238" x2="525.396" y2="254.034"/>
|
|
21
|
+
</g>
|
|
22
|
+
</svg>
|
package/index.js
CHANGED
|
@@ -27,7 +27,11 @@ const mapAreas = [
|
|
|
27
27
|
]
|
|
28
28
|
const LOCAL_KCNAV_ASSET_ROOT = path.join(__dirname, 'assets', 'kcnav')
|
|
29
29
|
const LOCAL_PROPHET_SPOT_ICON_ROOT = path.join(__dirname, 'assets', 'icons', 'spot')
|
|
30
|
-
const
|
|
30
|
+
const LOCAL_PROPHET_PARAMETER_ICON_ROOT = path.join(__dirname, 'assets', 'icons', 'parameter')
|
|
31
|
+
const preferencesStorage = require('./storage')
|
|
32
|
+
const preferencesBase = () => typeof APPDATA_PATH !== 'undefined'
|
|
33
|
+
? APPDATA_PATH
|
|
34
|
+
: path.join(process.env.HOME || process.cwd(), '.poi-dark')
|
|
31
35
|
|
|
32
36
|
const MAP_PHASES = {
|
|
33
37
|
'5-6': [
|
|
@@ -209,12 +213,10 @@ function normalizeOverrides(value) {
|
|
|
209
213
|
}
|
|
210
214
|
|
|
211
215
|
function readCompassPreferences() {
|
|
212
|
-
const storage = compassStorage()
|
|
213
|
-
if (!storage) return {}
|
|
214
216
|
try {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
217
|
+
return preferencesStorage.load(preferencesBase(), compassStorage())
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.warn('[compass] 无法读取设置;原数据已保留', error)
|
|
218
220
|
return {}
|
|
219
221
|
}
|
|
220
222
|
}
|
|
@@ -375,6 +377,10 @@ function prophetSpotIconUrl(icon) {
|
|
|
375
377
|
return pathToFileURL(path.join(LOCAL_PROPHET_SPOT_ICON_ROOT, `${icon}.svg`)).toString()
|
|
376
378
|
}
|
|
377
379
|
|
|
380
|
+
function prophetParameterIconUrl(icon) {
|
|
381
|
+
return pathToFileURL(path.join(LOCAL_PROPHET_PARAMETER_ICON_ROOT, `${icon}.svg`)).toString()
|
|
382
|
+
}
|
|
383
|
+
|
|
378
384
|
function nodeMarker(nodeType, nodeInfo) {
|
|
379
385
|
const prophetIcon = PROPHET_NODE_ICONS[nodeType]
|
|
380
386
|
if (prophetIcon) return { source: 'prophet', url: prophetSpotIconUrl(prophetIcon) }
|
|
@@ -728,10 +734,8 @@ class Compass extends React.Component {
|
|
|
728
734
|
}
|
|
729
735
|
|
|
730
736
|
persistPreferences() {
|
|
731
|
-
const storage = compassStorage()
|
|
732
|
-
if (!storage) return
|
|
733
737
|
try {
|
|
734
|
-
const previous =
|
|
738
|
+
const previous = preferencesStorage.load(preferencesBase(), compassStorage())
|
|
735
739
|
const overridesByView = isRecord(previous.overridesByView) ? { ...previous.overridesByView } : {}
|
|
736
740
|
overridesByView[viewPreferenceKey(this.state.mapId, this.state.phaseId, this.state.deckId)] = normalizeOverrides(this.state.overrides)
|
|
737
741
|
const exportByView = isRecord(previous.exportByView) ? { ...previous.exportByView } : {}
|
|
@@ -744,7 +748,7 @@ class Compass extends React.Component {
|
|
|
744
748
|
phaseByMap[this.state.mapId] = this.state.phaseId
|
|
745
749
|
const routeOptionByMap = isRecord(previous.routeOptionByMap) ? { ...previous.routeOptionByMap } : {}
|
|
746
750
|
if (this.state.routeOptionId) routeOptionByMap[this.state.mapId] = this.state.routeOptionId
|
|
747
|
-
|
|
751
|
+
preferencesStorage.save(preferencesBase(), {
|
|
748
752
|
...previous,
|
|
749
753
|
mapId: this.state.mapId,
|
|
750
754
|
phaseId: this.state.phaseId,
|
|
@@ -756,9 +760,9 @@ class Compass extends React.Component {
|
|
|
756
760
|
showEnemyAvatars: this.state.showEnemyAvatars,
|
|
757
761
|
overridesByView,
|
|
758
762
|
exportByView,
|
|
759
|
-
})
|
|
760
|
-
} catch (
|
|
761
|
-
|
|
763
|
+
})
|
|
764
|
+
} catch (error) {
|
|
765
|
+
console.warn('[compass] 无法保存设置;原数据已保留', error)
|
|
762
766
|
}
|
|
763
767
|
}
|
|
764
768
|
|
|
@@ -861,8 +865,15 @@ class Compass extends React.Component {
|
|
|
861
865
|
|
|
862
866
|
renderFleetAir(air) {
|
|
863
867
|
if (!air || !Number.isFinite(air.min) || !Number.isFinite(air.max) || air.max <= 0) return null
|
|
864
|
-
|
|
865
|
-
|
|
868
|
+
const { SlotitemIcon } = require('views/components/etc/icon')
|
|
869
|
+
const value = air.min === air.max ? air.min : `${air.min}~${air.max}`
|
|
870
|
+
return h('span', {
|
|
871
|
+
className: 'compass-fleet-air',
|
|
872
|
+
title: `poi 当前舰队制空(熟练度范围):${value}`,
|
|
873
|
+
'aria-label': `当前舰队制空 ${value}`,
|
|
874
|
+
},
|
|
875
|
+
h('span', { className: 'compass-fleet-air-icon', 'aria-hidden': 'true' }, h(SlotitemIcon, { slotitemId: 6, alt: '' })),
|
|
876
|
+
h('span', { className: 'compass-fleet-air-value' }, value))
|
|
866
877
|
}
|
|
867
878
|
|
|
868
879
|
setManualOverride(node, to, edgeId = null, selectedNode = node) {
|
|
@@ -1217,6 +1228,45 @@ class Compass extends React.Component {
|
|
|
1217
1228
|
return `${master.api_name}${rank}`
|
|
1218
1229
|
}
|
|
1219
1230
|
const equipName = (id) => masters.$equips?.[id]?.api_name || `#${id}`
|
|
1231
|
+
const equipIconId = (id) => {
|
|
1232
|
+
const iconId = Number(masters.$equips?.[id]?.api_type?.[3])
|
|
1233
|
+
return Number.isFinite(iconId) && iconId > 0 ? iconId : Number(id)
|
|
1234
|
+
}
|
|
1235
|
+
const { SlotitemIcon } = require('views/components/etc/icon')
|
|
1236
|
+
const FontAwesome = require('react-fontawesome')
|
|
1237
|
+
const statDefinitions = {
|
|
1238
|
+
hp: { label: 'HP', icon: 'heart' },
|
|
1239
|
+
fp: { label: '火力', icon: 'firepower' },
|
|
1240
|
+
torp: { label: '雷装', icon: 'torpedo' },
|
|
1241
|
+
aa: { label: '对空', icon: 'AA' },
|
|
1242
|
+
armor: { label: '装甲', icon: 'armor' },
|
|
1243
|
+
}
|
|
1244
|
+
const renderEnemyStat = (field, value) => {
|
|
1245
|
+
const definition = statDefinitions[field]
|
|
1246
|
+
const icon = definition.icon === 'heart'
|
|
1247
|
+
? h(FontAwesome, { name: 'heart', className: 'compass-enemy-stat-icon', 'aria-hidden': 'true' })
|
|
1248
|
+
: h('img', { className: 'compass-enemy-stat-icon', src: prophetParameterIconUrl(definition.icon), alt: '' })
|
|
1249
|
+
return h('span', {
|
|
1250
|
+
key: field,
|
|
1251
|
+
className: `compass-enemy-stat compass-enemy-stat-${field}`,
|
|
1252
|
+
title: definition.label,
|
|
1253
|
+
'aria-label': `${definition.label} ${value ?? '—'}`,
|
|
1254
|
+
}, icon, h('span', { className: 'compass-enemy-stat-value' }, value ?? '—'))
|
|
1255
|
+
}
|
|
1256
|
+
const renderEnemyEquip = (id, index) => {
|
|
1257
|
+
if (!(Number(id) > 0)) return h('span', {
|
|
1258
|
+
key: `empty-${index}`,
|
|
1259
|
+
className: 'compass-enemy-equip is-empty',
|
|
1260
|
+
'aria-hidden': 'true',
|
|
1261
|
+
})
|
|
1262
|
+
const name = equipName(id)
|
|
1263
|
+
return h('span', {
|
|
1264
|
+
key: `${id}-${index}`,
|
|
1265
|
+
className: 'compass-enemy-equip',
|
|
1266
|
+
title: name,
|
|
1267
|
+
'aria-label': name,
|
|
1268
|
+
}, h(SlotitemIcon, { slotitemId: equipIconId(id), alt: '' }))
|
|
1269
|
+
}
|
|
1220
1270
|
return h('section', { className: 'compass-panel compass-enemies' }, heading,
|
|
1221
1271
|
data.rows.length ? h('div', { className: 'compass-enemy-air-range' }, `空优 ${rangeText(data.superiority)} / 空确 ${rangeText(data.supremacy)}${data.uncertain ? '?' : ''}`) : null,
|
|
1222
1272
|
!data.rows.length ? h('div', { className: 'compass-muted' }, '此期间无记录') : null,
|
|
@@ -1239,11 +1289,13 @@ class Compass extends React.Component {
|
|
|
1239
1289
|
h('div', null, `陆航:${airText(entry.lbasThresholds)}`),
|
|
1240
1290
|
entry.uncertain ? h('div', null, `装备不确定:${entry.uncertainAirpowerItems.join('、')}`) : null,
|
|
1241
1291
|
h('div', { className: 'compass-enemy-stats' },
|
|
1242
|
-
h('div', { className: 'compass-enemy-stat-row compass-muted' }, ...['', 'HP', '火力', '雷装', '对空', '装甲', '装备'].map((label, i) => h('span', { key: i }, label))),
|
|
1243
1292
|
...fleet.map((ship, shipIndex) => h('div', { key: shipIndex, className: `compass-enemy-stat-row${shipIndex === entry.mainFleet.length ? ' is-escort-start' : ''}` },
|
|
1244
1293
|
this.renderShipPortrait(ship.id, shipName(ship), getRootState()),
|
|
1245
|
-
...['hp', 'fp', 'torp', 'aa', 'armor'].map(field =>
|
|
1246
|
-
h('span', { className: 'compass-enemy-equips' },
|
|
1294
|
+
...['hp', 'fp', 'torp', 'aa', 'armor'].map(field => renderEnemyStat(field, ship[field])),
|
|
1295
|
+
h('span', { className: 'compass-enemy-equips' },
|
|
1296
|
+
Array.isArray(ship.equips) && ship.equips.length
|
|
1297
|
+
? ship.equips.map(renderEnemyEquip)
|
|
1298
|
+
: h('span', { className: 'compass-enemy-equip is-empty' }, '—')),
|
|
1247
1299
|
)),
|
|
1248
1300
|
),
|
|
1249
1301
|
),
|
|
@@ -1361,6 +1413,7 @@ class Compass extends React.Component {
|
|
|
1361
1413
|
].filter(Boolean)
|
|
1362
1414
|
|
|
1363
1415
|
return h('div', { className: 'poi-compass' },
|
|
1416
|
+
h('div', { className: 'compass-scroll-viewport' },
|
|
1364
1417
|
h('link', { rel: 'stylesheet', href: cssPath }),
|
|
1365
1418
|
h('header', { className: 'compass-titlebar' }, h('h3', null, '舰队罗盘')),
|
|
1366
1419
|
h('div', { className: 'compass-toolbar' },
|
|
@@ -1446,6 +1499,7 @@ class Compass extends React.Component {
|
|
|
1446
1499
|
),
|
|
1447
1500
|
),
|
|
1448
1501
|
h('div', { className: 'compass-enemy-region' }, this.renderEnemyPanel(this.state.mapId, selectedNode)),
|
|
1502
|
+
),
|
|
1449
1503
|
)
|
|
1450
1504
|
}
|
|
1451
1505
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poi-plugin-compass",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "通常海域舰队罗盘",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"homepage": "https://github.com/RikaKagurasaka/poi-plugin-compass#readme",
|
|
14
14
|
"files": [
|
|
15
15
|
"index.js",
|
|
16
|
+
"storage.js",
|
|
16
17
|
"logic.js",
|
|
17
18
|
"enemies.js",
|
|
18
19
|
"exporters.js",
|
|
@@ -31,8 +32,8 @@
|
|
|
31
32
|
"cache:kcnav-enemies:long-low": "node scripts/cache-kcnav-enemies-long-low.js",
|
|
32
33
|
"compress:kcnav-maps": "node scripts/compress-kcnav-maps.js --remove-png",
|
|
33
34
|
"compress:kcnav-enemies": "node scripts/compact-kcnav-enemies.js",
|
|
34
|
-
"check:syntax": "node --check index.js && node --check logic.js && node --check enemies.js && node --check exporters.js && node --check scripts/cache-kcnav-enemies.js && node --check scripts/cache-kcnav-enemies-long-low.js && node --check scripts/compress-kcnav-maps.js && node --check scripts/compact-kcnav-enemies.js && node --check test.js && node --check test-enemies.js && node --check test-exporters.js",
|
|
35
|
-
"test": "node test.js && node test-enemies.js && node test-exporters.js && node test-ui.js && node test-overview.js"
|
|
35
|
+
"check:syntax": "node --check index.js && node --check logic.js && node --check enemies.js && node --check exporters.js && node --check scripts/cache-kcnav-enemies.js && node --check scripts/cache-kcnav-enemies-long-low.js && node --check scripts/compress-kcnav-maps.js && node --check scripts/compact-kcnav-enemies.js && node --check test.js && node --check test-enemies.js && node --check test-exporters.js && node --check storage.js",
|
|
36
|
+
"test": "node test.js && node test-enemies.js && node test-exporters.js && node test-ui.js && node test-overview.js && node --test test-storage.js"
|
|
36
37
|
},
|
|
37
38
|
"keywords": [
|
|
38
39
|
"poi-plugin",
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
"icon": "fa/compass",
|
|
53
54
|
"priority": 20,
|
|
54
55
|
"apiVer": {
|
|
55
|
-
"11.99.99": "0.1.
|
|
56
|
+
"11.99.99": "0.1.7"
|
|
56
57
|
}
|
|
57
58
|
},
|
|
58
59
|
"peerDependencies": {
|
|
@@ -17,6 +17,14 @@ const mapIds = Array.from(new Set(catalogs.flatMap((catalog) => Object.keys(cata
|
|
|
17
17
|
|
|
18
18
|
const DEFAULT_INTERVAL_MS = 15_000
|
|
19
19
|
|
|
20
|
+
// KCNav currently exposes 6-5 J as a normal battle node, but the WikiWiki
|
|
21
|
+
// route table identifies it as an opening-night battle. Keep the raw response
|
|
22
|
+
// unchanged and apply only this verified presentation correction to the
|
|
23
|
+
// derived node-type cache.
|
|
24
|
+
const VERIFIED_NODE_TYPE_OVERRIDES = {
|
|
25
|
+
'6-5': { J: 11 },
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
function apiUrl(mapId) {
|
|
21
29
|
return `https://tsunkit.net/api/routing/maps/${mapId}/`
|
|
22
30
|
}
|
|
@@ -32,7 +40,7 @@ function intervalFromArgs() {
|
|
|
32
40
|
return Number.isFinite(value) && value >= 0 ? value : DEFAULT_INTERVAL_MS
|
|
33
41
|
}
|
|
34
42
|
|
|
35
|
-
function nodeTypesFromResult(result) {
|
|
43
|
+
function nodeTypesFromResult(result, mapId = null) {
|
|
36
44
|
const nodeValues = {}
|
|
37
45
|
const spots = result?.spots || {}
|
|
38
46
|
Object.keys(spots).forEach((node) => { nodeValues[node] = -1 })
|
|
@@ -48,6 +56,7 @@ function nodeTypesFromResult(result) {
|
|
|
48
56
|
const nodeType = Number(entry[2])
|
|
49
57
|
if (Number.isFinite(nodeType)) nodeValues[entry[1]] = nodeType
|
|
50
58
|
})
|
|
59
|
+
Object.assign(nodeValues, VERIFIED_NODE_TYPE_OVERRIDES[mapId] || {})
|
|
51
60
|
return nodeValues
|
|
52
61
|
}
|
|
53
62
|
|
|
@@ -82,7 +91,7 @@ async function main() {
|
|
|
82
91
|
const content = `${JSON.stringify(payload, null, 2)}\n`
|
|
83
92
|
const contentFile = `${mapId}.json`
|
|
84
93
|
fs.writeFileSync(path.join(outputDir, contentFile), content, 'utf8')
|
|
85
|
-
nodeTypesByMap[mapId] = nodeTypesFromResult(payload.result)
|
|
94
|
+
nodeTypesByMap[mapId] = nodeTypesFromResult(payload.result, mapId)
|
|
86
95
|
manifest.push({
|
|
87
96
|
mapId,
|
|
88
97
|
apiUrl: url,
|
package/storage.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const { randomUUID } = require('crypto')
|
|
5
|
+
const legacyKey = 'poi-plugin-compass.preferences.v1'
|
|
6
|
+
const isRecord = value => value != null && typeof value === 'object' && !Array.isArray(value)
|
|
7
|
+
|
|
8
|
+
function parse(raw) {
|
|
9
|
+
const value = JSON.parse(raw)
|
|
10
|
+
if (!isRecord(value)) throw Error('罗盘设置文件格式错误;原数据已保留')
|
|
11
|
+
return value
|
|
12
|
+
}
|
|
13
|
+
function file(base) {
|
|
14
|
+
if (!base) throw Error('poi 数据目录尚未加载')
|
|
15
|
+
return path.join(base, 'compass', 'preferences.json')
|
|
16
|
+
}
|
|
17
|
+
function save(base, value) {
|
|
18
|
+
const target = file(base)
|
|
19
|
+
// Never replace an unreadable or malformed existing file with default preferences.
|
|
20
|
+
const previous = fs.existsSync(target) ? parse(fs.readFileSync(target, 'utf8')) : null
|
|
21
|
+
const raw = JSON.stringify(value, null, 2) + '\n'
|
|
22
|
+
parse(raw)
|
|
23
|
+
if (previous && JSON.stringify(previous, null, 2) + '\n' === raw) return
|
|
24
|
+
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
25
|
+
const temporary = `${target}.${randomUUID()}.tmp`
|
|
26
|
+
try {
|
|
27
|
+
fs.writeFileSync(temporary, raw, { flag: 'wx' })
|
|
28
|
+
fs.renameSync(temporary, target)
|
|
29
|
+
} finally {
|
|
30
|
+
if (fs.existsSync(temporary)) fs.unlinkSync(temporary)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function load(base, legacy) {
|
|
34
|
+
const target = file(base)
|
|
35
|
+
try {
|
|
36
|
+
return parse(fs.readFileSync(target, 'utf8'))
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error.code !== 'ENOENT') throw error
|
|
39
|
+
}
|
|
40
|
+
const raw = legacy?.getItem(legacyKey)
|
|
41
|
+
if (raw == null) return {}
|
|
42
|
+
const value = parse(raw)
|
|
43
|
+
save(base, value)
|
|
44
|
+
// Keep the original localStorage entry intact as a migration backup.
|
|
45
|
+
return value
|
|
46
|
+
}
|
|
47
|
+
module.exports = { load, save, legacyKey }
|