@sayue_ltr/fleq 1.50.1 → 2.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 (99) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/README.md +42 -6
  3. package/dist/config.js +37 -4
  4. package/dist/dmdata/rest-client.js +58 -3
  5. package/dist/dmdata/telegram-parser.js +115 -64
  6. package/dist/dmdata/ws-client.js +49 -18
  7. package/dist/engine/cli/cli-run.js +88 -3
  8. package/dist/engine/cli/cli.js +12 -0
  9. package/dist/engine/eew/eew-tracker.js +41 -15
  10. package/dist/engine/filter/compile-filter.js +21 -0
  11. package/dist/engine/filter/compiler.js +188 -0
  12. package/dist/engine/filter/errors.js +41 -0
  13. package/dist/engine/filter/field-registry.js +78 -0
  14. package/dist/engine/filter/index.js +15 -0
  15. package/dist/engine/filter/parser.js +137 -0
  16. package/dist/engine/filter/rank-maps.js +34 -0
  17. package/dist/engine/filter/tokenizer.js +121 -0
  18. package/dist/engine/filter/type-checker.js +104 -0
  19. package/dist/engine/filter/types.js +2 -0
  20. package/dist/engine/filter-template/pipeline-controller.js +73 -0
  21. package/dist/engine/filter-template/pipeline.js +16 -0
  22. package/dist/engine/messages/display-callbacks.js +7 -0
  23. package/dist/engine/messages/message-router.js +114 -182
  24. package/dist/engine/messages/summary-tracker.js +106 -0
  25. package/dist/engine/messages/telegram-stats.js +103 -0
  26. package/dist/engine/messages/volcano-route-handler.js +122 -0
  27. package/dist/engine/monitor/monitor.js +52 -4
  28. package/dist/engine/monitor/shutdown.js +1 -0
  29. package/dist/engine/notification/notifier.js +21 -4
  30. package/dist/engine/notification/sound-player.js +398 -36
  31. package/dist/engine/presentation/diff-store.js +158 -0
  32. package/dist/engine/presentation/diff-types.js +2 -0
  33. package/dist/engine/presentation/events/from-earthquake.js +53 -0
  34. package/dist/engine/presentation/events/from-eew.js +72 -0
  35. package/dist/engine/presentation/events/from-lg-observation.js +58 -0
  36. package/dist/engine/presentation/events/from-nankai-trough.js +39 -0
  37. package/dist/engine/presentation/events/from-raw.js +35 -0
  38. package/dist/engine/presentation/events/from-seismic-text.js +37 -0
  39. package/dist/engine/presentation/events/from-tsunami.js +51 -0
  40. package/dist/engine/presentation/events/from-volcano.js +88 -0
  41. package/dist/engine/presentation/events/to-presentation-event.js +32 -0
  42. package/dist/engine/presentation/level-helpers.js +118 -0
  43. package/dist/engine/presentation/processors/process-earthquake.js +36 -0
  44. package/dist/engine/presentation/processors/process-eew.js +105 -0
  45. package/dist/engine/presentation/processors/process-lg-observation.js +30 -0
  46. package/dist/engine/presentation/processors/process-message.js +53 -0
  47. package/dist/engine/presentation/processors/process-nankai-trough.js +30 -0
  48. package/dist/engine/presentation/processors/process-raw.js +22 -0
  49. package/dist/engine/presentation/processors/process-seismic-text.js +30 -0
  50. package/dist/engine/presentation/processors/process-tsunami.js +42 -0
  51. package/dist/engine/presentation/processors/process-volcano.js +41 -0
  52. package/dist/engine/presentation/types.js +2 -0
  53. package/dist/engine/startup/config-resolver.js +2 -0
  54. package/dist/engine/template/compile-template.js +18 -0
  55. package/dist/engine/template/compiler.js +105 -0
  56. package/dist/engine/template/field-accessor.js +31 -0
  57. package/dist/engine/template/filters.js +100 -0
  58. package/dist/engine/template/index.js +5 -0
  59. package/dist/engine/template/parser.js +185 -0
  60. package/dist/engine/template/tokenizer.js +96 -0
  61. package/dist/engine/template/types.js +2 -0
  62. package/dist/types.js +3 -2
  63. package/dist/ui/display-adapter.js +60 -0
  64. package/dist/ui/earthquake-formatter.js +22 -5
  65. package/dist/ui/eew-formatter.js +25 -10
  66. package/dist/ui/formatter.js +116 -32
  67. package/dist/ui/minimap/grid-layout.js +91 -0
  68. package/dist/ui/minimap/index.js +16 -0
  69. package/dist/ui/minimap/minimap-renderer.js +277 -0
  70. package/dist/ui/minimap/pref-mapping.js +82 -0
  71. package/dist/ui/minimap/types.js +2 -0
  72. package/dist/ui/night-overlay.js +56 -0
  73. package/dist/ui/repl-handlers/command-definitions.js +327 -0
  74. package/dist/ui/repl-handlers/index.js +11 -0
  75. package/dist/ui/repl-handlers/info-handlers.js +633 -0
  76. package/dist/ui/repl-handlers/operation-handlers.js +233 -0
  77. package/dist/ui/repl-handlers/settings-handlers.js +927 -0
  78. package/dist/ui/repl-handlers/types.js +10 -0
  79. package/dist/ui/repl.js +81 -1752
  80. package/dist/ui/statistics-formatter.js +258 -0
  81. package/dist/ui/status-line.js +80 -0
  82. package/dist/ui/summary/index.js +5 -0
  83. package/dist/ui/summary/summary-line.js +18 -0
  84. package/dist/ui/summary/summary-model.js +31 -0
  85. package/dist/ui/summary/token-builders.js +317 -0
  86. package/dist/ui/summary/types.js +2 -0
  87. package/dist/ui/summary/width-fit.js +41 -0
  88. package/dist/ui/summary-interval-formatter.js +72 -0
  89. package/dist/ui/test-samples.js +6 -0
  90. package/dist/ui/theme.js +43 -5
  91. package/dist/ui/tip-shuffler.js +81 -0
  92. package/dist/ui/volcano-formatter.js +15 -13
  93. package/dist/ui/waiting-tips-eew.js +63 -0
  94. package/dist/ui/waiting-tips-info-systems.js +81 -0
  95. package/dist/ui/waiting-tips-seismology.js +97 -0
  96. package/dist/ui/waiting-tips-tsunami.js +72 -0
  97. package/dist/ui/waiting-tips-weather.js +189 -0
  98. package/dist/ui/waiting-tips.js +420 -249
  99. package/package.json +1 -1
