poi-plugin-compass 0.1.3 → 0.1.4

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/air-status.js CHANGED
@@ -1,11 +1,11 @@
1
1
  'use strict'
2
2
 
3
3
  const states = [
4
- { label: 'AL', color: '#ff8585' },
5
- { label: 'AD', color: '#ffbd79' },
6
- { label: 'AP', color: '#f4df8b' },
7
- { label: 'AS', color: '#8fd7a3' },
8
- { label: 'AS+', color: '#87d9ff' },
4
+ { label: '', shadow: '#ef5b5b' },
5
+ { label: '', shadow: '#ed9f45' },
6
+ { label: '', shadow: '#9aa1aa' },
7
+ { label: '', shadow: '#91d7a2' },
8
+ { label: '', shadow: '#29975e' },
9
9
  ]
10
10
 
11
11
  function airStateRange(air, data) {
@@ -35,6 +35,7 @@
35
35
  min-height: 30px;
36
36
  gap: 8px;
37
37
  margin-bottom: 8px;
38
+ flex-wrap: wrap;
38
39
  }
39
40
 
40
41
  .compass-toolbar-fleet,
@@ -60,6 +61,15 @@
60
61
  .compass-los-icon { position: relative; display: inline-flex; width: 28px; height: 28px; align-items: center; justify-content: center; }
61
62
  .compass-los-icon img { width: 28px; height: 28px; }
62
63
  .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; }
