signalk-chiplog 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE +21 -0
  3. package/README.md +290 -0
  4. package/index.js +327 -0
  5. package/lib/api.js +427 -0
  6. package/lib/database.js +264 -0
  7. package/lib/detection.js +407 -0
  8. package/lib/entries.js +345 -0
  9. package/lib/errors.js +22 -0
  10. package/lib/event-watcher.js +332 -0
  11. package/lib/events.js +287 -0
  12. package/lib/export.js +220 -0
  13. package/lib/formats.js +215 -0
  14. package/lib/manoeuvre-types.js +84 -0
  15. package/lib/observation-recorder.js +152 -0
  16. package/lib/place-names.js +208 -0
  17. package/lib/places.js +128 -0
  18. package/lib/propulsion-detector.js +156 -0
  19. package/lib/propulsion.js +75 -0
  20. package/lib/rows.js +15 -0
  21. package/lib/track-recorder.js +149 -0
  22. package/lib/track.js +58 -0
  23. package/lib/usb-scheduler.js +131 -0
  24. package/lib/validation.js +105 -0
  25. package/package.json +67 -0
  26. package/public/app.css +603 -0
  27. package/public/entry/entry.css +528 -0
  28. package/public/entry/icons/apple-touch-icon.png +0 -0
  29. package/public/entry/icons/icon-192.png +0 -0
  30. package/public/entry/icons/icon-512.png +0 -0
  31. package/public/entry/index.html +22 -0
  32. package/public/entry/js/access.mjs +116 -0
  33. package/public/entry/js/clock.mjs +29 -0
  34. package/public/entry/js/components/AccessGate.mjs +70 -0
  35. package/public/entry/js/components/Dialogs.mjs +64 -0
  36. package/public/entry/js/components/ManoeuvrePad.mjs +106 -0
  37. package/public/entry/js/components/NotePanel.mjs +34 -0
  38. package/public/entry/js/components/RecentList.mjs +113 -0
  39. package/public/entry/js/components/SketchPanel.mjs +181 -0
  40. package/public/entry/js/components/StatusHeader.mjs +69 -0
  41. package/public/entry/js/journal.mjs +139 -0
  42. package/public/entry/js/main.mjs +360 -0
  43. package/public/entry/js/outbox.mjs +124 -0
  44. package/public/entry/js/strokes.mjs +81 -0
  45. package/public/entry/manifest.webmanifest +26 -0
  46. package/public/entry/sw.js +75 -0
  47. package/public/icon.svg +7 -0
  48. package/public/index.html +18 -0
  49. package/public/js/api.mjs +85 -0
  50. package/public/js/auth.mjs +41 -0
  51. package/public/js/components/ExportView.mjs +182 -0
  52. package/public/js/components/LogView.mjs +132 -0
  53. package/public/js/components/PassageView.mjs +343 -0
  54. package/public/js/components/PropulsionStrip.mjs +60 -0
  55. package/public/js/components/StatusBar.mjs +55 -0
  56. package/public/js/components/Timeline.mjs +226 -0
  57. package/public/js/components/TrackMap.mjs +85 -0
  58. package/public/js/components/common.mjs +70 -0
  59. package/public/js/context.mjs +56 -0
  60. package/public/js/days.mjs +56 -0
  61. package/public/js/format.mjs +84 -0
  62. package/public/js/i18n.mjs +480 -0
  63. package/public/js/ids.mjs +10 -0
  64. package/public/js/main.mjs +48 -0
  65. package/public/js/status.mjs +21 -0
  66. package/public/vendor/htm-LICENSE +202 -0
  67. package/public/vendor/leaflet/LICENSE +26 -0
  68. package/public/vendor/leaflet/images/layers-2x.png +0 -0
  69. package/public/vendor/leaflet/images/layers.png +0 -0
  70. package/public/vendor/leaflet/images/marker-icon-2x.png +0 -0
  71. package/public/vendor/leaflet/images/marker-icon.png +0 -0
  72. package/public/vendor/leaflet/images/marker-shadow.png +0 -0
  73. package/public/vendor/leaflet/leaflet.css +661 -0
  74. package/public/vendor/leaflet/leaflet.js +6 -0
  75. package/public/vendor/preact-LICENSE +21 -0
  76. package/public/vendor/preact-htm.mjs +1 -0