@@ -61,10 +61,13 @@ exports.visualPadEnd = visualPadEnd;
61
61
  exports.renderFrameTable = renderFrameTable;
62
62
  exports.wrapFrameLines = wrapFrameLines;
63
63
  exports.wrapTextLines = wrapTextLines;
64
+ exports.renderGroupedItemList = renderGroupedItemList;
65
+ exports.renderSimpleNameList = renderSimpleNameList;
64
66
  exports.collectHighlightSpans = collectHighlightSpans;
65
67
  exports.highlightAndWrap = highlightAndWrap;
66
68
  exports.formatTimestamp = formatTimestamp;
67
69
  exports.formatElapsedTime = formatElapsedTime;
70
+ exports.formatUptime = formatUptime;
68
71
  exports.intensityColor = intensityColor;
69
72
  exports.lgIntensityColor = lgIntensityColor;
70
73
  exports.lgIntToNumeric = lgIntToNumeric;
@@ -311,30 +314,28 @@ function sanitizeForTerminal(str) {
311
314
  // eslint-disable-next-line no-control-regex
312
315
  return stripAnsi(str).replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
313
316
  }
317
+ /** コードポイントが CJK 等の全角文字かどうかを判定する */
318
+ function isWideChar(cp) {
319
+ return ((cp >= 0x1100 && cp <= 0x115F) || // Hangul Jamo
320
+ (cp >= 0x2E80 && cp <= 0x303E) || // CJK部首・記号
321
+ (cp >= 0x3041 && cp <= 0x33BF) || // ひらがな・カタカナ・CJK互換
322
+ (cp >= 0x3400 && cp <= 0x4DBF) || // CJK統合漢字拡張A
323
+ (cp >= 0x4E00 && cp <= 0xA4CF) || // CJK統合漢字 + Yi
324
+ (cp >= 0xAC00 && cp <= 0xD7AF) || // Hangul Syllables
325
+ (cp >= 0xF900 && cp <= 0xFAFF) || // CJK互換漢字
326
+ (cp >= 0xFE30 && cp <= 0xFE4F) || // CJK互換形
327
+ (cp >= 0xFF01 && cp <= 0xFF60) || // 全角ASCII・半角カタカナ
328
+ (cp >= 0xFFE0 && cp <= 0xFFE6) || // 全角記号
329
+ (cp >= 0x20000 && cp <= 0x2FA1F) // CJK統合漢字拡張B-F
330
+ );
331
+ }
314
332
  /** 文字列の視覚的な幅を計算(全角文字を2として数える) */
315
333
  function visualWidth(str) {
316
334
  const plain = stripAnsi(str);
317
335
  let width = 0;
318
336
  for (const ch of plain) {
319
337
  const cp = ch.codePointAt(0) ?? 0;
320
- // CJK統合漢字、ひらがな、カタカナ、全角記号、全角括弧等
321
- if ((cp >= 0x1100 && cp <= 0x115F) || // Hangul Jamo
322
- (cp >= 0x2E80 && cp <= 0x303E) || // CJK部首・記号
323
- (cp >= 0x3041 && cp <= 0x33BF) || // ひらがな・カタカナ・CJK互換
324
- (cp >= 0x3400 && cp <= 0x4DBF) || // CJK統合漢字拡張A
325
- (cp >= 0x4E00 && cp <= 0xA4CF) || // CJK統合漢字 + Yi
326
- (cp >= 0xAC00 && cp <= 0xD7AF) || // Hangul Syllables
327
- (cp >= 0xF900 && cp <= 0xFAFF) || // CJK互換漢字
328
- (cp >= 0xFE30 && cp <= 0xFE4F) || // CJK互換形
329
- (cp >= 0xFF01 && cp <= 0xFF60) || // 全角ASCII・半角カタカナ
330
- (cp >= 0xFFE0 && cp <= 0xFFE6) || // 全角記号
331
- (cp >= 0x20000 && cp <= 0x2FA1F) // CJK統合漢字拡張B-F
332
- ) {
333
- width += 2;
334
- }
335
- else {
336
- width += 1;
337
- }
338
+ width += isWideChar(cp) ? 2 : 1;
338
339
  }
339
340
  return width;
340
341
  }