64
+ .compass-option-group { display: inline-flex; align-items: center; flex-shrink: 0; }
65
+ .compass-option-label { padding: 0 4px; color: #c9d8e7; font-size: 12px; }
66
+ .compass-option-group button { border-radius: 0; }
67
+ .compass-option-group button:first-of-type { border-radius: 3px 0 0 3px; }
68
+ .compass-option-group button:last-of-type { border-radius: 0 3px 3px 0; }
69
+ .compass-option-group button + button { margin-left: -1px; }
70
+ .compass-option-group button.is-active { position: relative; z-index: 1; border-color: #8fe0a2; background: rgba(143, 224, 162, 0.2); }
71
+ .compass-option-toggle { display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; color: #c9d8e7; font-size: 12px; white-space: nowrap; }
72
+ .compass-option-toggle input { margin: 0; accent-color: #8fe0a2; }
63
73
  .compass-portrait {
64
74
  position: relative;
65
75
  display: inline-flex;
@@ -100,7 +110,8 @@
100
110
  .compass-map-select summary,
101
111
  .compass-reset,
102
112
  .compass-manual button,
103
- .compass-phase-group button {
113
+ .compass-phase-group button,
114
+ .compass-option-group button {
104
115
  color: #f1f5f9;
105
116
  border: 1px solid #687587;
106
117
  border-radius: 3px;
@@ -178,7 +189,9 @@
178
189
  .compass-manual button:hover,
179
190
  .compass-manual button:focus,
180
191
  .compass-phase-group button:hover,
181
- .compass-phase-group button:focus {
192
+ .compass-phase-group button:focus,
193
+ .compass-option-group button:hover,
194
+ .compass-option-group button:focus {
182
195
  border-color: #b8c7d9;
183
196
  background: #3a4655;
184
197
  }
@@ -216,60 +229,56 @@
216
229
  pointer-events: none;
217
230
  }
218
231
 
219
- .compass-node-air-state { font-size: 18px; font-weight: 750; fill: white; stroke: #151a22; stroke-width: 3px; paint-order: stroke fill; stroke-linejoin: round; }
220
-
221
- .compass-edge {
222
- outline: none;
223
- cursor: pointer;
224
- }
225
-
226
- .compass-edge line {
227
- stroke: #8cbce0;
228
- stroke-width: 4;
229
- opacity: 0.85;
230
- transition: stroke 120ms ease, stroke-width 120ms ease, opacity 120ms ease;
231
- }
232
-
233
- .compass-edge.is-reachable line {
234
- opacity: 1;
235
- }
236
-
237
- .compass-edge.is-probability-high line {
238
- stroke: #78d99a;
239
- stroke-width: 7;
232
+ .compass-node-air-state {
233
+ font-family: "Songti SC", "STSong", "SimSun", serif;
234
+ font-size: 14px;
235
+ font-weight: 700;
236
+ fill: #ffffff;
237
+ stroke: #151a22;
238
+ stroke-width: 2px;
239
+ paint-order: stroke fill;
240
+ stroke-linejoin: round;
240
241
  }
241
242
 
242
- .compass-edge.is-probability-medium line {
243
- stroke: #8fc9f1;
244
- stroke-width: 5;
243
+ .compass-node-icon {
244
+ pointer-events: auto;
245
+ overflow: visible;
245
246
  }
246
247
 
247
- .compass-edge.is-probability-low line {
248
- stroke: #91c4e8;
249
- stroke-width: 4.5;
248
+ .compass-node-icon.is-prophet {
249
+ opacity: 0.92;
250
250
  }
251
251
 
252
- .compass-edge.is-probability-zero line {
253
- stroke: #8cbce0;
254
- stroke-width: 4;
255
- opacity: 0.85;
252
+ .compass-node-overlay {
253
+ overflow: visible;
254
+ pointer-events: none;
256
255
  }
257
256
 
258
- .compass-edge.is-passed line {
259
- stroke-width: 6;
260
- opacity: 1;
257
+ .compass-node-overlay-content {
258
+ display: flex;
259
+ width: 100%;
260
+ height: 100%;
261
+ align-items: center;
262
+ justify-content: center;
263
+ overflow: visible;
261
264
  }
262
265
 
263
- .compass-edge.is-passed.is-probability-high line {
264
- stroke-width: 8;
266
+ .compass-node-enemy-avatar .compass-portrait {
267
+ width: 100% !important;
268
+ height: 100% !important;
269
+ box-shadow: 0 0 0 1px #151a22, 0 0 0 2px #ffffff99;
265
270
  }
266
271
 
267
- .compass-edge.is-passed.is-probability-medium line {
268
- stroke-width: 7;
272
+ .compass-edge {
273
+ outline: none;
274
+ cursor: pointer;
269
275
  }
270
276
 
271
- .compass-edge.is-passed.is-probability-zero line {
272
- stroke-width: 6;
277
+ .compass-edge line {
278
+ stroke: var(--compass-edge-color, #87919a);
279
+ stroke-width: var(--compass-edge-width, 2.25);
280
+ opacity: var(--compass-edge-opacity, 0.3);
281
+ transition: stroke 120ms ease, stroke-width 120ms ease, opacity 120ms ease;
273
282
  }
274
283
 
275
284
  .compass-edge.is-manual-choice {
@@ -324,10 +333,8 @@
324
333
  vector-effect: non-scaling-stroke;
325
334
  }
326
335
 
327
- .compass-node-icon,
328
- .compass-node-icon-secondary {
329
- pointer-events: none;
330
- }
336
+ .compass-node-icon { pointer-events: auto; }
337
+ .compass-node-icon-secondary { pointer-events: none; }
331
338
 
332
339
  .compass-node:hover .compass-node-shape,
333
340
  .compass-node:focus .compass-node-shape {
@@ -354,6 +361,15 @@
354
361
  filter: brightness(1.15);
355
362
  }
356
363
 
364
+ .compass-node.is-node-choice .compass-node-shape {
365
+ filter: drop-shadow(0 0 3px rgba(130, 233, 255, 0.95)) drop-shadow(0 0 8px rgba(130, 233, 255, 0.65));
366
+ }
367
+
368
+ .compass-node.is-node-choice:hover .compass-node-shape,
369
+ .compass-node.is-node-choice:focus .compass-node-shape {
370
+ filter: brightness(1.12) drop-shadow(0 0 3px rgba(130, 233, 255, 1)) drop-shadow(0 0 10px rgba(130, 233, 255, 0.75));
371
+ }
372
+
357
373
  .compass-node-label {
358
374
  text-anchor: middle;
359
375
  pointer-events: none;
@@ -0,0 +1,7 @@
1
+ Copyright (c)
2
+ 2016, 2017 [かがみ](https://github.com/KagamiChan)
3
+ All rights reserved.
4
+
5
+ THESE SVG ICONS ARE **NOT** GOVERNED BY THE MIT LICENSE.
6
+
7
+ ICONS HERE CAN **ONLY** BE USED IN POI, INCLUDING PLUGINS, EXTENSIONS AND DERIVATIVES OF POI. ANY OTHER USAGE WITHOUT EXPLICIT PERMISSION IS PROHIBITED.
@@ -0,0 +1,37 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 17.0.0, SVG 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="400px" height="400px" viewBox="0 0 400 400" style="enable-background:new 0 0 400 400;" xml:space="preserve">
6
+ <circle style="fill:#B2FF59;stroke:#7CB342;stroke-width:50;stroke-miterlimit:10;" cx="200" cy="200" r="160"/>
7
+ <g>
8
+ </g>
9
+ <g>
10
+ </g>
11
+ <g>
12
+ </g>
13
+ <g>
14
+ </g>
15
+ <g>
16
+ </g>
17
+ <g>
18
+ </g>
19
+ <g>
20
+ </g>
21
+ <g>
22
+ </g>
23
+ <g>
24
+ </g>
25
+ <g>
26
+ </g>
27
+ <g>
28
+ </g>
29
+ <g>
30
+ </g>
31
+ <g>
32
+ </g>
33
+ <g>
34
+ </g>
35
+ <g>
36
+ </g>
37
+ </svg>
@@ -0,0 +1,37 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 17.0.0, SVG 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="400px" height="400px" viewBox="0 0 400 400" style="enable-background:new 0 0 400 400;" xml:space="preserve">
6
+ <circle style="fill:#B39DDB;stroke:#512DA8;stroke-width:50;stroke-miterlimit:10;" cx="200" cy="200" r="160"/>
7
+ <g>
8
+ </g>
9
+ <g>
10
+ </g>
11
+ <g>
12
+ </g>
13
+ <g>
14
+ </g>
15
+ <g>
16
+ </g>
17
+ <g>
18
+ </g>
19
+ <g>
20
+ </g>
21
+ <g>
22
+ </g>
23
+ <g>
24
+ </g>
25
+ <g>
26
+ </g>
27
+ <g>
28
+ </g>
29
+ <g>
30
+ </g>
31
+ <g>
32
+ </g>
33
+ <g>
34
+ </g>
35
+ <g>
36
+ </g>
37
+ </svg>
@@ -0,0 +1,37 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 17.0.0, SVG 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="400px" height="400px" viewBox="0 0 400 400" style="enable-background:new 0 0 400 400;" xml:space="preserve">
6
+ <circle style="fill:#EF9A9A;stroke:#D32F2F;stroke-width:50;stroke-miterlimit:10;" cx="200" cy="200" r="160"/>
7
+ <g>
8
+ </g>
9
+ <g>
10
+ </g>
11
+ <g>
12
+ </g>
13
+ <g>
14
+ </g>
15
+ <g>
16
+ </g>
17
+ <g>
18
+ </g>
19
+ <g>
20
+ </g>
21
+ <g>
22
+ </g>
23
+ <g>
24
+ </g>
25
+ <g>
26
+ </g>
27
+ <g>
28
+ </g>
29
+ <g>
30
+ </g>
31
+ <g>
32
+ </g>
33
+ <g>
34
+ </g>
35
+ <g>
36
+ </g>
37
+ </svg>
@@ -0,0 +1,37 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 17.0.0, SVG 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="400px" height="400px" viewBox="0 0 400 400" style="enable-background:new 0 0 400 400;" xml:space="preserve">
6
+ <circle style="fill:#BBDEFB;stroke:#1976D2;stroke-width:50;stroke-miterlimit:10;" cx="200" cy="200" r="160"/>
7
+ <g>
8
+ </g>
9
+ <g>
10
+ </g>
11
+ <g>
12
+ </g>
13
+ <g>
14
+ </g>
15
+ <g>
16
+ </g>
17
+ <g>
18
+ </g>
19
+ <g>
20
+ </g>
21
+ <g>
22
+ </g>
23
+ <g>
24
+ </g>
25
+ <g>
26
+ </g>
27
+ <g>
28
+ </g>
29
+ <g>
30
+ </g>
31
+ <g>
32
+ </g>
33
+ <g>
34
+ </g>
35
+ <g>
36
+ </g>
37
+ </svg>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 17.0.0, SVG 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="400px" height="400px" viewBox="0 0 400 400" enable-background="new 0 0 400 400" xml:space="preserve">
6
+ <circle fill="none" stroke="#B39DDB" stroke-width="20" stroke-miterlimit="10" cx="200" cy="200" r="175"/>
7
+ <circle fill="#512DA8" cx="200" cy="199" r="135"/>
8
+ </svg>
@@ -0,0 +1,30 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 17.0.0, SVG 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="400px" height="400px" viewBox="0 0 400 400" enable-background="new 0 0 400 400" xml:space="preserve">
6
+ <polygon fill="none" points="0,300 0,0 400,0 400,100 220,160 200,200 180,240 "/>
7
+ <g>
8
+ <defs>
9
+ <polygon id="SVGID_1_" points="10,10 390,10 390,92.792 212.908,151.823 172.908,231.823 10,286.126 "/>
10
+ </defs>
11
+ <clipPath id="SVGID_2_">
12
+ <use xlink:href="#SVGID_1_" overflow="visible"/>
13
+ </clipPath>
14
+
15
+ <circle clip-path="url(#SVGID_2_)" fill="none" stroke="#B39DDB" stroke-width="20" stroke-miterlimit="10" cx="200" cy="199" r="175"/>
16
+ <circle clip-path="url(#SVGID_2_)" fill="#512DA8" cx="200" cy="198" r="135"/>
17
+ </g>
18
+ <polygon fill="none" points="400,100 400,400 0,400 0,300 180,240 200,200 220,160 "/>
19
+ <g>
20
+ <defs>
21
+ <polygon id="SVGID_3_" points="10,390 10,307.208 187.092,248.177 227.092,168.177 390,113.875 390,390 "/>
22
+ </defs>
23
+ <clipPath id="SVGID_4_">
24
+ <use xlink:href="#SVGID_3_" overflow="visible"/>
25
+ </clipPath>
26
+
27
+ <circle clip-path="url(#SVGID_4_)" fill="none" stroke="#EF9A9A" stroke-width="20" stroke-miterlimit="10" cx="200" cy="200" r="175"/>
28
+ <circle clip-path="url(#SVGID_4_)" fill="#D32F2F" cx="200" cy="199" r="135"/>
29
+ </g>
30
+ </svg>
@@ -0,0 +1,50 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 17.0.0, SVG 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="400px" height="400px" viewBox="0 0 400 400" style="enable-background:new 0 0 400 400;" xml:space="preserve">
6
+ <g>
7
+ <path style="fill:#9CCC65;stroke:#33691E;stroke-width:30;stroke-miterlimit:10;" d="M124.027,115.36"/>
8
+ <path style="fill:#D32F2F;" d="M199.726,104.727c0,0,46.718-0.637,71.481,24.681c24.337,6.663,51.808-99.407,51.808-99.407
9
+ s22.973,39.45,27.481,71.86s-4.077,101.078-4.077,101.078s3.981,2.502,5.833,16.916s10.311,44.288,0,75.072
10
+ c-10.311,30.784-63.914,64.056-87.788,70.736c-25.225,5.779-103.701,5.779-128.926,0c-23.874-6.68-77.477-39.834-87.788-70.618
11
+ s-1.851-60.717,0-75.131c1.851-14.414,5.833-16.946,5.833-16.946s-8.585-68.684-4.077-101.093s27.481-71.867,27.481-71.867
12
+ s27.471,106.067,51.808,99.403c24.762-25.319,71.481-24.683,71.481-24.683"/>
13
+ <g>
14
+ <path style="fill:#DF9A9A;" d="M101.707,161.397c0,0,24,32.333,65,47.667c-1,11.167-31.833,28-57.667,8.333
15
+ S101.707,161.397,101.707,161.397z"/>
16
+ <path style="fill:#DF9A9A;" d="M298.293,161.397c0,0-24,32.333-65,47.667c1,11.167,31.833,28,57.667,8.333
17
+ C316.793,197.73,298.293,161.397,298.293,161.397z"/>
18
+ </g>
19
+ </g>
20
+ <g>
21
+ </g>
22
+ <g>
23
+ </g>
24
+ <g>
25
+ </g>
26
+ <g>
27
+ </g>
28
+ <g>
29
+ </g>
30
+ <g>
31
+ </g>
32
+ <g>
33
+ </g>
34
+ <g>
35
+ </g>
36
+ <g>
37
+ </g>
38
+ <g>
39
+ </g>
40
+ <g>
41
+ </g>
42
+ <g>
43
+ </g>
44
+ <g>
45
+ </g>
46
+ <g>
47
+ </g>
48
+ <g>
49
+ </g>
50
+ </svg>
@@ -0,0 +1,56 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 17.0.0, SVG 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="400px" height="400px" viewBox="0 0 400 400" style="enable-background:new 0 0 400 400;" xml:space="preserve">
6
+ <circle style="fill:#E1F5FE;stroke:#33691E;stroke-width:20;stroke-miterlimit:10;" cx="200" cy="200" r="165"/>
7
+ <g>
8
+ <path style="fill:none;" d="M-138.068,124.63c-2.204,0-4.112,0.805-5.723,2.416c-1.611,1.611-2.416,3.518-2.416,5.723
9
+ s0.805,4.112,2.416,5.723c1.611,1.611,3.518,2.416,5.723,2.416s4.112-0.805,5.723-2.416c1.611-1.611,2.416-3.518,2.416-5.723
10
+ s-0.805-4.112-2.416-5.723C-133.956,125.435-135.864,124.63-138.068,124.63z"/>
11
+ <path style="fill:#33691E;" d="M281.414,252.786l-2.561,3.461c-6.119,8.311-14.844,15.379-25.88,21.01
12
+ c-10.841,5.532-23.06,9.23-36.195,10.994l-5.778,0.76V196h29.643c0.878,0,1.62-0.269,2.296-0.945
13
+ c0.677-0.676,1.061-1.303,1.061-2.18v-16.278c0-0.877-0.385-1.74-1.06-2.416c-0.677-0.676-1.419-1.181-2.297-1.181H211v-28.137
14
+ l2.585-1.449c4.22-2.474,7.621-5.75,10.175-10.018c2.518-4.208,3.745-8.713,3.745-13.774c0-7.677-2.637-14.05-8.07-19.483
15
+ c-5.433-5.433-11.8-8.073-19.477-8.073c-7.677,0-14.039,2.641-19.472,8.073c-5.433,5.433-8.052,11.806-8.052,19.483
16
+ c0,5.061,1.268,9.566,3.786,13.775c2.552,4.267,5.923,7.543,10.143,10.017l2.637,1.449V173h-29.748
17
+ c-0.877,0-1.568,0.505-2.243,1.18c-0.677,0.677-1.009,1.539-1.009,2.417v16.278c0,0.877,0.332,1.504,1.009,2.18
18
+ s1.366,0.945,2.243,0.945H189v93.011l-5.83-0.742c-13.136-1.764-25.463-5.444-36.305-10.976
19
+ c-11.036-5.631-19.732-12.665-25.851-20.974l-2.547-3.317L132.677,239H91l0.002,41.467l14.159-14.337l3.504,4.194
20
+ c9.522,11.442,22.506,20.649,38.592,27.382c16.221,6.789,33.949,10.227,52.691,10.227c18.742,0,36.485-3.445,52.705-10.234
21
+ c16.086-6.732,29.085-15.949,38.607-27.392l3.527-4.212L309,280.254v-41.684l-41.739,0.002L281.414,252.786z M199.948,132.762
22
+ c-3.518,0-6.72-1.342-9.258-3.88c-2.539-2.539-3.881-5.741-3.881-9.259c0-3.518,1.342-6.719,3.881-9.258s5.741-3.881,9.258-3.881
23
+ c3.519,0,6.72,1.342,9.258,3.88c2.539,2.538,3.881,5.74,3.881,9.259s-1.342,6.721-3.88,9.258
24
+ C206.668,131.42,203.466,132.762,199.948,132.762z"/>
25
+ </g>
26
+ <g>
27
+ </g>
28
+ <g>
29
+ </g>
30
+ <g>
31
+ </g>
32
+ <g>
33
+ </g>
34
+ <g>
35
+ </g>
36
+ <g>
37
+ </g>
38
+ <g>
39
+ </g>
40
+ <g>
41
+ </g>
42
+ <g>
43
+ </g>
44
+ <g>
45
+ </g>
46
+ <g>
47
+ </g>
48
+ <g>
49
+ </g>
50
+ <g>
51
+ </g>
52
+ <g>
53
+ </g>
54
+ <g>
55
+ </g>
56
+ </svg>
@@ -0,0 +1,37 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 17.0.0, SVG 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="400px" height="400px" viewBox="0 0 400 400" style="enable-background:new 0 0 400 400;" xml:space="preserve">
6
+ <circle style="fill:#9CCC65;stroke:#33691E;stroke-width:50;stroke-miterlimit:10;" cx="200" cy="200" r="160"/>
7
+ <g>
8
+ </g>
9
+ <g>
10
+ </g>
11
+ <g>
12
+ </g>
13
+ <g>
14
+ </g>
15
+ <g>
16
+ </g>
17
+ <g>
18
+ </g>
19
+ <g>
20
+ </g>
21
+ <g>
22
+ </g>
23
+ <g>
24
+ </g>
25
+ <g>
26
+ </g>
27
+ <g>
28
+ </g>
29
+ <g>
30
+ </g>
31
+ <g>
32
+ </g>
33
+ <g>
34
+ </g>
35
+ <g>
36
+ </g>
37
+ </svg>
package/index.js CHANGED
@@ -26,6 +26,8 @@ const mapAreas = [
26
26
  ['4', '西方'], ['5', '南方'], ['6', '中部'],
27
27
  ]
28
28
  const LOCAL_KCNAV_ASSET_ROOT = path.join(__dirname, 'assets', 'kcnav')
29
+ const LOCAL_PROPHET_SPOT_ICON_ROOT = path.join(__dirname, 'assets', 'icons', 'spot')
30
+ const PREFERENCES_STORAGE_KEY = 'poi-plugin-compass.preferences.v1'
29
31
 
30
32
  const MAP_PHASES = {
31
33
  '5-6': [
@@ -67,7 +69,6 @@ const {
67
69
  evaluateMap,
68
70
  evaluatePredicate,
69
71
  fleetContextFromState,
70
- formatPercent,
71
72
  mapNodeLabel,
72
73
  normalizeMapId,
73
74
  normalizeOutcomes,
@@ -114,6 +115,18 @@ const NODE_DEFAULTS = {
114
115
  border: '#999999',
115
116
  }
116
117
 
118
+ const COMBAT_NODE_TYPES = new Set([4, 5, 11, 13, 15])
119
+ const AIR_NODE_TYPES = new Set([7, 10])
120
+ const AIR_STATUS_NODE_TYPES = new Set([4, 5, 7, 10, 11, 13, 15])
121
+ const ICON_ONLY_NODE_TYPES = new Set([0, 5, 7, 8, 10])
122
+ const PROPHET_NODE_ICONS = {
123
+ 5: '5',
124
+ 8: '8',
125
+ }
126
+ const NODE_ICON_SIZE = 64
127
+ const NODE_ICON_SIZE_BY_TYPE = { 7: 56, 8: 48, 10: 56 }
128
+ const NODE_ICON_OFFSET_Y_BY_TYPE = { 0: -2, 5: -6 }
129
+
117
130
  function nodeVisualStyle(nodeInfo = {}) {
118
131
  const style = { ...NODE_DEFAULTS, ...nodeInfo }
119
132
  const size = Number.isFinite(Number(style.size)) ? Number(style.size) : Number(style.simpleSize)
@@ -156,6 +169,66 @@ function getRootState() {
156
169
  return store && typeof store.getState === 'function' ? store.getState() : {}
157
170
  }
158
171
 
172
+ function compassStorage() {
173
+ try {
174
+ return typeof window !== 'undefined' && window.localStorage ? window.localStorage : null
175
+ } catch (_) {
176
+ return null
177
+ }
178
+ }
179
+
180
+ function isRecord(value) {
181
+ return value != null && typeof value === 'object' && !Array.isArray(value)
182
+ }
183
+
184
+ function normalizeOverrides(value) {
185
+ if (!isRecord(value)) return {}
186
+ return Object.fromEntries(Object.entries(value).filter(([node, target]) => node && typeof target === 'string' && target))
187
+ }
188
+
189
+ function readCompassPreferences() {
190
+ const storage = compassStorage()
191
+ if (!storage) return {}
192
+ try {
193
+ const parsed = JSON.parse(storage.getItem(PREFERENCES_STORAGE_KEY) || '{}')
194
+ return isRecord(parsed) ? parsed : {}
195
+ } catch (_) {
196
+ return {}
197
+ }
198
+ }
199
+
200
+ function viewPreferenceKey(mapId, phaseId, deckId) {
201
+ return `${mapId}|${phaseId || ''}|${deckId}`
202
+ }
203
+
204
+ function storedOverridesForView(preferences, mapId, phaseId, deckId) {
205
+ const overridesByView = isRecord(preferences?.overridesByView) ? preferences.overridesByView : {}
206
+ return normalizeOverrides(overridesByView[viewPreferenceKey(mapId, phaseId, deckId)])
207
+ }
208
+
209
+ function normalizeStringMap(value) {
210
+ if (!isRecord(value)) return {}
211
+ return Object.fromEntries(Object.entries(value).filter(([key, entry]) => key && typeof entry === 'string' && entry))
212
+ }
213
+
214
+ function normalizeNumberMap(value) {
215
+ if (!isRecord(value)) return {}
216
+ return Object.fromEntries(Object.entries(value)
217
+ .map(([key, entry]) => [key, Number(entry)])
218
+ .filter(([key, entry]) => key && Number.isFinite(entry)))
219
+ }
220
+
221
+ function storedExportPreferencesForView(preferences, mapId, phaseId, deckId) {
222
+ const exportByView = isRecord(preferences?.exportByView) ? preferences.exportByView : {}
223
+ const stored = exportByView[viewPreferenceKey(mapId, phaseId, deckId)]
224
+ if (!isRecord(stored)) return { exportRoute: null, exportEnemies: {}, exportFormations: {} }
225
+ return {
226
+ exportRoute: typeof stored.route === 'string' && stored.route ? stored.route : null,
227
+ exportEnemies: normalizeStringMap(stored.enemies),
228
+ exportFormations: normalizeNumberMap(stored.formations),
229
+ }
230
+ }
231
+
159
232
  function getFleet(state, deckId = 1) {
160
233
  const fleets = state?.info?.fleets
161
234
  if (Array.isArray(fleets)) return fleets[deckId - 1] || null
@@ -275,6 +348,21 @@ function nodeIconUrl(icon) {
275
348
  return null
276
349
  }
277
350
 
351
+ function prophetSpotIconUrl(icon) {
352
+ if (icon == null) return null
353
+ return pathToFileURL(path.join(LOCAL_PROPHET_SPOT_ICON_ROOT, `${icon}.svg`)).toString()
354
+ }
355
+
356
+ function nodeMarker(nodeType, nodeInfo) {
357
+ const prophetIcon = PROPHET_NODE_ICONS[nodeType]
358
+ if (prophetIcon) return { source: 'prophet', url: prophetSpotIconUrl(prophetIcon) }
359
+ if ([0, 7, 10].includes(nodeType)) {
360
+ const kcnavIcon = nodeIconUrl(nodeInfo?.icon)
361
+ return kcnavIcon ? { source: 'kcnav', url: kcnavIcon } : null
362
+ }
363
+ return null
364
+ }
365
+
278
366
  function localNodeTypesForMap(mapId, mapDefinition) {
279
367
  const nodeTypes = { ...(localKcnavNodeTypes[mapId] || {}) }
280
368
  if (mapDefinition?.start) nodeTypes[mapDefinition.start] = 0
@@ -400,8 +488,19 @@ function routeEdges(geometry) {
400
488
  .filter((edge) => edge.from && edge.to)
401
489
  }
402
490
 
403
- function outcomeText(outcome) {
404
- return `${outcome.to} ${formatPercent(outcome.probability, outcome.estimated)}`
491
+ function formatProbability(value, estimated = false, mode = 'percent') {
492
+ if (!Number.isFinite(Number(value))) return '未知'
493
+ const probability = Math.max(0, Math.min(1, Number(value)))
494
+ const suffix = estimated ? '?' : ''
495
+ if (mode === 'decimal') {
496
+ const rounded = Math.round(probability * 100) / 100
497
+ return `${rounded === 1 ? '1' : rounded.toFixed(2).replace(/^0/, '')}${suffix}`
498
+ }
499
+ return `${Math.round(probability * 100)}%${suffix}`
500
+ }
501
+
502
+ function outcomeText(outcome, mode = 'percent') {
503
+ return `${outcome.to} ${formatProbability(outcome.probability, outcome.estimated, mode)}`
405
504
  }
406
505
 
407
506
  // These predicates came from separate list items in the cached KCWiki route
@@ -515,12 +614,21 @@ function edgeProbability(evaluation, from, to) {
515
614
  }
516
615
  }
517
616
 
518
- function probabilityTier(value) {
519
- if (!Number.isFinite(Number(value))) return ''
520
- if (Number(value) === 0) return 'is-probability-zero'
521
- if (Number(value) >= 0.75) return 'is-probability-high'
522
- if (Number(value) >= 0.4) return 'is-probability-medium'
523
- return 'is-probability-low'
617
+ function edgeVisualStyle(value) {
618
+ const probability = Number(value)
619
+ if (!Number.isFinite(probability) || probability <= 0) {
620
+ return { color: '#87919a', width: 2.25, opacity: 0.3 }
621
+ }
622
+ const ratio = Math.max(0, Math.min(1, probability))
623
+ if (ratio >= 0.999999) return { color: '#78d99a', width: 7, opacity: 1 }
624
+ const start = [125, 135, 145]
625
+ const end = [120, 217, 154]
626
+ const color = `rgb(${start.map((component, index) => Math.round(component + (end[index] - component) * ratio)).join(', ')})`
627
+ return {
628
+ color,
629
+ width: 2.25 + ratio * 4.75,
630
+ opacity: 0.3 + ratio * 0.7,
631
+ }
524
632
  }
525
633
 
526
634
  function mapBackgroundUrl(mapId) {
@@ -546,19 +654,27 @@ function manualSourceForTarget(evaluation, target) {
546
654
  class Compass extends React.Component {
547
655
  constructor(props) {
548
656
  super(props)
657
+ const preferences = readCompassPreferences()
658
+ const storedMapId = typeof preferences.mapId === 'string' && mapCatalog.maps[preferences.mapId]
659
+ ? preferences.mapId : null
660
+ const initialMapId = storedMapId
661
+ || (pluginState.currentMapId && mapCatalog.maps[pluginState.currentMapId] ? pluginState.currentMapId : mapIds[0])
662
+ const initialDeckId = [1, 2, 3, 4].includes(Number(preferences.deckId)) ? Number(preferences.deckId) : 1
663
+ const preferredPhaseId = preferences.phaseByMap?.[initialMapId] ?? preferences.phaseId
664
+ const initialPhaseId = phaseOptionForMap(initialMapId, preferredPhaseId)?.id || null
665
+ const initialExportPreferences = storedExportPreferencesForView(preferences, initialMapId, initialPhaseId, initialDeckId)
549
666
  this.state = {
550
- deckId: 1,
551
- mapId: pluginState.currentMapId && mapCatalog.maps[pluginState.currentMapId]
552
- ? pluginState.currentMapId
553
- : mapIds[0],
554
- phaseId: phaseOptionsForMap(mapIds[0])[0]?.id || null,
667
+ deckId: initialDeckId,
668
+ mapId: initialMapId,
669
+ phaseId: initialPhaseId,
555
670
  selectedNode: pluginState.currentNode || null,
556
671
  selectedEdge: null,
557
- overrides: {},
672
+ overrides: storedOverridesForView(preferences, initialMapId, initialPhaseId, initialDeckId),
673
+ probabilityFormat: preferences.probabilityFormat === 'decimal' ? 'decimal' : 'percent',
674
+ showEstimatedAir: typeof preferences.showEstimatedAir === 'boolean' ? preferences.showEstimatedAir : true,
675
+ showEnemyAvatars: typeof preferences.showEnemyAvatars === 'boolean' ? preferences.showEnemyAvatars : true,
558
676
  exportOpen: false,
559
- exportRoute: null,
560
- exportEnemies: {},
561
- exportFormations: {},
677
+ ...initialExportPreferences,
562
678
  exportError: null,
563
679
  exportBusy: false,
564
680
  }
@@ -582,6 +698,42 @@ class Compass extends React.Component {
582
698
  this.refresh()
583
699
  }
584
700
 
701
+ componentDidUpdate() {
702
+ this.persistPreferences()
703
+ }
704
+
705
+ persistPreferences() {
706
+ const storage = compassStorage()
707
+ if (!storage) return
708
+ try {
709
+ const previous = readCompassPreferences()
710
+ const overridesByView = isRecord(previous.overridesByView) ? { ...previous.overridesByView } : {}
711
+ overridesByView[viewPreferenceKey(this.state.mapId, this.state.phaseId, this.state.deckId)] = normalizeOverrides(this.state.overrides)
712
+ const exportByView = isRecord(previous.exportByView) ? { ...previous.exportByView } : {}
713
+ exportByView[viewPreferenceKey(this.state.mapId, this.state.phaseId, this.state.deckId)] = {
714
+ route: typeof this.state.exportRoute === 'string' ? this.state.exportRoute : null,
715
+ enemies: normalizeStringMap(this.state.exportEnemies),
716
+ formations: normalizeNumberMap(this.state.exportFormations),
717
+ }
718
+ const phaseByMap = isRecord(previous.phaseByMap) ? { ...previous.phaseByMap } : {}
719
+ phaseByMap[this.state.mapId] = this.state.phaseId
720
+ storage.setItem(PREFERENCES_STORAGE_KEY, JSON.stringify({
721
+ ...previous,
722
+ mapId: this.state.mapId,
723
+ phaseId: this.state.phaseId,
724
+ phaseByMap,
725
+ deckId: this.state.deckId,
726
+ probabilityFormat: this.state.probabilityFormat,
727
+ showEstimatedAir: this.state.showEstimatedAir,
728
+ showEnemyAvatars: this.state.showEnemyAvatars,
729
+ overridesByView,
730
+ exportByView,
731
+ }))
732
+ } catch (_) {
733
+ // Preferences are optional; a restricted localStorage must not break the plugin.
734
+ }
735
+ }
736
+
585
737
  componentWillUnmount() {
586
738
  listeners.delete(this.refresh)
587
739
  this.unsubscribeStore?.()
@@ -589,15 +741,16 @@ class Compass extends React.Component {
589
741
  }
590
742
 
591
743
  selectMap(mapId) {
744
+ const preferences = readCompassPreferences()
745
+ const phaseId = phaseOptionForMap(mapId, preferences.phaseByMap?.[mapId])?.id || null
746
+ const exportPreferences = storedExportPreferencesForView(preferences, mapId, phaseId, this.state.deckId)
592
747
  this.setState({
593
748
  mapId,
594
- phaseId: phaseOptionsForMap(mapId)[0]?.id || null,
749
+ phaseId,
595
750
  selectedNode: null,
596
751
  selectedEdge: null,
597
- overrides: {},
598
- exportRoute: null,
599
- exportEnemies: {},
600
- exportFormations: {},
752
+ overrides: storedOverridesForView(preferences, mapId, phaseId, this.state.deckId),
753
+ ...exportPreferences,
601
754
  exportError: null,
602
755
  })
603
756
  }
@@ -641,7 +794,10 @@ class Compass extends React.Component {
641
794
  }
642
795
 
643
796
  selectPhase(phaseId) {
644
- this.setState({ phaseId, selectedNode: null, selectedEdge: null, overrides: {}, exportRoute: null, exportEnemies: {}, exportFormations: {}, exportError: null })
797
+ const preferences = readCompassPreferences()
798
+ const overrides = storedOverridesForView(preferences, this.state.mapId, phaseId, this.state.deckId)
799
+ const exportPreferences = storedExportPreferencesForView(preferences, this.state.mapId, phaseId, this.state.deckId)
800
+ this.setState({ phaseId, selectedNode: null, selectedEdge: null, overrides, ...exportPreferences, exportError: null })
645
801
  }
646
802
 
647
803
  selectNode(node) {
@@ -649,7 +805,10 @@ class Compass extends React.Component {
649
805
  }
650
806
 
651
807
  selectFleet(deckId) {
652
- this.setState({ deckId, selectedNode: null, selectedEdge: null, overrides: {}, exportRoute: null, exportEnemies: {}, exportFormations: {}, exportError: null })
808
+ const preferences = readCompassPreferences()
809
+ const overrides = storedOverridesForView(preferences, this.state.mapId, this.state.phaseId, deckId)
810
+ const exportPreferences = storedExportPreferencesForView(preferences, this.state.mapId, this.state.phaseId, deckId)
811
+ this.setState({ deckId, selectedNode: null, selectedEdge: null, overrides, ...exportPreferences, exportError: null })
653
812
  }
654
813
 
655
814
  renderLosIcons(scores) {
@@ -684,6 +843,35 @@ class Compass extends React.Component {
684
843
  this.setState({ selectedEdge: edgeId })
685
844
  }
686
845
 
846
+ renderNodeEnemyAvatar(ship, rootState, x, y, size) {
847
+ if (!ship?.id) return null
848
+ const name = ship.name || '敌舰'
849
+ return h('foreignObject', {
850
+ className: 'compass-node-overlay compass-node-enemy-avatar',
851
+ x,
852
+ y,
853
+ width: size,
854
+ height: size,
855
+ 'aria-label': `最常见敌方旗舰:${name}`,
856
+ }, h('div', { className: 'compass-node-overlay-content' }, this.renderShipPortrait(ship.id, name, rootState, size)))
857
+ }
858
+
859
+ renderNodeIcon(marker, centerX, centerY, nodeType) {
860
+ if (!marker?.url) return null
861
+ const size = NODE_ICON_SIZE_BY_TYPE[nodeType] || NODE_ICON_SIZE
862
+ const offsetY = NODE_ICON_OFFSET_Y_BY_TYPE[nodeType] || 0
863
+ return h('image', {
864
+ className: `compass-node-icon is-${marker.source}`,
865
+ href: marker.url,
866
+ x: centerX - size / 2,
867
+ y: centerY + offsetY - size / 2,
868
+ width: size,
869
+ height: size,
870
+ preserveAspectRatio: 'xMidYMid meet',
871
+ 'aria-hidden': 'true',
872
+ })
873
+ }
874
+
687
875
  renderMap(mapDefinition, geometry, evaluation, selectedNode, currentNode, passedNodes = [], mapId = null) {
688
876
  if (!mapGeometryAvailable(mapDefinition, geometry)) {
689
877
  return h('div', { className: 'compass-empty-panel' }, 'poi 地图数据不可用')
@@ -693,7 +881,8 @@ class Compass extends React.Component {
693
881
  const edges = routeEdges(geometry)
694
882
  const passedEdges = passedEdgeKeys(passedNodes)
695
883
  const nodeTypes = localNodeTypesForMap(mapId, mapDefinition)
696
- const airPower = calculatePoiFleetStat(getRootState(), this.state.deckId, 1, true)
884
+ const rootState = getRootState()
885
+ const airPower = calculatePoiFleetStat(rootState, this.state.deckId, 1, true)
697
886
  const width = Math.max(1200, ...Object.values(positions).map((position) => position.x + 100))
698
887
  const height = Math.max(720, ...Object.values(positions).map((position) => position.y + 100))
699
888
  return h(
@@ -717,6 +906,7 @@ class Compass extends React.Component {
717
906
  const to = positions[edge.to]
718
907
  if (!from || !to) return null
719
908
  const probability = edgeProbability(evaluation, edge.from, edge.to)
909
+ const edgeVisual = edgeVisualStyle(probability.global)
720
910
  const manualOutcomes = evaluation.decisions[edge.from]?.manualOutcomes
721
911
  || evaluation.decisions[edge.from]?.baseOutcomes
722
912
  || []
@@ -727,7 +917,6 @@ class Compass extends React.Component {
727
917
  'compass-edge',
728
918
  probability.global != null ? 'is-reachable' : '',
729
919
  probability.estimated ? 'is-estimated' : '',
730
- probabilityTier(probability.global),
731
920
  passedEdges.has(`${edge.from}->${edge.to}`) ? 'is-passed' : '',
732
921
  manualChoice ? 'is-manual-choice' : '',
733
922
  selected ? 'is-selected' : '',
@@ -745,6 +934,11 @@ class Compass extends React.Component {
745
934
  role: 'button',
746
935
  tabIndex: 0,
747
936
  'aria-label': `边 ID ${edge.cell}:${edge.from}→${edge.to}`,
937
+ style: {
938
+ '--compass-edge-color': edgeVisual.color,
939
+ '--compass-edge-width': edgeVisual.width,
940
+ '--compass-edge-opacity': edgeVisual.opacity,
941
+ },
748
942
  onClick: () => manualChoice
749
943
  ? this.setManualOverride(edge.from, edge.to, edge.cell)
750
944
  : this.selectEdge(edge.cell),
@@ -754,7 +948,7 @@ class Compass extends React.Component {
754
948
  `边 ID ${edge.cell}:${edge.from} → ${edge.to}`,
755
949
  manualChoice ? '点击选择方向' : null,
756
950
  probability.global != null && Number(probability.global) > 0
757
- ? `局部 ${formatPercent(probability.local, probability.estimated)}`
951
+ ? `局部 ${formatProbability(probability.local, probability.estimated, this.state.probabilityFormat)}`
758
952
  : null,
759
953
  ].filter(Boolean).join(' · ')),
760
954
  h('line', {
@@ -763,13 +957,13 @@ class Compass extends React.Component {
763
957
  x2: to.x,
764
958
  y2: to.y,
765
959
  }),
766
- probability.global != null && Number(probability.global) > 0
960
+ probability.global != null && Number(probability.global) > 0 && Math.round(Number(probability.global) * 100) > 0
767
961
  ? h('text', {
768
962
  x: (from.x + to.x) / 2,
769
963
  y: (from.y + to.y) / 2,
770
964
  dominantBaseline: 'middle',
771
965
  className: 'compass-edge-label',
772
- }, formatPercent(probability.global, probability.estimated))
966
+ }, formatProbability(probability.global, probability.estimated, this.state.probabilityFormat))
773
967
  : null,
774
968
  selected
775
969
  ? h('text', {
@@ -787,10 +981,20 @@ class Compass extends React.Component {
787
981
  ? kcnavNodeCatalog['-1']
788
982
  : kcnavNodeCatalog[String(nodeType)] || kcnavNodeCatalog['-1']
789
983
  const visual = nodeVisualStyle(nodeInfo)
790
- const airState = evaluation.probability.reach[node] > 0 && [4, 5, 7, 10, 15].includes(nodeType)
791
- ? airStateRange(airPower, loadEnemies(mapId, node)) : null
984
+ const marker = nodeMarker(nodeType, nodeInfo)
985
+ const iconOnly = ICON_ONLY_NODE_TYPES.has(nodeType)
986
+ const enemyData = (COMBAT_NODE_TYPES.has(nodeType) || AIR_NODE_TYPES.has(nodeType) || AIR_STATUS_NODE_TYPES.has(nodeType))
987
+ ? loadEnemies(mapId, node) : null
988
+ const commonFlagship = this.state.showEnemyAvatars
989
+ && (COMBAT_NODE_TYPES.has(nodeType) || AIR_NODE_TYPES.has(nodeType))
990
+ && enemyData?.status === 'ready'
991
+ ? enemyData.rows?.[0]?.mainFleet?.[0] : null
992
+ const airState = evaluation.probability.reach[node] > 0 && AIR_STATUS_NODE_TYPES.has(nodeType)
993
+ ? airStateRange(airPower, enemyData) : null
994
+ const displayAirState = this.state.showEstimatedAir ? airState : null
792
995
  const centerX = position.x + visual.offsetX
793
996
  const centerY = position.y + visual.offsetY
997
+ const overlaySize = Math.min(20, Math.max(12, Math.round(Math.min(visual.rx, visual.ry))))
794
998
  const manualSource = manualSourceForTarget(evaluation, node)
795
999
  const manualTarget = manualSource && manualSource !== node
796
1000
  const classNames = [
@@ -816,7 +1020,7 @@ class Compass extends React.Component {
816
1020
  onKeyDown: activateNode,
817
1021
  },
818
1022
  h('title', null, nodeInfo?.label ? `${node} · ${nodeInfo.label}` : node),
819
- h('ellipse', {
1023
+ iconOnly ? null : h('ellipse', {
820
1024
  className: 'compass-node-shape',
821
1025
  cx: centerX,
822
1026
  cy: centerY,
@@ -827,7 +1031,7 @@ class Compass extends React.Component {
827
1031
  strokeWidth: visual.stroke,
828
1032
  opacity: visual.opacity,
829
1033
  }),
830
- visual.simpleBorder
1034
+ !iconOnly && visual.simpleBorder
831
1035
  ? h('ellipse', {
832
1036
  className: 'compass-node-simple-border',
833
1037
  cx: centerX,
@@ -839,9 +1043,10 @@ class Compass extends React.Component {
839
1043
  opacity: visual.opacity,
840
1044
  })
841
1045
  : null,
1046
+ this.renderNodeIcon(marker, centerX, centerY, nodeType),
842
1047
  h('text', {
843
1048
  x: centerX,
844
- y: centerY + visual.textOffsetY,
1049
+ y: centerY + (iconOnly ? 0 : visual.textOffsetY),
845
1050
  dominantBaseline: 'middle',
846
1051
  className: 'compass-node-label',
847
1052
  style: {
@@ -851,11 +1056,32 @@ class Compass extends React.Component {
851
1056
  fontSize: visual.fontSize,
852
1057
  },
853
1058
  }, node),
854
- airState ? h('text', { x: centerX, y: centerY + visual.ry + 21, className: 'compass-node-air-state', textAnchor: 'middle' },
1059
+ commonFlagship ? this.renderNodeEnemyAvatar(
1060
+ commonFlagship,
1061
+ rootState,
1062
+ centerX + visual.rx * 0.7 - overlaySize / 2,
1063
+ centerY + visual.ry * 0.7 - overlaySize / 2,
1064
+ overlaySize,
1065
+ ) : null,
1066
+ displayAirState ? h('text', {
1067
+ x: centerX + visual.rx * 0.85,
1068
+ y: centerY - visual.ry * 0.85,
1069
+ className: 'compass-node-air-state',
1070
+ textAnchor: 'middle',
1071
+ dominantBaseline: 'middle',
1072
+ },
855
1073
  h('title', null, `当前舰队制空 ${airPower.min}~${airPower.max};未计沿途损耗、陆航和支援${airState.uncertain ? ';敌编成数据不完整或不确定' : ''}`),
856
- ...airState.states.flatMap((state, index) => [
1074
+ ...displayAirState.states.flatMap((state, index) => [
857
1075
  index ? h('tspan', { key: 'separator', fill: '#ffffff' }, '/') : null,
858
- h('tspan', { key: state.label, fill: state.color }, state.label),
1076
+ h('tspan', {
1077
+ key: `${state.label}-${index}`,
1078
+ fill: '#ffffff',
1079
+ style: {
1080
+ fill: '#ffffff',
1081
+ textShadow: `1px 1px 0 ${state.shadow}`,
1082
+ filter: `drop-shadow(1px 1px 0 ${state.shadow})`,
1083
+ },
1084
+ }, state.label),
859
1085
  ]), airState.uncertain ? '?' : null) : null,
860
1086
  )
861
1087
  }),
@@ -909,7 +1135,7 @@ class Compass extends React.Component {
909
1135
  route ? h('label', { className: 'compass-export-route' }, '路线',
910
1136
  h('select', { value: route.key, 'aria-label': '导出路线', onChange: (event) => this.setState({ exportRoute: event.target.value, exportError: null }) },
911
1137
  ...choices.routes.map((candidate) => h('option', { key: candidate.key, value: candidate.key },
912
- `${candidate.nodes.join('→')}${candidate.boss ? ' · Boss' : ''} · ${candidate.probability == null ? '含能动选路' : formatPercent(candidate.probability, candidate.estimated)}`))),
1138
+ `${candidate.nodes.join('→')}${candidate.boss ? ' · Boss' : ''} · ${candidate.probability == null ? '含能动选路' : formatProbability(candidate.probability, candidate.estimated, this.state.probabilityFormat)}`))),
913
1139
  ) : null,
914
1140
  choices?.blocked.length ? h('div', { className: 'compass-warnings' }, `未知分歧:${choices.blocked.join('、')}`) : null,
915
1141
  battles?.length ? h('div', { className: 'compass-export-battles' },
@@ -1024,7 +1250,9 @@ class Compass extends React.Component {
1024
1250
  const displayedOutcomes = active && (decision?.matchedRuleIds?.length || 0) <= 1
1025
1251
  ? decision.outcomes
1026
1252
  : ruleOutcomes
1027
- const outcomes = displayedOutcomes.length ? displayedOutcomes.map(outcomeText).join(' / ') : '—'
1253
+ const outcomes = displayedOutcomes.length
1254
+ ? displayedOutcomes.map((outcome) => outcomeText(outcome, this.state.probabilityFormat)).join(' / ')
1255
+ : '—'
1028
1256
  const rawLabel = predicateLabel(rule.predicate)
1029
1257
  const label = visibleRuleLabel(rule)
1030
1258
  const notes = rulePopoverNotes(rule, rawLabel, label)
@@ -1114,6 +1342,35 @@ class Compass extends React.Component {
1114
1342
  }, option.label)),
1115
1343
  )
1116
1344
  : null,
1345
+ h('div', { className: 'compass-option-group', role: 'group', 'aria-label': '概率标签格式' },
1346
+ h('span', { className: 'compass-option-label' }, '概率'),
1347
+ ...[
1348
+ ['percent', '%'],
1349
+ ['decimal', '小数'],
1350
+ ].map(([value, label]) => h('button', {
1351
+ key: value,
1352
+ type: 'button',
1353
+ className: this.state.probabilityFormat === value ? 'is-active' : '',
1354
+ 'aria-pressed': this.state.probabilityFormat === value,
1355
+ onClick: () => this.setState({ probabilityFormat: value }),
1356
+ }, label)),
1357
+ ),
1358
+ h('label', { className: 'compass-option-toggle' },
1359
+ h('input', {
1360
+ type: 'checkbox',
1361
+ checked: this.state.showEstimatedAir,
1362
+ onChange: (event) => this.setState({ showEstimatedAir: event.target.checked }),
1363
+ }),
1364
+ h('span', null, '推测制空'),
1365
+ ),
1366
+ h('label', { className: 'compass-option-toggle' },
1367
+ h('input', {
1368
+ type: 'checkbox',
1369
+ checked: this.state.showEnemyAvatars,
1370
+ onChange: (event) => this.setState({ showEnemyAvatars: event.target.checked }),
1371
+ }),
1372
+ h('span', null, '敌舰头像'),
1373
+ ),
1117
1374
  h('span', {
1118
1375
  className: 'compass-toolbar-fleet',
1119
1376
  title: `舰队 ${this.state.deckId}:${fleetSummary(context)};${mapLosText}`,
@@ -1152,6 +1409,8 @@ exports.pluginWillUnload = stopPlugin
1152
1409
  exports.__test = {
1153
1410
  calculatePoiLos33,
1154
1411
  cellFromDetail,
1412
+ edgeVisualStyle,
1413
+ formatProbability,
1155
1414
  handleGameResponse,
1156
1415
  kcnavNodeTypesFromMap,
1157
1416
  localNodeTypesForMap,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poi-plugin-compass",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "通常海域舰队罗盘",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -52,7 +52,7 @@
52
52
  "icon": "fa/compass",
53
53
  "priority": 20,
54
54
  "apiVer": {
55
- "11.99.99": "0.1.3"
55
+ "11.99.99": "0.1.4"
56
56
  }
57
57
  },
58
58
  "peerDependencies": {