package/public/app.css ADDED
@@ -0,0 +1,603 @@
1
+ :root {
2
+ color-scheme: light dark;
3
+ --bg: #f2f5f8;
4
+ --surface: #ffffff;
5
+ --text: #16222e;
6
+ --muted: #5a6b7b;
7
+ --border: #d6dee6;
8
+ --accent: #0b5cad;
9
+ --accent-contrast: #ffffff;
10
+ --engine: #e8590c;
11
+ --sail: #1c7ed6;
12
+ --danger: #c92a2a;
13
+ --ok: #2b8a3e;
14
+ --warn-bg: #fff4d6;
15
+ --warn-text: #7a5300;
16
+ --error-bg: #fde8e8;
17
+ --ok-bg: #e6f4ea;
18
+ --radius: 10px;
19
+ font-family:
20
+ system-ui,
21
+ -apple-system,
22
+ 'Segoe UI',
23
+ Roboto,
24
+ sans-serif;
25
+ font-size: 16px;
26
+ line-height: 1.45;
27
+ }
28
+
29
+ @media (prefers-color-scheme: dark) {
30
+ :root {
31
+ --bg: #0d151d;
32
+ --surface: #16212c;
33
+ --text: #e2e9ef;
34
+ --muted: #93a3b2;
35
+ --border: #2a3847;
36
+ --accent: #6cb4ff;
37
+ --accent-contrast: #0d151d;
38
+ --warn-bg: #3a2e10;
39
+ --warn-text: #f3d27a;
40
+ --error-bg: #3b1a1a;
41
+ --ok-bg: #173323;
42
+ }
43
+ }
44
+
45
+ * {
46
+ box-sizing: border-box;
47
+ }
48
+
49
+ body {
50
+ margin: 0;
51
+ background: var(--bg);
52
+ color: var(--text);
53
+ }
54
+
55
+ a {
56
+ color: var(--accent);
57
+ }
58
+
59
+ button {
60
+ font: inherit;
61
+ padding: 0.45rem 0.9rem;
62
+ border-radius: 8px;
63
+ border: 1px solid var(--accent);
64
+ background: var(--accent);
65
+ color: var(--accent-contrast);
66
+ cursor: pointer;
67
+ }
68
+
69
+ button:disabled {
70
+ opacity: 0.5;
71
+ cursor: default;
72
+ }
73
+
74
+ button.link-button {
75
+ background: none;
76
+ border: none;
77
+ color: var(--accent);
78
+ padding: 0.2rem 0.4rem;
79
+ }
80
+
81
+ button.danger {
82
+ background: none;
83
+ color: var(--danger);
84
+ border-color: var(--danger);
85
+ }
86
+
87
+ input {
88
+ font: inherit;
89
+ padding: 0.4rem 0.6rem;
90
+ border-radius: 8px;
91
+ border: 1px solid var(--border);
92
+ background: var(--surface);
93
+ color: var(--text);
94
+ }
95
+
96
+ .muted {
97
+ color: var(--muted);
98
+ }
99
+
100
+ /* Layout */
101
+
102
+ .topbar,
103
+ .statusbar,
104
+ main,
105
+ .attribution {
106
+ max-width: 1100px;
107
+ margin: 0 auto;
108
+ padding-left: 1rem;
109
+ padding-right: 1rem;
110
+ }
111
+
112
+ .topbar {
113
+ display: flex;
114
+ align-items: center;
115
+ justify-content: space-between;
116
+ gap: 1rem;
117
+ padding-top: 0.75rem;
118
+ padding-bottom: 0.75rem;
119
+ }
120
+
121
+ .brand {
122
+ display: flex;
123
+ align-items: center;
124
+ gap: 0.5rem;
125
+ font-weight: 700;
126
+ font-size: 1.2rem;
127
+ color: var(--text);
128
+ text-decoration: none;
129
+ }
130
+
131
+ .topbar nav {
132
+ display: flex;
133
+ gap: 0.25rem;
134
+ }
135
+
136
+ .topbar nav a {
137
+ padding: 0.4rem 0.8rem;
138
+ border-radius: 8px;
139
+ text-decoration: none;
140
+ color: var(--text);
141
+ }
142
+
143
+ .topbar nav a[aria-current='page'] {
144
+ background: var(--surface);
145
+ box-shadow: inset 0 0 0 1px var(--border);
146
+ font-weight: 600;
147
+ }
148
+
149
+ .statusbar {
150
+ display: flex;
151
+ flex-wrap: wrap;
152
+ align-items: center;
153
+ gap: 0.4rem 1rem;
154
+ padding-top: 0.5rem;
155
+ padding-bottom: 0.5rem;
156
+ color: var(--muted);
157
+ }
158
+
159
+ .statusbar strong {
160
+ color: var(--text);
161
+ }
162
+
163
+ .status-dot {
164
+ width: 0.7rem;
165
+ height: 0.7rem;
166
+ border-radius: 50%;
167
+ background: var(--muted);
168
+ }
169
+
170
+ .statusbar-underway .status-dot {
171
+ background: var(--ok);
172
+ }
173
+
174
+ .status-fallback {
175
+ flex-basis: 100%;
176
+ padding: 0.35rem 0.7rem;
177
+ border-radius: 8px;
178
+ background: var(--warn-bg);
179
+ color: var(--warn-text);
180
+ font-size: 0.9rem;
181
+ }
182
+
183
+ main {
184
+ padding-bottom: 2rem;
185
+ }
186
+
187
+ .attribution {
188
+ padding-bottom: 1.5rem;
189
+ color: var(--muted);
190
+ font-size: 0.8rem;
191
+ }
192
+
193
+ .page-title {
194
+ margin: 1rem 0;
195
+ }
196
+
197
+ .card {
198
+ margin: 1rem 0;
199
+ padding: 1rem;
200
+ border-radius: var(--radius);
201
+ background: var(--surface);
202
+ border: 1px solid var(--border);
203
+ }
204
+
205
+ .card h2 {
206
+ margin: 0 0 0.75rem;
207
+ font-size: 1.1rem;
208
+ }
209
+
210
+ .card-header {
211
+ display: flex;
212
+ flex-wrap: wrap;
213
+ align-items: baseline;
214
+ justify-content: space-between;
215
+ gap: 0.5rem;
216
+ }
217
+
218
+ .notice {
219
+ margin: 0.75rem 0;
220
+ padding: 0.6rem 0.9rem;
221
+ border-radius: 8px;
222
+ background: var(--surface);
223
+ border: 1px solid var(--border);
224
+ }
225
+
226
+ .notice p {
227
+ margin: 0 0 0.5rem;
228
+ }
229
+
230
+ .notice p:last-child {
231
+ margin: 0;
232
+ }
233
+
234
+ .notice-error {
235
+ background: var(--error-bg);
236
+ border-color: transparent;
237
+ }
238
+
239
+ .notice-ok {
240
+ background: var(--ok-bg);
241
+ border-color: transparent;
242
+ }
243
+
244
+ .loading,
245
+ .empty {
246
+ color: var(--muted);
247
+ padding: 2rem 0;
248
+ }
249
+
250
+ /* Places */
251
+
252
+ .place-pending {
253
+ font-style: italic;
254
+ text-decoration: underline dotted var(--muted);
255
+ cursor: help;
256
+ }
257
+
258
+ /* Log */
259
+
260
+ .day {
261
+ margin: 1.5rem 0;
262
+ }
263
+
264
+ .day-header {
265
+ display: flex;
266
+ flex-wrap: wrap;
267
+ align-items: baseline;
268
+ justify-content: space-between;
269
+ gap: 0.5rem;
270
+ border-bottom: 1px solid var(--border);
271
+ margin-bottom: 0.6rem;
272
+ }
273
+
274
+ .day-header h2 {
275
+ margin: 0 0 0.3rem;
276
+ font-size: 1.05rem;
277
+ }
278
+
279
+ .day-header h2::first-letter {
280
+ text-transform: uppercase;
281
+ }
282
+
283
+ .day-distance {
284
+ color: var(--muted);
285
+ font-size: 0.9rem;
286
+ }
287
+
288
+ .passage-list {
289
+ list-style: none;
290
+ margin: 0;
291
+ padding: 0;
292
+ display: grid;
293
+ gap: 0.6rem;
294
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
295
+ }
296
+
297
+ .passage-card {
298
+ display: block;
299
+ height: 100%;
300
+ padding: 0.8rem 0.9rem;
301
+ border-radius: var(--radius);
302
+ background: var(--surface);
303
+ border: 1px solid var(--border);
304
+ color: var(--text);
305
+ text-decoration: none;
306
+ }
307
+
308
+ .passage-card:hover,
309
+ .passage-card:focus-visible {
310
+ border-color: var(--accent);
311
+ }
312
+
313
+ .passage-active {
314
+ border-left: 4px solid var(--ok);
315
+ }
316
+
317
+ .passage-times {
318
+ color: var(--muted);
319
+ font-size: 0.9rem;
320
+ }
321
+
322
+ .passage-places {
323
+ margin: 0.2rem 0 0.4rem;
324
+ font-weight: 600;
325
+ }
326
+
327
+ .passage-facts {
328
+ display: flex;
329
+ gap: 1rem;
330
+ color: var(--muted);
331
+ font-size: 0.9rem;
332
+ }
333
+
334
+ .passage-continues {
335
+ margin-top: 0.3rem;
336
+ color: var(--muted);
337
+ font-size: 0.8rem;
338
+ font-style: italic;
339
+ }
340
+
341
+ .engine-sail {
342
+ display: flex;
343
+ height: 6px;
344
+ margin-top: 0.5rem;
345
+ border-radius: 3px;
346
+ overflow: hidden;
347
+ }
348
+
349
+ .engine-sail-engine {
350
+ background: var(--engine);
351
+ }
352
+
353
+ .engine-sail-sail {
354
+ background: var(--sail);
355
+ }
356
+
357
+ .load-older {
358
+ display: block;
359
+ margin: 1.5rem auto;
360
+ }
361
+
362
+ /* Passage */
363
+
364
+ .back {
365
+ display: inline-block;
366
+ margin-top: 1rem;
367
+ }
368
+
369
+ .passage-header h1 {
370
+ margin: 0.5rem 0 0.2rem;
371
+ font-size: 1.5rem;
372
+ }
373
+
374
+ .passage-when {
375
+ margin: 0;
376
+ color: var(--muted);
377
+ }
378
+
379
+ .passage-when::first-letter {
380
+ text-transform: uppercase;
381
+ }
382
+
383
+ .badge {
384
+ display: inline-block;
385
+ margin-top: 0.4rem;
386
+ padding: 0.15rem 0.6rem;
387
+ border-radius: 999px;
388
+ background: var(--ok-bg);
389
+ color: var(--ok);
390
+ font-size: 0.85rem;
391
+ font-weight: 600;
392
+ }
393
+
394
+ .facts {
395
+ display: grid;
396
+ grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
397
+ gap: 0.6rem;
398
+ margin: 1rem 0;
399
+ }
400
+
401
+ .facts div {
402
+ padding: 0.6rem 0.8rem;
403
+ border-radius: var(--radius);
404
+ background: var(--surface);
405
+ border: 1px solid var(--border);
406
+ }
407
+
408
+ .facts dt {
409
+ color: var(--muted);
410
+ font-size: 0.8rem;
411
+ }
412
+
413
+ .facts dd {
414
+ margin: 0;
415
+ font-size: 1.15rem;
416
+ font-weight: 600;
417
+ }
418
+
419
+ .fact-engine {
420
+ color: var(--engine);
421
+ }
422
+
423
+ .fact-sail {
424
+ color: var(--sail);
425
+ }
426
+
427
+ .track-map {
428
+ height: 380px;
429
+ border-radius: 8px;
430
+ }
431
+
432
+ .strip {
433
+ position: relative;
434
+ height: 18px;
435
+ border-radius: 4px;
436
+ background: var(--bg);
437
+ overflow: hidden;
438
+ }
439
+
440
+ .strip-segment {
441
+ position: absolute;
442
+ top: 0;
443
+ bottom: 0;
444
+ }
445
+
446
+ .strip-engine {
447
+ background: var(--engine);
448
+ }
449
+
450
+ .strip-sail {
451
+ background: var(--sail);
452
+ }
453
+
454
+ .segment-list {
455
+ list-style: none;
456
+ margin: 0.75rem 0 0;
457
+ padding: 0;
458
+ }
459
+
460
+ .segment-list li {
461
+ display: flex;
462
+ flex-wrap: wrap;
463
+ align-items: center;
464
+ gap: 0.2rem 1rem;
465
+ padding: 0.35rem 0;
466
+ border-top: 1px solid var(--border);
467
+ }
468
+
469
+ .segment-type {
470
+ min-width: 4.5rem;
471
+ font-weight: 600;
472
+ }
473
+
474
+ .segment-engine {
475
+ color: var(--engine);
476
+ }
477
+
478
+ .segment-sail {
479
+ color: var(--sail);
480
+ }
481
+
482
+ .timeline-scroll {
483
+ overflow-x: auto;
484
+ }
485
+
486
+ .timeline {
487
+ width: 100%;
488
+ border-collapse: collapse;
489
+ font-size: 0.9rem;
490
+ font-variant-numeric: tabular-nums;
491
+ }
492
+
493
+ .timeline th,
494
+ .timeline td {
495
+ padding: 0.35rem 0.5rem;
496
+ text-align: left;
497
+ vertical-align: top;
498
+ border-top: 1px solid var(--border);
499
+ white-space: nowrap;
500
+ }
501
+
502
+ .timeline thead th {
503
+ border-top: none;
504
+ color: var(--muted);
505
+ font-weight: 600;
506
+ }
507
+
508
+ .timeline-day th {
509
+ padding-top: 0.8rem;
510
+ color: var(--text);
511
+ }
512
+
513
+ .timeline-day th::first-letter {
514
+ text-transform: uppercase;
515
+ }
516
+
517
+ .timeline-remarks {
518
+ white-space: normal !important;
519
+ min-width: 14rem;
520
+ }
521
+
522
+ .timeline-event .timeline-time {
523
+ font-weight: 600;
524
+ }
525
+
526
+ .alarm {
527
+ color: var(--danger);
528
+ }
529
+
530
+ .strokes {
531
+ display: block;
532
+ max-width: 16rem;
533
+ max-height: 6rem;
534
+ margin: 0.2rem 0;
535
+ }
536
+
537
+ /* Corrections */
538
+
539
+ .name-field {
540
+ display: flex;
541
+ flex-wrap: wrap;
542
+ align-items: center;
543
+ gap: 0.4rem 0.8rem;
544
+ padding: 0.4rem 0;
545
+ }
546
+
547
+ .name-label {
548
+ min-width: 5.5rem;
549
+ color: var(--muted);
550
+ }
551
+
552
+ .name-form {
553
+ display: flex;
554
+ flex-wrap: wrap;
555
+ gap: 0.4rem;
556
+ }
557
+
558
+ .actions {
559
+ display: flex;
560
+ flex-wrap: wrap;
561
+ gap: 0.6rem;
562
+ margin-top: 1rem;
563
+ }
564
+
565
+ /* Export */
566
+
567
+ .date-range {
568
+ display: flex;
569
+ flex-wrap: wrap;
570
+ gap: 1rem;
571
+ }
572
+
573
+ .date-range label {
574
+ display: flex;
575
+ flex-direction: column;
576
+ gap: 0.25rem;
577
+ }
578
+
579
+ .downloads {
580
+ padding-left: 1.2rem;
581
+ }
582
+
583
+ .downloads li {
584
+ margin: 0.35rem 0;
585
+ }
586
+
587
+ @media (max-width: 600px) {
588
+ .passage-header h1 {
589
+ font-size: 1.25rem;
590
+ }
591
+
592
+ .track-map {
593
+ height: 280px;
594
+ }
595
+ }
596
+
597
+ .usb-status {
598
+ list-style: none;
599
+ display: grid;
600
+ gap: 0.35rem;
601
+ margin: 0 0 1rem;
602
+ padding: 0;
603
+ }