@@ -467,20 +468,7 @@ function wrapTextLines(text, maxWidth) {
467
468
  let currentWidth = 0;
468
469
  for (const ch of text) {
469
470
  const cp = ch.codePointAt(0) ?? 0;
470
- let charWidth = 1;
471
- if ((cp >= 0x1100 && cp <= 0x115F) ||
472
- (cp >= 0x2E80 && cp <= 0x303E) ||
473
- (cp >= 0x3041 && cp <= 0x33BF) ||
474
- (cp >= 0x3400 && cp <= 0x4DBF) ||
475
- (cp >= 0x4E00 && cp <= 0xA4CF) ||
476
- (cp >= 0xAC00 && cp <= 0xD7AF) ||
477
- (cp >= 0xF900 && cp <= 0xFAFF) ||
478
- (cp >= 0xFE30 && cp <= 0xFE4F) ||
479
- (cp >= 0xFF01 && cp <= 0xFF60) ||
480
- (cp >= 0xFFE0 && cp <= 0xFFE6) ||
481
- (cp >= 0x20000 && cp <= 0x2FA1F)) {
482
- charWidth = 2;
483
- }
471
+ const charWidth = isWideChar(cp) ? 2 : 1;
484
472
  if (currentWidth + charWidth > maxWidth) {
485
473
  lines.push(currentLine);
486
474
  currentLine = ch;
@@ -496,6 +484,54 @@ function wrapTextLines(text, maxWidth) {
496
484
  }
497
485
  return lines;
498
486
  }
487
+ /**
488
+ * 震度別・階級別にグループ化された項目リストを描画する。
489
+ * 各グループは `prefix + item, item, ...` 形式で、2行目以降は prefix 幅分のハンギングインデント。
490
+ * badges は primary の直後に連結される(先頭スペースは呼び出し側で付与済み想定)。
491
+ */
492
+ function renderGroupedItemList(options) {
493
+ const { level, width, groups, itemSeparator = ", " } = options;
494
+ const out = options.buf
495
+ ? (line) => options.buf.push(line)
496
+ : (line) => console.log(line);
497
+ for (const group of groups) {
498
+ if (group.items.length === 0)
499
+ continue;
500
+ const prefix = group.prefix;
501
+ const indentWidth = visualWidth(stripAnsi(prefix));
502
+ // 各項目を "primary + badges" 形式の文字列に組み立て
503
+ const itemTexts = group.items.map((item) => {
504
+ let text = item.primary;
505
+ if (item.badges != null && item.badges.length > 0) {
506
+ text += item.badges.join("");
507
+ }
508
+ return text;
509
+ });
510
+ const content = prefix + itemTexts.join(itemSeparator);
511
+ for (const line of wrapFrameLines(level, content, width, indentWidth)) {
512
+ out(line);
513
+ }
514
+ }
515
+ }
516
+ /**
517
+ * 単純な名前リストを描画する。
518
+ * `label + name, name, ...` 形式。2行目以降は label 幅分のハンギングインデント。
519
+ * label 指定時は ` label ` の形式(先頭・末尾にスペース)で gray 表示される。
520
+ */
521
+ function renderSimpleNameList(options) {
522
+ const { level, width, items, label, separator = ", " } = options;
523
+ if (items.length === 0)
524
+ return;
525
+ const out = options.buf
526
+ ? (line) => options.buf.push(line)
527
+ : (line) => console.log(line);
528
+ const prefix = label ? ` ${chalk_1.default.gray(label)} ` : "";
529
+ const indentWidth = label ? visualWidth(stripAnsi(prefix)) : 0;
530
+ const content = prefix + items.join(separator);
531
+ for (const line of wrapFrameLines(level, content, width, indentWidth)) {
532
+ out(line);
533
+ }
534
+ }
499
535
  /** 元の行からマッチspanを収集する */
500
536
  function collectHighlightSpans(line, rules) {
501
537
  const spans = [];
@@ -584,6 +620,54 @@ function formatElapsedTime(ms) {
584
620
  const ss = totalSec % 60;
585
621
  return `${String(hh).padStart(2, "0")}:${String(mm).padStart(2, "0")}:${String(ss).padStart(2, "0")}`;
586
622
  }
623
+ /**
624
+ * 稼働時間を "DDD:HH:MM:SS" 形式に整形する。
625
+ * 未使用の上位ゼロ桁は dim (gray) 表示にして画面ヤケを軽減する。
626
+ * 日数が 3 桁を超える場合は桁数が増える。
627
+ */
628
+ function formatUptime(ms) {
629
+ const safeMs = Math.max(0, ms);
630
+ const totalSec = Math.floor(safeMs / 1000);
631
+ const dd = Math.floor(totalSec / 86400);
632
+ const hh = Math.floor((totalSec % 86400) / 3600);
633
+ const mm = Math.floor((totalSec % 3600) / 60);
634
+ const ss = totalSec % 60;
635
+ const ddStr = String(dd).padStart(3, "0");
636
+ const hhStr = String(hh).padStart(2, "0");
637
+ const mmStr = String(mm).padStart(2, "0");
638
+ const ssStr = String(ss).padStart(2, "0");
639
+ const raw = `${ddStr}:${hhStr}:${mmStr}:${ssStr}`;
640
+ // セグメント境界: DDD(0-2) : HH(4-5) : MM(7-8) : SS(10-11)
641
+ // segStarts は dd < 1000 (3桁) の通常ケース用。
642
+ // dd >= 1000 では firstNonZero=0 → activeSegStart=0 で全体 white になるため問題なし。
643
+ const segStarts = [0, 4, 7, 10];
644
+ // 先頭から連続するゼロと区切り文字をスキャン
645
+ let firstNonZero = -1;
646
+ for (let i = 0; i < raw.length; i++) {
647
+ if (raw[i] !== "0" && raw[i] !== ":") {
648
+ firstNonZero = i;
649
+ break;
650
+ }
651
+ }
652
+ // dim 境界を決定
653
+ // - 日部分 (index 0-2): 文字レベルで先頭ゼロをグレーアウト
654
+ // - 時刻部分 (HH/MM/SS): セグメント境界でグレーアウト
655
+ // - 全桁ゼロ: SS セグメントから通常表示
656
+ let dimEnd;
657
+ if (firstNonZero === -1) {
658
+ dimEnd = segStarts[segStarts.length - 1];
659
+ }
660
+ else if (firstNonZero <= 2) {
661
+ dimEnd = firstNonZero;
662
+ }
663
+ else {
664
+ dimEnd = segStarts.filter((s) => s <= firstNonZero).pop();
665
+ }
666
+ if (dimEnd === 0) {
667
+ return chalk_1.default.white(raw);
668
+ }
669
+ return chalk_1.default.gray(raw.slice(0, dimEnd)) + chalk_1.default.white(raw.slice(dimEnd));
670
+ }
587
671
  /** 区切り線 (後方互換用、新コードではフレーム関数を使用) */
588
672
  function separator(char = "─", len = 60) {
589
673
  return chalk_1.default.gray(char.repeat(len));
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ALL_PREF_IDS = exports.PREF_PLACEMENTS = exports.GRID_COLS = exports.GRID_ROWS = void 0;
4
+ exports.GRID_ROWS = 12;
5
+ exports.GRID_COLS = 13;
6
+ /** Helper to create a placement */
7
+ function p(id, cells, anchor) {
8
+ return {
9
+ id,
10
+ cells: cells.map(([row, col]) => ({ row, col })),
11
+ anchor: { row: anchor[0], col: anchor[1] },
12
+ };
13
+ }
14
+ /**
15
+ * All 47 prefecture placements on the 12×13 grid.
16
+ *
17
+ * Grid layout (from spec):
18
+ * Col: 0 1 2 3 4 5 6 7 8 9 10 11 12
19
+ * r00: HK *HK HK HK
20
+ * r01: HK HK HK HK *AO AO
21
+ * r02: AK IT
22
+ * r03: YG MG
23
+ * r04: OK *FS FS
24
+ * r05: *IS *NI NI TC IB
25
+ * r06: TM IS *NA GI GU ST
26
+ * r07: SM TT *HG KY *FI FI NA YN TY *CB
27
+ * r08: YA HS OY HG SI *ME AI SZ KN CB
28
+ * r09: NS SG FO OS NR ME
29
+ * r10: KU OI EH KA WA
30
+ * r11: KG MZ *KO KO TK
31
+ */
32
+ exports.PREF_PLACEMENTS = [
33
+ // ── 北海道 (4×2) ──
34
+ p("HK", [[0, 3], [0, 4], [0, 5], [0, 6], [1, 3], [1, 4], [1, 5], [1, 6]], [0, 4]),
35
+ // ── 東北 ──
36
+ p("AO", [[1, 11], [1, 12]], [1, 11]),
37
+ p("AK", [[2, 11]], [2, 11]),
38
+ p("IT", [[2, 12]], [2, 12]),
39
+ p("YG", [[3, 11]], [3, 11]),
40
+ p("MG", [[3, 12]], [3, 12]),
41
+ p("FS", [[4, 11], [4, 12]], [4, 11]),
42
+ // ── 関東 ──
43
+ p("IB", [[5, 12]], [5, 12]),
44
+ p("TC", [[5, 11]], [5, 11]),
45
+ p("GU", [[6, 11]], [6, 11]),
46
+ p("ST", [[6, 12]], [6, 12]),
47
+ p("CB", [[7, 12], [8, 12]], [7, 12]),
48
+ p("TY", [[7, 11]], [7, 11]),
49
+ p("KN", [[8, 11]], [8, 11]),
50
+ // ── 中部 ──
51
+ p("NI", [[5, 9], [5, 10]], [5, 9]),
52
+ p("TM", [[6, 7]], [6, 7]),
53
+ p("IS", [[5, 8], [6, 8]], [5, 8]),
54
+ p("FI", [[7, 7], [7, 8]], [7, 7]),
55
+ p("YN", [[7, 10]], [7, 10]),
56
+ p("NA", [[6, 9], [7, 9]], [6, 9]),
57
+ p("GI", [[6, 10]], [6, 10]),
58
+ p("SZ", [[8, 10]], [8, 10]),
59
+ p("AI", [[8, 9]], [8, 9]),
60
+ // ── 近畿 ──
61
+ p("ME", [[8, 8], [9, 8]], [8, 8]),
62
+ p("SI", [[8, 6]], [8, 6]),
63
+ p("KY", [[7, 6]], [7, 6]),
64
+ p("OS", [[9, 6]], [9, 6]),
65
+ p("HG", [[7, 5], [8, 5]], [7, 5]),
66
+ p("NR", [[9, 7]], [9, 7]),
67
+ p("WA", [[10, 7]], [10, 7]),
68
+ // ── 中国 ──
69
+ p("TT", [[7, 4]], [7, 4]),
70
+ p("SM", [[7, 3]], [7, 3]),
71
+ p("OY", [[8, 4]], [8, 4]),
72
+ p("HS", [[8, 3]], [8, 3]),
73
+ p("YA", [[8, 2]], [8, 2]),
74
+ // ── 四国 ──
75
+ p("TK", [[11, 5]], [11, 5]),
76
+ p("KA", [[10, 5]], [10, 5]),
77
+ p("EH", [[10, 4]], [10, 4]),
78
+ p("KO", [[11, 3], [11, 4]], [11, 3]),
79
+ // ── 九州 ──
80
+ p("FO", [[9, 2]], [9, 2]),
81
+ p("SG", [[9, 1]], [9, 1]),
82
+ p("NS", [[9, 0]], [9, 0]),
83
+ p("KU", [[10, 1]], [10, 1]),
84
+ p("OI", [[10, 2]], [10, 2]),
85
+ p("MZ", [[11, 2]], [11, 2]),
86
+ p("KG", [[11, 1]], [11, 1]),
87
+ // ── 沖縄 ──
88
+ p("OK", [[4, 0]], [4, 0]),
89
+ ];
90
+ /** All prefecture IDs in definition order */
91
+ exports.ALL_PREF_IDS = exports.PREF_PLACEMENTS.map((pp) => pp.id);
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderMinimapForEvent = exports.shouldShowMinimap = exports.buildMinimapCells = exports.renderMinimap = exports.GRID_COLS = exports.GRID_ROWS = exports.ALL_PREF_IDS = exports.PREF_PLACEMENTS = exports.mapAreaToPref = exports.PREF_DEFS = void 0;
4
+ var pref_mapping_1 = require("./pref-mapping");
5
+ Object.defineProperty(exports, "PREF_DEFS", { enumerable: true, get: function () { return pref_mapping_1.PREF_DEFS; } });
6
+ Object.defineProperty(exports, "mapAreaToPref", { enumerable: true, get: function () { return pref_mapping_1.mapAreaToPref; } });
7
+ var grid_layout_1 = require("./grid-layout");
8
+ Object.defineProperty(exports, "PREF_PLACEMENTS", { enumerable: true, get: function () { return grid_layout_1.PREF_PLACEMENTS; } });
9
+ Object.defineProperty(exports, "ALL_PREF_IDS", { enumerable: true, get: function () { return grid_layout_1.ALL_PREF_IDS; } });
10
+ Object.defineProperty(exports, "GRID_ROWS", { enumerable: true, get: function () { return grid_layout_1.GRID_ROWS; } });
11
+ Object.defineProperty(exports, "GRID_COLS", { enumerable: true, get: function () { return grid_layout_1.GRID_COLS; } });
12
+ var minimap_renderer_1 = require("./minimap-renderer");
13
+ Object.defineProperty(exports, "renderMinimap", { enumerable: true, get: function () { return minimap_renderer_1.renderMinimap; } });
14
+ Object.defineProperty(exports, "buildMinimapCells", { enumerable: true, get: function () { return minimap_renderer_1.buildMinimapCells; } });
15
+ Object.defineProperty(exports, "shouldShowMinimap", { enumerable: true, get: function () { return minimap_renderer_1.shouldShowMinimap; } });
16
+ Object.defineProperty(exports, "renderMinimapForEvent", { enumerable: true, get: function () { return minimap_renderer_1.renderMinimapForEvent; } });
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.renderMinimap = renderMinimap;
7
+ exports.buildMinimapCells = buildMinimapCells;
8
+ exports.shouldShowMinimap = shouldShowMinimap;
9
+ exports.renderMinimapForEvent = renderMinimapForEvent;
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const formatter_1 = require("../formatter");
12
+ const grid_layout_1 = require("./grid-layout");
13
+ const pref_mapping_1 = require("./pref-mapping");
14
+ // ── Constants ──
15
+ /** Width of a single cell including trailing space: "AA:xx " = 6 chars */
16
+ const CELL_WIDTH = 6;
17
+ /** Continuation marker for multi-cell prefectures */
18
+ const CONTINUATION = "·····";
19
+ // ── Legend ──
20
+ /** Intensity values in display order */
21
+ const INTENSITY_LEGEND = ["1", "2", "3", "4", "5-", "5+", "6-", "6+", "7"];
22
+ /** Tsunami abbreviations in display order */
23
+ const TSUNAMI_LEGEND = [
24
+ { abbrev: "MJ", label: "MJ", color: chalk_1.default.redBright },
25
+ { abbrev: "WN", label: "WN", color: chalk_1.default.red },
26
+ { abbrev: "AD", label: "AD", color: chalk_1.default.yellow },
27
+ ];
28
+ /**
29
+ * Build legend lines to overlay in the upper-left empty area.
30
+ * Returns an array of { row, text } for rows 0-3.
31
+ */
32
+ function buildLegend() {
33
+ const lines = [];
34
+ // Row 0: "震度:" header + first 4 values
35
+ const intLine1 = INTENSITY_LEGEND.slice(0, 4)
36
+ .map((v) => (0, formatter_1.intensityColor)(v)(v.padEnd(2)))
37
+ .join(" ");
38
+ lines.push({ row: 0, text: `震度: ${intLine1}` });
39
+ // Row 1: next 4 values
40
+ const intLine2 = INTENSITY_LEGEND.slice(4, 8)
41
+ .map((v) => (0, formatter_1.intensityColor)(v)(v.padEnd(2)))
42
+ .join(" ");
43
+ lines.push({ row: 1, text: ` ${intLine2}` });
44
+ // Row 2: last value (7)
45
+ const intLine3 = (0, formatter_1.intensityColor)("7")("7 ");
46
+ lines.push({ row: 2, text: ` ${intLine3}` });
47
+ // Row 3: tsunami header + values
48
+ const tsunamiText = TSUNAMI_LEGEND.map((t) => t.color(t.label)).join(" ");
49
+ lines.push({ row: 3, text: `津波: ${tsunamiText}` });
50
+ return lines;
51
+ }
52
+ // ── ANSI strip helper ──
53
+ function stripAnsi(str) {
54
+ // eslint-disable-next-line no-control-regex
55
+ return str.replace(/\u001b\[[0-9;]*m/g, "");
56
+ }
57
+ // ── Rendering ──
58
+ /**
59
+ * Render the minimap from a set of cells.
60
+ * Returns 12 lines of text representing the ASCII minimap with legend overlay.
61
+ */
62
+ function renderMinimap(cells) {
63
+ // Build cell lookup
64
+ const cellMap = new Map();
65
+ for (const cell of cells) {
66
+ cellMap.set(cell.prefId, cell);
67
+ }
68
+ // Build placement lookup: (row,col) -> { prefId, isAnchor }
69
+ const gridMap = new Map();
70
+ for (const placement of grid_layout_1.PREF_PLACEMENTS) {
71
+ for (const pos of placement.cells) {
72
+ const key = `${pos.row},${pos.col}`;
73
+ gridMap.set(key, {
74
+ prefId: placement.id,
75
+ isAnchor: pos.row === placement.anchor.row && pos.col === placement.anchor.col,
76
+ });
77
+ }
78
+ }
79
+ // Build legend
80
+ const legend = buildLegend();
81
+ const legendByRow = new Map();
82
+ for (const entry of legend) {
83
+ legendByRow.set(entry.row, entry.text);
84
+ }
85
+ // Render each row
86
+ const lines = [];
87
+ for (let row = 0; row < grid_layout_1.GRID_ROWS; row++) {
88
+ let line = "";
89
+ // Find the last occupied column in this row
90
+ let lastCol = -1;
91
+ for (let col = 0; col < grid_layout_1.GRID_COLS; col++) {
92
+ if (gridMap.has(`${row},${col}`))
93
+ lastCol = col;
94
+ }
95
+ for (let col = 0; col < grid_layout_1.GRID_COLS; col++) {
96
+ const key = `${row},${col}`;
97
+ const entry = gridMap.get(key);
98
+ if (entry == null) {
99
+ // Empty cell (ocean) — check if legend should be rendered here
100
+ if (legendByRow.has(row) && col === 0) {
101
+ const legendText = legendByRow.get(row);
102
+ line += legendText;
103
+ // Calculate how many columns the legend text spans
104
+ const legendVisualLen = stripAnsi(legendText).length;
105
+ const colsSpanned = Math.ceil(legendVisualLen / CELL_WIDTH);
106
+ col += colsSpanned - 1; // skip ahead (loop will increment)
107
+ continue;
108
+ }
109
+ if (col <= lastCol) {
110
+ line += " ".repeat(CELL_WIDTH);
111
+ }
112
+ }
113
+ else {
114
+ const cell = cellMap.get(entry.prefId);
115
+ const content = cell?.content ?? "..";
116
+ const isMatch = content !== "..";
117
+ if (entry.isAnchor) {
118
+ // Anchor cell: show "AA:xx"
119
+ const padded = content.length >= 2 ? content.slice(0, 2) : content.padEnd(2);
120
+ const text = `${entry.prefId}:${padded}`;
121
+ if (isMatch && cell?.color) {
122
+ line += cell.color(text) + " ";
123
+ }
124
+ else {
125
+ line += chalk_1.default.dim(text) + " ";
126
+ }
127
+ }
128
+ else {
129
+ // Continuation cell: show "·····"
130
+ if (isMatch && cell?.color) {
131
+ line += cell.color(CONTINUATION) + " ";
132
+ }
133
+ else {
134
+ line += chalk_1.default.dim(CONTINUATION) + " ";
135
+ }
136
+ }
137
+ }
138
+ }
139
+ lines.push(line.trimEnd());
140
+ }
141
+ return lines;
142
+ }
143
+ // ── Building cells from PresentationEvent ──
144
+ /** Tsunami warning kind abbreviations */
145
+ function tsunamiKindAbbrev(kind) {
146
+ if (kind.includes("大津波"))
147
+ return "MJ";
148
+ if (kind.includes("津波警報"))
149
+ return "WN";
150
+ if (kind.includes("注意報"))
151
+ return "AD";
152
+ return "??";
153
+ }
154
+ /** Tsunami warning color by abbreviation */
155
+ function tsunamiColor(abbrev) {
156
+ switch (abbrev) {
157
+ case "MJ":
158
+ return chalk_1.default.redBright;
159
+ case "WN":
160
+ return chalk_1.default.red;
161
+ case "AD":
162
+ return chalk_1.default.yellow;
163
+ default:
164
+ return chalk_1.default.white;
165
+ }
166
+ }
167
+ /** Tsunami kind priority (higher = more severe) */
168
+ function tsunamiKindPriority(abbrev) {
169
+ switch (abbrev) {
170
+ case "MJ":
171
+ return 3;
172
+ case "WN":
173
+ return 2;
174
+ case "AD":
175
+ return 1;
176
+ default:
177
+ return 0;
178
+ }
179
+ }
180
+ /**
181
+ * Build MinimapCell[] from a PresentationEvent.
182
+ * Assigns prefecture-level data (max intensity, tsunami kind) for each matched area.
183
+ */
184
+ function buildMinimapCells(event) {
185
+ const prefData = new Map();
186
+ if (event.domain === "earthquake" || event.domain === "lgObservation" || event.domain === "eew") {
187
+ for (const item of event.areaItems) {
188
+ const prefId = (0, pref_mapping_1.mapAreaToPref)(item.name);
189
+ if (prefId == null)
190
+ continue;
191
+ const maxInt = item.maxInt ?? "..";
192
+ const rank = maxInt !== ".." ? (0, formatter_1.intensityToNumeric)(maxInt) : -1;
193
+ const existing = prefData.get(prefId);
194
+ if (!existing || rank > existing.priority) {
195
+ prefData.set(prefId, {
196
+ content: maxInt,
197
+ color: maxInt !== ".." ? (0, formatter_1.intensityColor)(maxInt) : undefined,
198
+ priority: rank,
199
+ });
200
+ }
201
+ }
202
+ }
203
+ else if (event.domain === "tsunami") {
204
+ for (const item of event.areaItems) {
205
+ const prefId = (0, pref_mapping_1.mapAreaToPref)(item.name);
206
+ if (prefId == null)
207
+ continue;
208
+ const kind = item.kind ?? "";
209
+ const abbrev = tsunamiKindAbbrev(kind);
210
+ const priority = tsunamiKindPriority(abbrev);
211
+ const existing = prefData.get(prefId);
212
+ if (!existing || priority > existing.priority) {
213
+ prefData.set(prefId, {
214
+ content: abbrev,
215
+ color: tsunamiColor(abbrev),
216
+ priority,
217
+ });
218
+ }
219
+ }
220
+ }
221
+ // Build cells for all prefectures
222
+ const cells = [];
223
+ for (const prefId of grid_layout_1.ALL_PREF_IDS) {
224
+ const data = prefData.get(prefId);
225
+ if (data) {
226
+ cells.push({ prefId, content: data.content, color: data.color });
227
+ }
228
+ else {
229
+ cells.push({ prefId, content: ".." });
230
+ }
231
+ }
232
+ return cells;
233
+ }
234
+ // ── Display conditions ──
235
+ /**
236
+ * Determine whether the minimap should be shown for this event.
237
+ */
238
+ function shouldShowMinimap(event) {
239
+ const termWidth = process.stdout.columns ?? 80;
240
+ if (termWidth < 80)
241
+ return false;
242
+ if (event.isCancellation)
243
+ return false;
244
+ switch (event.domain) {
245
+ case "earthquake": {
246
+ if (event.areaCount === 0)
247
+ return false;
248
+ const rank = event.maxIntRank ?? 0;
249
+ return rank >= 4 || event.areaCount >= 4;
250
+ }
251
+ case "eew":
252
+ return event.forecastAreaCount > 0;
253
+ case "tsunami":
254
+ if (event.forecastAreaCount === 0 && event.areaCount === 0)
255
+ return false;
256
+ return ["critical", "warning", "normal"].includes(event.frameLevel);
257
+ case "lgObservation": {
258
+ if (event.areaCount === 0 && event.observationCount === 0)
259
+ return false;
260
+ const rank = event.maxIntRank ?? 0;
261
+ const count = event.observationCount > 0 ? event.observationCount : event.areaCount;
262
+ return rank >= 4 || count >= 4;
263
+ }
264
+ default:
265
+ return false;
266
+ }
267
+ }
268
+ /**
269
+ * Public API: render the minimap for a PresentationEvent.
270
+ * Returns the minimap lines, or null if the minimap should not be shown.
271
+ */
272
+ function renderMinimapForEvent(event) {
273
+ if (!shouldShowMinimap(event))
274
+ return null;
275
+ const cells = buildMinimapCells(event);
276
+ return renderMinimap(cells);
277
+ }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PREF_DEFS = void 0;
4
+ exports.mapAreaToPref = mapAreaToPref;
5
+ /** All 47 prefecture definitions with match patterns */
6
+ exports.PREF_DEFS = [
7
+ { id: "HK", name: "北海道", patterns: ["北海道"] },
8
+ { id: "AO", name: "青森県", patterns: ["青森"] },
9
+ { id: "IT", name: "岩手県", patterns: ["岩手"] },
10
+ { id: "MG", name: "宮城県", patterns: ["宮城"] },
11
+ { id: "AK", name: "秋田県", patterns: ["秋田"] },
12
+ { id: "YG", name: "山形県", patterns: ["山形"] },
13
+ { id: "FS", name: "福島県", patterns: ["福島"] },
14
+ { id: "IB", name: "茨城県", patterns: ["茨城"] },
15
+ { id: "TC", name: "栃木県", patterns: ["栃木"] },
16
+ { id: "GU", name: "群馬県", patterns: ["群馬"] },
17
+ { id: "ST", name: "埼玉県", patterns: ["埼玉"] },
18
+ { id: "CB", name: "千葉県", patterns: ["千葉"] },
19
+ { id: "TY", name: "東京都", patterns: ["東京島しょ", "小笠原", "東京"] },
20
+ { id: "KN", name: "神奈川県", patterns: ["神奈川"] },
21
+ { id: "NI", name: "新潟県", patterns: ["新潟"] },
22
+ { id: "TM", name: "富山県", patterns: ["富山"] },
23
+ { id: "IS", name: "石川県", patterns: ["石川"] },
24
+ { id: "FI", name: "福井県", patterns: ["福井"] },
25
+ { id: "YN", name: "山梨県", patterns: ["山梨"] },
26
+ { id: "NA", name: "長野県", patterns: ["長野"] },
27
+ { id: "GI", name: "岐阜県", patterns: ["岐阜"] },
28
+ { id: "SZ", name: "静岡県", patterns: ["静岡"] },
29
+ { id: "AI", name: "愛知県", patterns: ["愛知"] },
30
+ { id: "ME", name: "三重県", patterns: ["三重"] },
31
+ { id: "SI", name: "滋賀県", patterns: ["滋賀"] },
32
+ { id: "KY", name: "京都府", patterns: ["京都"] },
33
+ { id: "OS", name: "大阪府", patterns: ["大阪"] },
34
+ { id: "HG", name: "兵庫県", patterns: ["兵庫"] },
35
+ { id: "NR", name: "奈良県", patterns: ["奈良"] },
36
+ { id: "WA", name: "和歌山県", patterns: ["和歌山"] },
37
+ { id: "TT", name: "鳥取県", patterns: ["鳥取"] },
38
+ { id: "SM", name: "島根県", patterns: ["島根"] },
39
+ { id: "OY", name: "岡山県", patterns: ["岡山"] },
40
+ { id: "HS", name: "広島県", patterns: ["広島"] },
41
+ { id: "YA", name: "山口県", patterns: ["山口"] },
42
+ { id: "TK", name: "徳島県", patterns: ["徳島"] },
43
+ { id: "KA", name: "香川県", patterns: ["香川"] },
44
+ { id: "EH", name: "愛媛県", patterns: ["愛媛"] },
45
+ { id: "KO", name: "高知県", patterns: ["高知"] },
46
+ { id: "FO", name: "福岡県", patterns: ["福岡"] },
47
+ { id: "SG", name: "佐賀県", patterns: ["佐賀"] },
48
+ { id: "NS", name: "長崎県", patterns: ["長崎"] },
49
+ { id: "KU", name: "熊本県", patterns: ["熊本"] },
50
+ { id: "OI", name: "大分県", patterns: ["大分"] },
51
+ { id: "MZ", name: "宮崎県", patterns: ["宮崎"] },
52
+ { id: "KG", name: "鹿児島県", patterns: ["奄美", "鹿児島"] },
53
+ { id: "OK", name: "沖縄県", patterns: ["沖縄"] },
54
+ ];
55
+ /**
56
+ * Build a sorted pattern index: longer patterns first to prevent false matches.
57
+ * e.g. "東京島しょ" is checked before "東京".
58
+ */
59
+ function buildPatternIndex() {
60
+ const entries = [];
61
+ for (const def of exports.PREF_DEFS) {
62
+ for (const pattern of def.patterns) {
63
+ entries.push({ pattern, prefId: def.id });
64
+ }
65
+ }
66
+ entries.sort((a, b) => b.pattern.length - a.pattern.length);
67
+ return entries;
68
+ }
69
+ const patternIndex = buildPatternIndex();
70
+ /**
71
+ * Map an area name (e.g. "石川県能登地方") to its prefecture ID.
72
+ * Uses substring matching with longer patterns checked first.
73
+ * Returns null if no match is found.
74
+ */
75
+ function mapAreaToPref(areaName) {
76
+ for (const { pattern, prefId } of patternIndex) {
77
+ if (areaName.includes(pattern)) {
78
+ return prefId;
79
+ }
80
+ }
81
+ return null;
82
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });