poe-svelte-ui-lib 1.90.0 → 1.92.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.
- package/README.md +5 -1
- package/dist/Accordion/Accordion.svelte +35 -28
- package/dist/Button/Button.svelte +1 -1
- package/dist/CopiedChip.svelte +1 -1
- package/dist/Graph/Graph.svelte +1 -1
- package/dist/Input/Input.svelte +1 -1
- package/dist/PeriodChart/PeriodChart.svelte +2 -2
- package/dist/Table/Table.svelte +2 -2
- package/dist/WidgetFileSystem/WidgetFileSystem.svelte +4 -4
- package/dist/WidgetMap/WidgetMap.svelte +10 -10
- package/dist/WidgetMap/mission/MapAgro.svelte +5 -5
- package/dist/WidgetMap/mission/MapRoute.svelte +2 -3
- package/dist/WidgetPFD/WidgetPFD.svelte +26 -17
- package/dist/WidgetStackInfo/WidgetStackInfo.svelte +4 -4
- package/dist/WidgetWiFi/WidgetWiFi.svelte +1 -1
- package/dist/theme.css +28 -4
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
**Компоненты.** Каждый привязывается к одной переменной устройства: `Input`, `Select`, `Switch`, `Slider`, `Button`, `Joystick`, `ProgressBar`, `Graph`, `PeriodChart`, `Table`, `FileAttach`, `VideoViewer`, `TextField`, `Widget`.
|
|
26
26
|
|
|
27
|
-
**Раскладка.** `Accordion`, `Tabs`, `Divider`, `Carousel`, `Modal`.
|
|
27
|
+
**Раскладка.** `Accordion`, `Tabs`, `Divider`, `Carousel`, `Modal`. У `Accordion` есть необязательный слот `actions` - кнопки справа в заголовке, нажатие на них блок не сворачивает.
|
|
28
28
|
|
|
29
29
|
**Ядро протоколов** (`poe-svelte-ui-lib/protocols`). Кодирование и разбор пакетов для всех каналов связи с устройством. Работает без Svelte, в том числе под Node.
|
|
30
30
|
|
|
@@ -102,6 +102,10 @@ npm install poe-svelte-ui-lib
|
|
|
102
102
|
|
|
103
103
|
Поверхности идут в четыре ступени: фон страницы `--bg-body`, панель `--back-color`, блок внутри панели `--container-color`, поле ввода `--field-color`. Для частых случаев есть готовые классы. `ui-panel` для блока прямо на фоне страницы, `ui-inset` для блока внутри панели.
|
|
104
104
|
|
|
105
|
+
Текст идёт в три ступени: основной `--font-color`, вторичный `--muted-color`, подсказки `--faint-color`. Все три проходят WCAG AA (не ниже 4,5:1) на любом фоне темы. Текст не делают полупрозрачным (`opacity`), для вторичного текста берут `--muted-color`. Мельче 11px подписи не делают.
|
|
106
|
+
|
|
107
|
+
Цветной текст пишут через `--red-text`, `--green-text`, `--orange-text` и так далее (классы `text-(--red-text)`), а не через `--red-color`. `--*-color` - это заливки под белый текст. В тёмной теме они тёмные и как текст нечитаемы, а `--*-text` там светлые.
|
|
108
|
+
|
|
105
109
|
Панели не делают полупрозрачными, на фоне они теряются. Выпадающие меню делают через `ui-panel` с тенью `--elevation-3`.
|
|
106
110
|
|
|
107
111
|
Стили тегов в приложении пишите только внутри `@layer base`. Правило вне слоя сильнее утилит Tailwind, и подписи в компонентах станут крупнее, чем задумано.
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
size = { width: 1, height: 1 },
|
|
14
14
|
label = { name: "", class: "text-left", icon: null },
|
|
15
15
|
children,
|
|
16
|
+
actions,
|
|
16
17
|
image = "",
|
|
17
18
|
}: IAccordionProps = $props()
|
|
18
19
|
|
|
@@ -27,35 +28,41 @@
|
|
|
27
28
|
>
|
|
28
29
|
<div class="overflow-hidden rounded-[14px] border border-(--hairline-color) bg-(--back-color)">
|
|
29
30
|
<!-- Фон заголовка - мягкий оттенок мастер-цвета (светлый в светлой теме, тёмный в тёмной), текст - мастер-цвет с цветом шрифта 60/40, раскрытое содержимое остаётся на чистом фоне (--back-color). -->
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
{#if label?.icon}
|
|
36
|
-
<span
|
|
37
|
-
class="flex h-[18px] w-[18px] shrink-0 items-center justify-center text-[color-mix(in_srgb,var(--accent-color)_60%,var(--font-color))] [&_svg]:h-full [&_svg]:w-full"
|
|
38
|
-
>
|
|
39
|
-
{#if typeof label?.icon === "string"}
|
|
40
|
-
{@html label.icon}
|
|
41
|
-
{:else}
|
|
42
|
-
{@const IconComponent = label?.icon}
|
|
43
|
-
<IconComponent />
|
|
44
|
-
{/if}
|
|
45
|
-
</span>
|
|
46
|
-
{/if}
|
|
47
|
-
<span class={twMerge(`min-w-0 truncate text-[14px] font-semibold`, label.class)}>{label?.name}</span>
|
|
48
|
-
</div>
|
|
49
|
-
|
|
50
|
-
<svg
|
|
51
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
52
|
-
class="h-[14px] w-[14px] shrink-0 text-[color-mix(in_srgb,var(--accent-color)_60%,var(--font-color))] transition-transform duration-200"
|
|
53
|
-
style="transform: rotate({isOpen ? 180 : 0}deg)"
|
|
54
|
-
viewBox="0 0 24 24"
|
|
31
|
+
<!-- Заголовок: кнопка сворачивания на всю ширину и необязательные кнопки справа (actions) - вне неё, чтобы не сворачивать блок -->
|
|
32
|
+
<div class="flex w-full items-center bg-(--accent-soft)">
|
|
33
|
+
<button
|
|
34
|
+
class="flex min-w-0 flex-1 cursor-pointer items-center justify-between gap-3 px-4 py-3 text-left text-[color-mix(in_srgb,var(--accent-color)_35%,var(--font-color))] transition-colors duration-150 hover:bg-[color-mix(in_srgb,var(--accent-color)_12%,var(--accent-soft))]"
|
|
35
|
+
onclick={toggle}
|
|
55
36
|
>
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
37
|
+
<div class="flex min-w-0 flex-1 items-center gap-3">
|
|
38
|
+
{#if label?.icon}
|
|
39
|
+
<span
|
|
40
|
+
class="flex h-[18px] w-[18px] shrink-0 items-center justify-center text-[color-mix(in_srgb,var(--accent-color)_35%,var(--font-color))] [&_svg]:h-full [&_svg]:w-full"
|
|
41
|
+
>
|
|
42
|
+
{#if typeof label?.icon === "string"}
|
|
43
|
+
{@html label.icon}
|
|
44
|
+
{:else}
|
|
45
|
+
{@const IconComponent = label?.icon}
|
|
46
|
+
<IconComponent />
|
|
47
|
+
{/if}
|
|
48
|
+
</span>
|
|
49
|
+
{/if}
|
|
50
|
+
<span class={twMerge(`min-w-0 truncate text-[14px] font-semibold`, label.class)}>{label?.name}</span>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<svg
|
|
54
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
55
|
+
class="h-[14px] w-[14px] shrink-0 text-[color-mix(in_srgb,var(--accent-color)_35%,var(--font-color))] transition-transform duration-200"
|
|
56
|
+
style="transform: rotate({isOpen ? 180 : 0}deg)"
|
|
57
|
+
viewBox="0 0 24 24"
|
|
58
|
+
>
|
|
59
|
+
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 9l6 6 6-6" />
|
|
60
|
+
</svg>
|
|
61
|
+
</button>
|
|
62
|
+
{#if actions}
|
|
63
|
+
<div class="flex shrink-0 items-center gap-1 pr-3">{@render actions()}</div>
|
|
64
|
+
{/if}
|
|
65
|
+
</div>
|
|
59
66
|
|
|
60
67
|
{#if isOpen}
|
|
61
68
|
<div
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
{#if content.name?.trim()}
|
|
127
127
|
<span class="truncate">{content.name}</span>
|
|
128
128
|
{#if keyBind.key && keyBind.showHint}
|
|
129
|
-
<span class="ml-1 text-[11px] font-medium tabular-nums opacity-
|
|
129
|
+
<span class="ml-1 text-[11px] font-medium tabular-nums opacity-80">
|
|
130
130
|
{keyBind.ctrlKey ? "Ctrl+" : ""}{keyBind.shiftKey ? "Shift+" : ""}{keyBind.altKey ? "Alt+" : ""}{keyBind.key}
|
|
131
131
|
</span>
|
|
132
132
|
{/if}
|
package/dist/CopiedChip.svelte
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
{#if show}
|
|
10
10
|
<span
|
|
11
|
-
class="{className} absolute flex items-center gap-1 rounded-lg bg-(--green-soft) px-2 py-1 text-[11px] font-bold whitespace-nowrap text-(--green-
|
|
11
|
+
class="{className} absolute flex items-center gap-1 rounded-lg bg-(--green-soft) px-2 py-1 text-[11px] font-bold whitespace-nowrap text-(--green-text)"
|
|
12
12
|
transition:fade={{ duration: 150 }}
|
|
13
13
|
>
|
|
14
14
|
{$T("ui.copied")}
|
package/dist/Graph/Graph.svelte
CHANGED
|
@@ -312,7 +312,7 @@
|
|
|
312
312
|
const { padding, graphWidth, graphHeight, minX, timeSpan, yMin, yMax, getX, getY } = computeGeometry()
|
|
313
313
|
|
|
314
314
|
/* Сетка в стиле PeriodChart: пунктир цветом --faint-color темы, подписи тем же цветом без прозрачности */
|
|
315
|
-
const gridColor = getComputedStyle(canvas).getPropertyValue("--faint-color").trim() || "#
|
|
315
|
+
const gridColor = getComputedStyle(canvas).getPropertyValue("--faint-color").trim() || "#5f6774"
|
|
316
316
|
ctx.strokeStyle = gridColor
|
|
317
317
|
ctx.fillStyle = gridColor
|
|
318
318
|
ctx.lineWidth = 1
|
package/dist/Input/Input.svelte
CHANGED
|
@@ -245,6 +245,6 @@
|
|
|
245
245
|
|
|
246
246
|
{#if help.regExp && !isValid}
|
|
247
247
|
<!-- Текст ошибки в потоке под полем и только когда поле невалидно: место заранее не резервируется, на соседей не наезжает -->
|
|
248
|
-
<span class="block text-[12px] font-semibold text-(--red-
|
|
248
|
+
<span class="block text-[12px] font-semibold text-(--red-text)">{$T("ui.invalid_format")}</span>
|
|
249
249
|
{/if}
|
|
250
250
|
</div>
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
<!-- Шкала Y: подписи нулевой высоты, центр каждой ровно на своей линии сетки -->
|
|
111
111
|
<div class="flex h-40 shrink-0 flex-col justify-between">
|
|
112
112
|
{#each axis.ticks as tick}
|
|
113
|
-
<div class="flex h-0 items-center justify-end text-[
|
|
113
|
+
<div class="flex h-0 items-center justify-end text-[11px] font-medium text-(--faint-color) tabular-nums">
|
|
114
114
|
{formatTick(tick)}
|
|
115
115
|
</div>
|
|
116
116
|
{/each}
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
|
|
153
153
|
<div class="flex w-full gap-1">
|
|
154
154
|
{#each values as _, i}
|
|
155
|
-
<div class="flex-1 truncate text-center text-[
|
|
155
|
+
<div class="flex-1 truncate text-center text-[11px] font-bold text-(--faint-color)">
|
|
156
156
|
{i % labelStep === 0 ? barLabel(i) : ""}
|
|
157
157
|
</div>
|
|
158
158
|
{/each}
|
package/dist/Table/Table.svelte
CHANGED
|
@@ -263,7 +263,7 @@
|
|
|
263
263
|
<div class="relative flex h-full w-full flex-col overflow-hidden rounded-[14px] border border-(--hairline-color) bg-(--back-color)">
|
|
264
264
|
<!-- Table Header -->
|
|
265
265
|
<div
|
|
266
|
-
class="grid border-b border-(--hairline-color) bg-[var(--table-header-color,var(--accent-soft))] text-[11px] font-bold tracking-[0.06em] text-[color-mix(in_srgb,var(--accent-color)
|
|
266
|
+
class="grid border-b border-(--hairline-color) bg-[var(--table-header-color,var(--accent-soft))] text-[11px] font-bold tracking-[0.06em] text-[color-mix(in_srgb,var(--accent-color)_35%,var(--font-color))] uppercase"
|
|
267
267
|
style={`grid-template-columns: ${gridTemplate}; padding-right: ${scrollbarWidth}px;`}
|
|
268
268
|
>
|
|
269
269
|
{#each header as column, index (column)}
|
|
@@ -294,7 +294,7 @@
|
|
|
294
294
|
{#if dataBuffer.clearButton}
|
|
295
295
|
<div class="flex items-center justify-center tracking-normal normal-case">
|
|
296
296
|
<Button
|
|
297
|
-
componentClass={twMerge("size-7 p-1.5 text-(--muted-color) hover:text-(--red-
|
|
297
|
+
componentClass={twMerge("size-7 p-1.5 text-(--muted-color) hover:text-(--red-text)", dataBuffer.clearClass)}
|
|
298
298
|
content={{ icon: ButtonClear, info: { text: $T("common.table.clear"), side: "left" } }}
|
|
299
299
|
onClick={clearBuffer}
|
|
300
300
|
/>
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
let files = $derived(value?.List ?? [])
|
|
59
59
|
let usedPercent = $derived(total > 0 ? Math.min(100, Math.round((used / total) * 100)) : 0)
|
|
60
60
|
/* Полоса - мастер-цвет виджета; почти заполненную ФС (закачка скоро начнёт падать) выдаёт только процент */
|
|
61
|
-
let percentColor = $derived(usedPercent >= 90 ? "var(--red-
|
|
61
|
+
let percentColor = $derived(usedPercent >= 90 ? "var(--red-text)" : usedPercent >= 75 ? "var(--orange-text)" : "var(--muted-color)")
|
|
62
62
|
|
|
63
63
|
/* Таблица файлов: путь обрезается «…», размер - уже отформатирован; действия - значки без текста: скачать и удалить (с подтверждением) */
|
|
64
64
|
type FileRow = IWidgetFileSystemFile & { SizeText: string }
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
data: {
|
|
75
75
|
icon: ButtonDelete,
|
|
76
76
|
title: $T("widget.common.delete"),
|
|
77
|
-
class: "bg-transparent px-0 text-(--red-
|
|
77
|
+
class: "bg-transparent px-0 text-(--red-text) hover:bg-(--red-color)/10",
|
|
78
78
|
onClick: (row: FileRow) => (pendingDelete = row.Name),
|
|
79
79
|
},
|
|
80
80
|
},
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
>
|
|
186
186
|
{/if}
|
|
187
187
|
{#if downloadError}
|
|
188
|
-
<span class="mt-2 text-[12px] text-(--red-
|
|
188
|
+
<span class="mt-2 text-[12px] text-(--red-text)" transition:slide={{ duration: 100 }}>{downloadError}</span>
|
|
189
189
|
{/if}
|
|
190
190
|
</div>
|
|
191
191
|
|
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
</div>
|
|
208
208
|
{/if}
|
|
209
209
|
{#if uploadError}
|
|
210
|
-
<span class="text-[12px] text-(--red-
|
|
210
|
+
<span class="text-[12px] text-(--red-text)" transition:slide={{ duration: 100 }}>{uploadError}</span>
|
|
211
211
|
{/if}
|
|
212
212
|
</div>
|
|
213
213
|
{/if}
|
|
@@ -328,7 +328,7 @@
|
|
|
328
328
|
<div class="flex flex-col items-center justify-center leading-none">
|
|
329
329
|
<div
|
|
330
330
|
class="flex size-8 shrink-0 items-center justify-center [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full
|
|
331
|
-
{isFresh(board) ? 'text-(--green-
|
|
331
|
+
{isFresh(board) ? 'text-(--green-text)' : 'text-(--red-text)'}"
|
|
332
332
|
style="rotate: {board.heading}deg;"
|
|
333
333
|
>
|
|
334
334
|
{#if markerIcon}
|
|
@@ -366,29 +366,29 @@
|
|
|
366
366
|
<div class="flex items-center gap-1.5">
|
|
367
367
|
<span class="size-2 shrink-0 rounded-full {isFresh(board) ? 'bg-(--green-color)' : 'bg-(--red-color)'}"></span>
|
|
368
368
|
<h6 class="text-sm leading-none font-bold">{board.name}</h6>
|
|
369
|
-
{#if board.name !== board.group}<span class="text-[
|
|
369
|
+
{#if board.name !== board.group}<span class="text-[11px] font-semibold uppercase text-(--muted-color)">{board.group}</span>{/if}
|
|
370
370
|
</div>
|
|
371
371
|
|
|
372
372
|
<div class="grid grid-cols-2 gap-x-3 gap-y-1 rounded-lg bg-(--container-color) p-2 text-xs">
|
|
373
373
|
<div>
|
|
374
|
-
<p class="text-[
|
|
374
|
+
<p class="text-[11px] font-semibold tracking-wide uppercase text-(--muted-color)">Lat / Lon</p>
|
|
375
375
|
<p class="font-medium">{board.lat.toFixed(6)}, {board.lon.toFixed(6)}</p>
|
|
376
376
|
</div>
|
|
377
377
|
<div>
|
|
378
|
-
<p class="text-[
|
|
378
|
+
<p class="text-[11px] font-semibold tracking-wide uppercase text-(--muted-color)">Alt</p>
|
|
379
379
|
<p class="font-medium">{board.alt.toFixed(1)} m</p>
|
|
380
380
|
</div>
|
|
381
381
|
<div>
|
|
382
|
-
<p class="text-[
|
|
382
|
+
<p class="text-[11px] font-semibold tracking-wide uppercase text-(--muted-color)">Heading</p>
|
|
383
383
|
<p class="font-medium">{board.heading.toFixed(1)}°</p>
|
|
384
384
|
</div>
|
|
385
385
|
<div>
|
|
386
|
-
<p class="text-[
|
|
386
|
+
<p class="text-[11px] font-semibold tracking-wide uppercase text-(--muted-color)">Satellites</p>
|
|
387
387
|
<p class="font-medium">{board.sat}</p>
|
|
388
388
|
</div>
|
|
389
389
|
</div>
|
|
390
390
|
|
|
391
|
-
{#if board.sn}<p class="text-center font-mono text-[
|
|
391
|
+
{#if board.sn}<p class="text-center font-mono text-[11px] tracking-wide uppercase">{board.sn}</p>{/if}
|
|
392
392
|
|
|
393
393
|
<div class="grid grid-cols-[1fr_auto_1fr] items-center border-t border-(--hairline-color) pt-1">
|
|
394
394
|
<div class="relative flex items-center justify-self-start">
|
|
@@ -431,7 +431,7 @@
|
|
|
431
431
|
</button>
|
|
432
432
|
|
|
433
433
|
<button
|
|
434
|
-
class="cursor-pointer justify-self-end rounded-lg border-none bg-transparent p-0.5 text-(--red-
|
|
434
|
+
class="cursor-pointer justify-self-end rounded-lg border-none bg-transparent p-0.5 text-(--red-text) transition hover:opacity-80"
|
|
435
435
|
aria-label="Hide"
|
|
436
436
|
title="Hide"
|
|
437
437
|
onclick={() => hideBoard(board.group)}
|
|
@@ -503,10 +503,10 @@
|
|
|
503
503
|
/>
|
|
504
504
|
<span
|
|
505
505
|
class="text-[12px] font-semibold tabular-nums {freshCount === 0
|
|
506
|
-
? 'text-(--red-
|
|
506
|
+
? 'text-(--red-text)'
|
|
507
507
|
: freshCount < groupList.length
|
|
508
508
|
? 'text-(--muted-color)'
|
|
509
|
-
: 'text-(--green-
|
|
509
|
+
: 'text-(--green-text)'}"
|
|
510
510
|
use:hint={{ text: $T("widget.map.hint.boards"), enabled: $hints }}
|
|
511
511
|
>
|
|
512
512
|
{freshCount} / {groupList.length}
|
|
@@ -608,7 +608,7 @@
|
|
|
608
608
|
</label>
|
|
609
609
|
{/each}
|
|
610
610
|
{#if plan.sprayControl === "none"}
|
|
611
|
-
<p class="m-0 text-[12px] text-(--red-
|
|
611
|
+
<p class="m-0 text-[12px] text-(--red-text)">{$T("widget.mission.spray.noneWarn")}</p>
|
|
612
612
|
{/if}
|
|
613
613
|
</div>
|
|
614
614
|
{#if plan.sprayControl === "servo"}
|
|
@@ -660,7 +660,7 @@
|
|
|
660
660
|
/>°
|
|
661
661
|
</span>
|
|
662
662
|
<span class="w-16 text-right font-semibold">{areaText(fieldArea(field, []))}</span>
|
|
663
|
-
<button class="cursor-pointer border-none bg-transparent p-0 text-(--red-
|
|
663
|
+
<button class="cursor-pointer border-none bg-transparent p-0 text-(--red-text)" onclick={() => removeField(i)} aria-label="Delete"
|
|
664
664
|
>✕</button
|
|
665
665
|
>
|
|
666
666
|
</span>
|
|
@@ -673,7 +673,7 @@
|
|
|
673
673
|
<span class="flex items-center gap-2"><span class="size-2.5 rounded-sm bg-[#dc2626]"></span>{$T("widget.mission.hole")} {i + 1}</span>
|
|
674
674
|
<span class="flex items-center gap-2">
|
|
675
675
|
<span class="font-semibold">{areaText(fieldArea(hole, []))}</span>
|
|
676
|
-
<button class="cursor-pointer border-none bg-transparent p-0 text-(--red-
|
|
676
|
+
<button class="cursor-pointer border-none bg-transparent p-0 text-(--red-text)" onclick={() => removeHole(i)} aria-label="Delete"
|
|
677
677
|
>✕</button
|
|
678
678
|
>
|
|
679
679
|
</span>
|
|
@@ -697,7 +697,7 @@
|
|
|
697
697
|
</div>
|
|
698
698
|
{/if}
|
|
699
699
|
{#if fields.length || plan.holes.length}
|
|
700
|
-
<button class="w-max cursor-pointer border-none bg-transparent p-0 text-[12px] text-(--red-
|
|
700
|
+
<button class="w-max cursor-pointer border-none bg-transparent p-0 text-[12px] text-(--red-text) underline" onclick={clearAll}>
|
|
701
701
|
{$T("widget.mission.clear")}
|
|
702
702
|
</button>
|
|
703
703
|
{/if}
|
|
@@ -741,7 +741,7 @@
|
|
|
741
741
|
</div>
|
|
742
742
|
{/if}
|
|
743
743
|
{#if stats?.unreachable}
|
|
744
|
-
<p class="m-0 text-[12px] text-(--red-
|
|
744
|
+
<p class="m-0 text-[12px] text-(--red-text)">{$T("widget.mission.unreachable")}: {stats.unreachable}</p>
|
|
745
745
|
{/if}
|
|
746
746
|
</div>
|
|
747
747
|
</div>
|
|
@@ -412,8 +412,7 @@
|
|
|
412
412
|
onUpdate={(v) => Number.isFinite(Number(v)) && setPoint(i, { speed: Math.min(40, Math.max(0.5, Number(v))) })}
|
|
413
413
|
/>
|
|
414
414
|
<span class="text-right text-(--muted-color)">{leg ? `${fmt(leg.length, 0)} ${$T("widget.mission.m")}` : "-"}</span>
|
|
415
|
-
<button class="cursor-pointer border-none bg-transparent p-0 text-(--red-
|
|
416
|
-
>✕</button
|
|
415
|
+
<button class="cursor-pointer border-none bg-transparent p-0 text-(--red-text)" onclick={() => removePoint(i)} aria-label="Delete">✕</button
|
|
417
416
|
>
|
|
418
417
|
</div>
|
|
419
418
|
{/each}
|
|
@@ -426,7 +425,7 @@
|
|
|
426
425
|
{$T("widget.route.returnHome")}
|
|
427
426
|
</label>
|
|
428
427
|
{#if route.points.length}
|
|
429
|
-
<button class="w-max cursor-pointer border-none bg-transparent p-0 text-[12px] text-(--red-
|
|
428
|
+
<button class="w-max cursor-pointer border-none bg-transparent p-0 text-[12px] text-(--red-text) underline" onclick={clearAll}
|
|
430
429
|
>{$T("widget.mission.clear")}</button
|
|
431
430
|
>
|
|
432
431
|
{/if}
|
|
@@ -14,7 +14,16 @@
|
|
|
14
14
|
import { readPersistedCollapsed, writePersistedCollapsed } from "../widgetCollapse"
|
|
15
15
|
import { createFlightSim } from "../flightSim"
|
|
16
16
|
|
|
17
|
-
let {
|
|
17
|
+
let {
|
|
18
|
+
wrapperClass = "",
|
|
19
|
+
componentClass = "",
|
|
20
|
+
label = {},
|
|
21
|
+
value: deviceValue,
|
|
22
|
+
limits,
|
|
23
|
+
isTest = false,
|
|
24
|
+
collapsed = $bindable(false),
|
|
25
|
+
persistKey,
|
|
26
|
+
}: IWidgetPFDProps = $props()
|
|
18
27
|
|
|
19
28
|
/* В режиме имитации данные устройства не учитываются, кадр полёта каждые 50 мс */
|
|
20
29
|
let simValue: IWidgetPFDData | undefined = $state()
|
|
@@ -244,7 +253,7 @@
|
|
|
244
253
|
let ekf = $derived(num(value?.EKF, -1))
|
|
245
254
|
/* Пороги дисперсии EKF - как в Mission Planner: от 0.5 внимание, от 0.8 тревога; неисправный датчик - тревога */
|
|
246
255
|
let navState: "ok" | "warn" | "bad" | "none" = $derived(sensErr > 0 || ekf >= 0.8 ? "bad" : ekf >= 0.5 ? "warn" : ekf >= 0 ? "ok" : "none")
|
|
247
|
-
const NAV_COLOR = { ok: "var(--green-
|
|
256
|
+
const NAV_COLOR = { ok: "var(--green-text)", warn: "var(--orange-text)", bad: "var(--red-text)", none: "var(--faint-color)" }
|
|
248
257
|
|
|
249
258
|
/* Сообщения автопилота (STATUSTEXT): новое - по смене MsgSeq, последнее висит 10 с, история из 5 - в подсказке */
|
|
250
259
|
let SEVERITY = $derived(Array.from({ length: 8 }, (_, i) => $T(`pfd.sev.${i}`)))
|
|
@@ -455,7 +464,7 @@
|
|
|
455
464
|
<WidgetHeader icon={PlaneIcon} label={label?.name || $T("pfd.title")} bind:collapsed {hints}>
|
|
456
465
|
{#snippet right()}
|
|
457
466
|
<div class="flex items-center gap-4">
|
|
458
|
-
<span class="text-[12px] font-semibold tabular-nums {stale ? 'text-(--red-
|
|
467
|
+
<span class="text-[12px] font-semibold tabular-nums {stale ? 'text-(--red-text)' : 'text-(--muted-color)'}">{fmtAge(age)}</span>
|
|
459
468
|
</div>
|
|
460
469
|
{/snippet}
|
|
461
470
|
</WidgetHeader>
|
|
@@ -474,7 +483,7 @@
|
|
|
474
483
|
class="flex flex-wrap items-center justify-between gap-x-5 gap-y-1.5 rounded-[10px] border border-(--hairline-color) bg-(--back-color) px-3 py-2 text-[12px] font-semibold tabular-nums"
|
|
475
484
|
>
|
|
476
485
|
<div class="flex min-w-0 items-center gap-2" data-tip="system">
|
|
477
|
-
<span class="shrink-0 text-[
|
|
486
|
+
<span class="shrink-0 text-[11px] tracking-wide whitespace-nowrap text-(--faint-color) uppercase">{$T("pfd.system")}</span>
|
|
478
487
|
<span class="rounded-md px-1.5 py-0.5 {armed ? 'bg-(--red-color) text-white' : 'bg-(--container-color) text-(--muted-color)'}" data-tip="armed"
|
|
479
488
|
>{armed ? "ARMED" : "DISARMED"}</span
|
|
480
489
|
>
|
|
@@ -486,21 +495,21 @@
|
|
|
486
495
|
</span>
|
|
487
496
|
</div>
|
|
488
497
|
<div class="flex min-w-0 items-center gap-2" data-tip="gps">
|
|
489
|
-
<span class="shrink-0 text-[
|
|
490
|
-
<span class={fix < 3 ? "text-(--orange-
|
|
491
|
-
{#if hdop >= 0}<span class={hdop > 2 ? "text-(--orange-
|
|
498
|
+
<span class="shrink-0 text-[11px] tracking-wide whitespace-nowrap text-(--faint-color) uppercase">GPS</span>
|
|
499
|
+
<span class={fix < 3 ? "text-(--orange-text)" : ""}>{FIX[fix] ?? fix} · {$T("pfd.sat", { n: num(value?.Sat) })}</span>
|
|
500
|
+
{#if hdop >= 0}<span class={hdop > 2 ? "text-(--orange-text)" : ""}>HDOP {fmt(hdop, 2)}</span>{/if}
|
|
492
501
|
</div>
|
|
493
502
|
{#if rssi >= 0 || remRssi >= 0 || drop >= 0}
|
|
494
503
|
<div class="flex min-w-0 items-center gap-2" data-tip="link">
|
|
495
|
-
<span class="shrink-0 text-[
|
|
496
|
-
<span class={Math.min(rssi, remRssi) >= 0 && Math.min(rssi, remRssi) < 30 ? "text-(--red-
|
|
504
|
+
<span class="shrink-0 text-[11px] tracking-wide whitespace-nowrap text-(--faint-color) uppercase">{$T("pfd.link")}</span>
|
|
505
|
+
<span class={Math.min(rssi, remRssi) >= 0 && Math.min(rssi, remRssi) < 30 ? "text-(--red-text)" : ""}
|
|
497
506
|
>{rssi >= 0 ? Math.round(rssi) : "-"} / {remRssi >= 0 ? Math.round(remRssi) : "-"} %</span
|
|
498
507
|
>
|
|
499
|
-
{#if drop >= 0}<span class={drop > 5 ? "text-(--orange-
|
|
508
|
+
{#if drop >= 0}<span class={drop > 5 ? "text-(--orange-text)" : ""}>{$T("pfd.link.drop", { v: fmt(drop) })}</span>{/if}
|
|
500
509
|
</div>
|
|
501
510
|
{/if}
|
|
502
|
-
<div class="flex items-center gap-2 {battLow ? 'text-(--red-
|
|
503
|
-
<span class="shrink-0 text-[
|
|
511
|
+
<div class="flex items-center gap-2 {battLow ? 'text-(--red-text)' : ''}" data-tip="battery">
|
|
512
|
+
<span class="shrink-0 text-[11px] tracking-wide whitespace-nowrap text-(--faint-color) uppercase">{$T("pfd.battery")}</span>
|
|
504
513
|
<span>{battText}</span>
|
|
505
514
|
</div>
|
|
506
515
|
</div>
|
|
@@ -764,7 +773,7 @@
|
|
|
764
773
|
>
|
|
765
774
|
{#if wp}
|
|
766
775
|
<div class="flex min-w-0 items-center gap-2" data-tip="route">
|
|
767
|
-
<span class="shrink-0 text-[
|
|
776
|
+
<span class="shrink-0 text-[11px] font-semibold tracking-wide whitespace-nowrap text-(--faint-color) uppercase">{$T("pfd.route")}</span>
|
|
768
777
|
<span
|
|
769
778
|
><span style="color:#22d3ee">◆</span>
|
|
770
779
|
{$T("pfd.route.line", { n: wp.num, d: fmtDistance(wp.dist), x: fmtDistance(Math.abs(wp.xte)) })}{eta ? ` · ${eta}` : ""}</span
|
|
@@ -773,22 +782,22 @@
|
|
|
773
782
|
{/if}
|
|
774
783
|
{#if home}
|
|
775
784
|
<div class="flex min-w-0 items-center gap-2" data-tip="home">
|
|
776
|
-
<span class="shrink-0 text-[
|
|
785
|
+
<span class="shrink-0 text-[11px] font-semibold tracking-wide whitespace-nowrap text-(--faint-color) uppercase">{$T("pfd.home")}</span>
|
|
777
786
|
<span><span style="color:#22c55e">●</span> {fmtDistance(home.distance)} · {Math.round(home.bearing)}°</span>
|
|
778
787
|
</div>
|
|
779
788
|
{/if}
|
|
780
789
|
{#if track !== null}
|
|
781
790
|
<div class="flex items-center gap-2" data-tip="track">
|
|
782
|
-
<span class="shrink-0 text-[
|
|
791
|
+
<span class="shrink-0 text-[11px] font-semibold tracking-wide whitespace-nowrap text-(--faint-color) uppercase">{$T("pfd.track")}</span>
|
|
783
792
|
<span><span style="color:#ec4899">▲</span> {Math.round(track)}°</span>
|
|
784
793
|
</div>
|
|
785
794
|
{/if}
|
|
786
795
|
<div class="flex min-w-0 items-center gap-2" data-tip="position">
|
|
787
|
-
<span class="shrink-0 text-[
|
|
796
|
+
<span class="shrink-0 text-[11px] font-semibold tracking-wide whitespace-nowrap text-(--faint-color) uppercase">{$T("pfd.position")}</span>
|
|
788
797
|
<span>{latLonText} · MSL {$T("pfd.u.m", { n: Math.round(altMsl) })}</span>
|
|
789
798
|
</div>
|
|
790
799
|
<div class="flex min-w-0 items-center gap-2" data-tip="air">
|
|
791
|
-
<span class="shrink-0 text-[
|
|
800
|
+
<span class="shrink-0 text-[11px] font-semibold tracking-wide whitespace-nowrap text-(--faint-color) uppercase">{$T("pfd.air")}</span>
|
|
792
801
|
<span>{fmt(num(value?.Press))} {$T("pfd.u.hpa")} · {fmt(num(value?.Temp))} °C</span>
|
|
793
802
|
</div>
|
|
794
803
|
</div>
|
|
@@ -76,17 +76,17 @@
|
|
|
76
76
|
const text = $T(key)
|
|
77
77
|
return text === key ? (kind === "name" ? code : "") : text
|
|
78
78
|
}
|
|
79
|
-
const percentColor = (p: number) => (p >= 90 ? "var(--red-
|
|
79
|
+
const percentColor = (p: number) => (p >= 90 ? "var(--red-text)" : p >= 75 ? "var(--orange-text)" : "var(--muted-color)")
|
|
80
80
|
|
|
81
81
|
let STATES = $derived(["running", "ready", "blocked", "suspended", "deleted"].map((state) => $T(`widget.stack.state.${state}`)))
|
|
82
|
-
const stackColor = (bytes: number) => (bytes < warnBytes ? "var(--red-
|
|
82
|
+
const stackColor = (bytes: number) => (bytes < warnBytes ? "var(--red-text)" : bytes < warnBytes * 2 ? "var(--orange-text)" : "")
|
|
83
83
|
|
|
84
84
|
type TaskRow = { Name: string; StateText: string; Prio: number; CoreText: string; CpuText: string; FreeMinText: string }
|
|
85
85
|
let tasks = $derived([...(value?.Tasks ?? [])].sort((a, b) => a.FreeMin - b.FreeMin))
|
|
86
86
|
let hasCore = $derived(tasks.some((t) => t.Core !== undefined))
|
|
87
87
|
/* Загрузка задачи, % одного ядра с прошлого ответа: от 50 % - оранжевая, от 80 % - красная; IDLE - это свободное время ядра, не подсвечивается */
|
|
88
88
|
let hasCpu = $derived(tasks.some((t) => t.CPU !== undefined))
|
|
89
|
-
const cpuColor = (cpu: number) => (cpu >= 80 ? "var(--red-
|
|
89
|
+
const cpuColor = (cpu: number) => (cpu >= 80 ? "var(--red-text)" : cpu >= 50 ? "var(--orange-text)" : "")
|
|
90
90
|
let lowCount = $derived(tasks.filter((t) => t.FreeMin < warnBytes).length)
|
|
91
91
|
|
|
92
92
|
/* Порядок фиксированный - по возрастанию запаса стека: при автоопросе сортировка кликом сбрасывалась бы каждый ответ */
|
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
<div class="flex items-baseline justify-between gap-2 text-[12px] text-(--muted-color)">
|
|
203
203
|
<span use:hint={{ text: $T("widget.stack.hint.tasks"), enabled: $hints }}>{$T("widget.stack.tasks", { n: tasks.length })}</span>
|
|
204
204
|
{#if lowCount}
|
|
205
|
-
<span class="font-semibold text-(--red-
|
|
205
|
+
<span class="font-semibold text-(--red-text)" use:hint={{ text: $T("widget.stack.hint.low"), enabled: $hints }}
|
|
206
206
|
>{$T("widget.stack.low", { warn: warnBytes, n: lowCount })}</span
|
|
207
207
|
>
|
|
208
208
|
{/if}
|
|
@@ -270,7 +270,7 @@
|
|
|
270
270
|
class="flex w-full max-w-80 flex-col gap-3 rounded-[16px] border border-(--hairline-color) bg-(--back-color) p-4 shadow-(--elevation-3)"
|
|
271
271
|
transition:scale={{ duration: 150, start: 0.96 }}
|
|
272
272
|
>
|
|
273
|
-
<div class="flex items-center gap-2 text-(--orange-
|
|
273
|
+
<div class="flex items-center gap-2 text-(--orange-text)">
|
|
274
274
|
<WarningIcon />
|
|
275
275
|
<h4 class="text-[15px] font-semibold text-(--font-color)">{$T("widget.wifi.ap_confirm_title")}</h4>
|
|
276
276
|
</div>
|
package/dist/theme.css
CHANGED
|
@@ -332,8 +332,9 @@
|
|
|
332
332
|
--hairline-color: rgb(17 24 39 / 0.11);
|
|
333
333
|
|
|
334
334
|
--font-color: #10141a;
|
|
335
|
-
|
|
336
|
-
--
|
|
335
|
+
/* Вторичный и подсказочный текст: контраст не ниже 4.8:1 на всех фонах темы (WCAG AA) */
|
|
336
|
+
--muted-color: #3f4651;
|
|
337
|
+
--faint-color: #5f6774;
|
|
337
338
|
|
|
338
339
|
--accent-color: #4175c8;
|
|
339
340
|
--accent-hover: #33599e;
|
|
@@ -359,6 +360,17 @@
|
|
|
359
360
|
--purple-color: #855dd0;
|
|
360
361
|
--pink-color: #c84182;
|
|
361
362
|
|
|
363
|
+
/* Цветной текст: в светлой теме - те же цвета, что и заливки (контраст ≥ 4.5:1 на белом) */
|
|
364
|
+
--gray-text: var(--muted-color);
|
|
365
|
+
--red-text: var(--red-color);
|
|
366
|
+
--orange-text: var(--orange-color);
|
|
367
|
+
--yellow-text: var(--yellow-color);
|
|
368
|
+
--green-text: var(--green-color);
|
|
369
|
+
--cyan-text: var(--cyan-color);
|
|
370
|
+
--blue-text: var(--blue-color);
|
|
371
|
+
--purple-text: var(--purple-color);
|
|
372
|
+
--pink-text: var(--pink-color);
|
|
373
|
+
|
|
362
374
|
/* Мягкие тинты для фонов блоков и подсветки строк */
|
|
363
375
|
--gray-soft: #f0f2f5;
|
|
364
376
|
--red-soft: #fbeae8;
|
|
@@ -380,8 +392,9 @@
|
|
|
380
392
|
--hairline-color: rgb(255 255 255 / 0.1);
|
|
381
393
|
|
|
382
394
|
--font-color: #e9ebf0;
|
|
383
|
-
|
|
384
|
-
--
|
|
395
|
+
/* Вторичный и подсказочный текст: контраст не ниже 5.6:1 на всех фонах темы (WCAG AA) */
|
|
396
|
+
--muted-color: #b4bbc8;
|
|
397
|
+
--faint-color: #939cac;
|
|
385
398
|
|
|
386
399
|
--accent-color: #3863a8;
|
|
387
400
|
--accent-hover: #4a78bf;
|
|
@@ -407,6 +420,17 @@
|
|
|
407
420
|
--purple-color: #754bc3;
|
|
408
421
|
--pink-color: #a8386e;
|
|
409
422
|
|
|
423
|
+
/* Цветной текст: тёмные заливки как текст нечитаемы, поэтому светлые оттенки с контрастом ≥ 6:1 на всех фонах темы */
|
|
424
|
+
--gray-text: var(--muted-color);
|
|
425
|
+
--red-text: #f2867d;
|
|
426
|
+
--orange-text: #e6a65c;
|
|
427
|
+
--yellow-text: #dcc067;
|
|
428
|
+
--green-text: #5cc896;
|
|
429
|
+
--cyan-text: #5fc6dc;
|
|
430
|
+
--blue-text: #83abee;
|
|
431
|
+
--purple-text: #b49cf2;
|
|
432
|
+
--pink-text: #f084b8;
|
|
433
|
+
|
|
410
434
|
--gray-soft: rgb(255 255 255 / 0.06);
|
|
411
435
|
--red-soft: rgb(168 64 56 / 0.18);
|
|
412
436
|
--orange-soft: rgb(130 90 43 / 0.18);
|
package/dist/types.d.ts
CHANGED