poe-svelte-ui-lib 1.93.0 → 1.95.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/dist/WidgetDeviceInfo/WidgetDeviceInfo.svelte +1 -1
- package/dist/WidgetFileSystem/WidgetFileSystem.svelte +1 -1
- package/dist/WidgetMap/README.md +3 -2
- package/dist/WidgetMap/WidgetMap.svelte +1 -1
- package/dist/WidgetMap/mission/MapAgro.svelte +81 -21
- package/dist/WidgetMap/mission/MapRoute.svelte +36 -17
- package/dist/WidgetMap/mission/MissionLayers.svelte +21 -3
- package/dist/WidgetMap/mission/MissionLayers.svelte.d.ts +1 -0
- package/dist/WidgetMap/mission/SpinnerIcon.svelte +3 -4
- package/dist/WidgetMap/mission/coverage.js +173 -74
- package/dist/WidgetMap/mission/fit.d.ts +3 -0
- package/dist/WidgetMap/mission/fit.js +36 -0
- package/dist/WidgetMap/mission/plan.js +46 -34
- package/dist/WidgetPFD/WidgetPFD.svelte +1 -1
- package/dist/WidgetRGBStrip/WidgetRGBStrip.svelte +1 -1
- package/dist/WidgetStackInfo/WidgetStackInfo.svelte +1 -1
- package/dist/WidgetWiFi/README.md +24 -24
- package/dist/WidgetWiFi/WidgetWiFi.svelte +1 -1
- package/dist/hints.d.ts +1 -1
- package/dist/hints.js +8 -7
- package/dist/locales/translations.js +17 -6
- package/package.json +1 -1
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
}: IWidgetDeviceInfoProps = $props()
|
|
27
27
|
|
|
28
28
|
/* Подсказки - кнопка «?» в шапке, выбор общий для всех WidgetDeviceInfo */
|
|
29
|
-
const hints = hintsSetting("WidgetDeviceInfo")
|
|
29
|
+
const hints = $derived(hintsSetting("WidgetDeviceInfo", persistKey))
|
|
30
30
|
|
|
31
31
|
let confirmReset = $state(false)
|
|
32
32
|
const factoryReset = () => {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
}: IWidgetFileSystemProps = $props()
|
|
31
31
|
|
|
32
32
|
/* Подсказки - кнопка «?» в шапке, выбор общий для всех WidgetFileSystem */
|
|
33
|
-
const hints = hintsSetting("WidgetFileSystem")
|
|
33
|
+
const hints = $derived(hintsSetting("WidgetFileSystem", persistKey))
|
|
34
34
|
|
|
35
35
|
let accentStyle = $derived(widgetAccentStyle(componentClass ?? ""))
|
|
36
36
|
|
package/dist/WidgetMap/README.md
CHANGED
|
@@ -52,6 +52,7 @@ Telemetry_SendObject(g_telemetry_stream, g_mcfg.DevSN, "Data", dev, sizeof(dev)
|
|
|
52
52
|
- **Свежесть.** Маркер зелёный, пока значения объекта менялись в пределах таймаута. Без изменений он красный, а ещё через таймаут скрывается до новых данных.
|
|
53
53
|
- **Траектория.** Точка добавляется только при смене координат. Курс и высота могут меняться чаще.
|
|
54
54
|
- **«Показать все»** в шапке вписывает все видимые объекты в экран, один объект приближает. Сама карта вид не меняет, ведь объектов может быть много.
|
|
55
|
+
- **«Показать все» в «Маршруте» и «Агро»** вписывает весь маршрут или все поля, зоны и точку взлёта. Когда карта появляется (загрузка страницы, разворачивание виджета), она вписывается сама, без анимации.
|
|
55
56
|
- **Клик по маркеру** открывает карточку: имя, группа, координаты, высота, курс, спутники, серийный номер. Кнопки «Копировать» (всё в буфер обмена) и «Скрыть» (до следующего обновления объекта).
|
|
56
57
|
- **Слежение.** Кнопка «Следить» в карточке залипает. Один отслеживаемый объект всегда в центре карты. Несколько вписываются в карту при включении слежения, дальше карта держит в центре их середину. Масштаб остаётся за оператором: карта сама отдаляется, только когда объект уходит за край. Под маркером отслеживаемого объекта точка, она видна и при закрытой карточке. Если объект пропал с карты, слежение за ним продолжится, когда он появится снова. «Скрыть» снимает слежение.
|
|
57
58
|
- **Шапка** показывает, сколько объектов на связи. Зелёный значит все, серый часть, красный ни одного.
|
|
@@ -94,7 +95,7 @@ Telemetry_SendObject(g_telemetry_stream, g_mcfg.DevSN, "Data", dev, sizeof(dev)
|
|
|
94
95
|
|
|
95
96
|
`mode="agro"` превращает карту в генератор миссии для агродрона. Оператор обводит на карте одно или несколько полей и запретные зоны, задаёт угол проходов и ширину захвата. Карта строит траекторию, которая покрывает все поля, обходит зоны и выключает распылитель на разворотах и переходах. Готовую миссию можно скачать, скопировать или отправить на борт в формате QGC WPL 110 для автопилота ArduPilot.
|
|
96
97
|
|
|
97
|
-
Аппараты из `groups` рисуются поверх миссии так же, как в режиме «Объекты»: маркер, карточка по клику и траектория, если она включена. План хранит владелец карты через `plan` и `onPlanChange`, миссия уходит наружу через `onSend`. Без `plan` показываются три демо-поля под Минском: большое с рощей, опорой ЛЭП и постройкой, среднее
|
|
98
|
+
Аппараты из `groups` рисуются поверх миссии так же, как в режиме «Объекты»: маркер, карточка по клику и траектория, если она включена. План хранит владелец карты через `plan` и `onPlanChange`, миссия уходит наружу через `onSend`. Без `plan` показываются три демо-поля под Минском: большое сложной формы с прорезью и выступами, рощей, опорой ЛЭП и постройкой, среднее с выступами, крюком и прудом, малое с вырезом и деревом. Между полями стоит большая треугольная зона, внизу зона, которая задевает все три поля; перелёты их обходят. Ширина захвата по умолчанию 5 м, отступ от границ 2 м. Ширина захвата по умолчанию 5 м, отступ от границ 2 м.
|
|
98
99
|
|
|
99
100
|
### Как пользоваться
|
|
100
101
|
|
|
@@ -108,7 +109,7 @@ Telemetry_SendObject(g_telemetry_stream, g_mcfg.DevSN, "Data", dev, sizeof(dev)
|
|
|
108
109
|
5. Блок **«Итог»** показывает контуры, площадь к обработке, покрытие, длину пути, время, расход раствора и число дозаправок.
|
|
109
110
|
6. **«Скачать»**, **«Копировать»** или **«Отправить»** миссию.
|
|
110
111
|
|
|
111
|
-
Точки контуров видны и перетаскиваются, только пока включён «Редактор полей» или «Редактор запретных зон». Потяните за середину стороны, чтобы добавить точку. Правый клик удаляет точку. Поля и зоны удаляются крестиком в блоке «Итог». Режим выключается той же кнопкой, кнопкой «Готово» или Esc.
|
|
112
|
+
Точки контуров видны и перетаскиваются, только пока включён «Редактор полей» или «Редактор запретных зон». Потяните за середину стороны, чтобы добавить точку. Правый клик удаляет точку. Поля и зоны удаляются крестиком в блоке «Итог». Там же видно, где какое поле или зона: при наведении на строку контур подсвечивается на карте, нажатие на название приближает к нему карту. Подписей на самой карте нет, маленькой зоне их некуда поставить. Режим выключается той же кнопкой, кнопкой «Готово» или Esc.
|
|
112
113
|
|
|
113
114
|
### Как строится траектория
|
|
114
115
|
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
}: IWidgetMapProps = $props()
|
|
45
45
|
|
|
46
46
|
/* Подсказки - кнопка «?» в шапке, выбор общий для всех WidgetMap */
|
|
47
|
-
const hints = hintsSetting("WidgetMap")
|
|
47
|
+
const hints = $derived(hintsSetting("WidgetMap", persistKey))
|
|
48
48
|
|
|
49
49
|
let accentStyle = $derived(widgetAccentStyle(componentClass ?? ""))
|
|
50
50
|
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
import * as UI from "../.."
|
|
12
12
|
import { MapLibre, NavigationControl, ScaleControl, FullScreenControl, CustomControl } from "../mapWrapper"
|
|
13
13
|
import MissionLayers from "./MissionLayers.svelte"
|
|
14
|
+
import FitIcon from "../FitIcon.svelte"
|
|
15
|
+
import { createAutoFit, fitMapTo } from "./fit"
|
|
14
16
|
import MissionSim from "./MissionSim.svelte"
|
|
15
17
|
import SpinnerIcon from "./SpinnerIcon.svelte"
|
|
16
18
|
import HelpText from "./HelpText.svelte"
|
|
@@ -52,7 +54,7 @@
|
|
|
52
54
|
onSend,
|
|
53
55
|
}: Props = $props()
|
|
54
56
|
|
|
55
|
-
const hints = hintsSetting("WidgetMap")
|
|
57
|
+
const hints = $derived(hintsSetting("WidgetMap", persistKey))
|
|
56
58
|
let accentStyle = $derived(widgetAccentStyle(componentClass ?? ""))
|
|
57
59
|
|
|
58
60
|
let hasMergedPersisted = false
|
|
@@ -285,6 +287,30 @@
|
|
|
285
287
|
geometry: { type: "LineString", coordinates: s.points },
|
|
286
288
|
})),
|
|
287
289
|
})
|
|
290
|
+
/* Поле или зона, на которые навели в списке «Итога», подсвечиваются на карте; нажатие на название приближает карту к контуру */
|
|
291
|
+
let highlighted = $state<{ kind: "field" | "hole"; index: number } | null>(null)
|
|
292
|
+
let highlightGeo = $derived.by(() => {
|
|
293
|
+
if (!highlighted) return null
|
|
294
|
+
const ring = highlighted.kind === "field" ? fields[highlighted.index] : plan.holes[highlighted.index]
|
|
295
|
+
if (!ring || ring.length < 3) return null
|
|
296
|
+
return {
|
|
297
|
+
type: "FeatureCollection",
|
|
298
|
+
features: [{ type: "Feature", properties: { kind: highlighted.kind }, geometry: { type: "Polygon", coordinates: [closed(ring)] } }],
|
|
299
|
+
}
|
|
300
|
+
})
|
|
301
|
+
const flyTo = (ring: LngLat[]) => {
|
|
302
|
+
if (!mapInstance || ring.length < 3) return
|
|
303
|
+
const lngs = ring.map((p) => p[0])
|
|
304
|
+
const lats = ring.map((p) => p[1])
|
|
305
|
+
mapInstance.fitBounds(
|
|
306
|
+
[
|
|
307
|
+
[Math.min(...lngs), Math.min(...lats)],
|
|
308
|
+
[Math.max(...lngs), Math.max(...lats)],
|
|
309
|
+
],
|
|
310
|
+
{ padding: 60, maxZoom: 19, duration: 500 },
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
|
|
288
314
|
let draftGeo = $derived({
|
|
289
315
|
type: "FeatureCollection",
|
|
290
316
|
features: drawing
|
|
@@ -406,6 +432,14 @@
|
|
|
406
432
|
void isDarkMode
|
|
407
433
|
mapColors = readMapColors(rootEl)
|
|
408
434
|
})
|
|
435
|
+
/* «Показать все» в шапке и вписывание при появлении карты */
|
|
436
|
+
const missionPoints = (): LngLat[] => [...fields.flat(), ...plan.holes.flat(), ...(plan.start ? [plan.start] : [])]
|
|
437
|
+
const fitAll = (animate = true) => fitMapTo(mapInstance, missionPoints(), animate)
|
|
438
|
+
const autoFit = createAutoFit()
|
|
439
|
+
$effect(() => {
|
|
440
|
+
const map = mapInstance
|
|
441
|
+
untrack(() => autoFit(map, () => fitAll(false)))
|
|
442
|
+
})
|
|
409
443
|
|
|
410
444
|
/* Мелкие зоны в м², остальное в га */
|
|
411
445
|
const areaText = (m2: number) => (m2 < 1000 ? `${fmt(m2, 0)} ${$T("widget.mission.m2")}` : `${fmt(m2 / 10000, 2)} ${$T("widget.mission.ha")}`)
|
|
@@ -470,22 +504,31 @@
|
|
|
470
504
|
>
|
|
471
505
|
<WidgetHeader icon={MissionIcon} label={label?.name || $T("widget.mission.title")} bind:collapsed {hints}>
|
|
472
506
|
{#snippet right()}
|
|
473
|
-
<
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
507
|
+
<div class="flex items-center gap-3">
|
|
508
|
+
<UI.Button
|
|
509
|
+
wrapperClass="w-auto"
|
|
510
|
+
componentClass="bg-transparent px-3"
|
|
511
|
+
content={{ icon: FitIcon, name: $T("constructor.props.map.fitAll"), info: { text: $T("widget.mission.fitHint"), side: "bottom" } }}
|
|
512
|
+
disabled={collapsed || missionPoints().length === 0}
|
|
513
|
+
onClick={() => fitAll()}
|
|
514
|
+
/>
|
|
515
|
+
<span class="flex items-center gap-3 text-[12px] font-semibold text-(--muted-color) tabular-nums">
|
|
516
|
+
{#if headerProgress !== null}
|
|
517
|
+
<span class="flex items-center gap-1.5" title={simulate ? $T("widget.sim.title") : $T("widget.mission.progressHint")}>
|
|
518
|
+
<span class="h-1.5 w-14 overflow-hidden rounded-full bg-(--hairline-color)"
|
|
519
|
+
><span class="block h-full rounded-full bg-(--accent-color)" style="width: {Math.round(headerProgress * 100)}%"></span></span
|
|
520
|
+
>
|
|
521
|
+
{Math.round(headerProgress * 100)}%{#if live}
|
|
522
|
+
· {$T("widget.mission.treated")} {fmt(treatedHa, 2)} {$T("widget.mission.ha")}{/if}
|
|
523
|
+
</span>
|
|
524
|
+
{/if}
|
|
525
|
+
{#if stats}
|
|
526
|
+
<span class="text-[12px] font-semibold text-(--muted-color) tabular-nums"
|
|
527
|
+
>{fmt(stats.ha, 2)} {$T("widget.mission.ha")} · {fmt(stats.total, 2)} {$T("widget.mission.km")}</span
|
|
478
528
|
>
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
{/if}
|
|
483
|
-
{#if stats}
|
|
484
|
-
<span class="text-[12px] font-semibold text-(--muted-color) tabular-nums"
|
|
485
|
-
>{fmt(stats.ha, 2)} {$T("widget.mission.ha")} · {fmt(stats.total, 2)} {$T("widget.mission.km")}</span
|
|
486
|
-
>
|
|
487
|
-
{/if}
|
|
488
|
-
</span>
|
|
529
|
+
{/if}
|
|
530
|
+
</span>
|
|
531
|
+
</div>
|
|
489
532
|
{/snippet}
|
|
490
533
|
</WidgetHeader>
|
|
491
534
|
|
|
@@ -542,6 +585,7 @@
|
|
|
542
585
|
{onDragEnd}
|
|
543
586
|
{onHandleRightClick}
|
|
544
587
|
colors={mapColors}
|
|
588
|
+
highlight={highlightGeo}
|
|
545
589
|
onCursor={(ll) => (cursor = ll)}
|
|
546
590
|
/>
|
|
547
591
|
{@render overlay?.()}
|
|
@@ -682,9 +726,16 @@
|
|
|
682
726
|
<span class="text-[11px] font-semibold tracking-wide text-(--faint-color) uppercase">{$T("widget.mission.zones")}</span>
|
|
683
727
|
<div class="flex flex-col gap-1.5 rounded-lg bg-(--container-color) p-3 text-[13px] tabular-nums">
|
|
684
728
|
{#each fields as field, i}
|
|
685
|
-
<div
|
|
686
|
-
|
|
687
|
-
|
|
729
|
+
<div
|
|
730
|
+
class="-mx-1.5 flex items-center justify-between gap-2 rounded-md px-1.5 transition-colors duration-150 hover:bg-(--green-soft)"
|
|
731
|
+
role="listitem"
|
|
732
|
+
onmouseenter={() => (highlighted = { kind: "field", index: i })}
|
|
733
|
+
onmouseleave={() => (highlighted = null)}
|
|
734
|
+
>
|
|
735
|
+
<button
|
|
736
|
+
class="flex cursor-pointer items-center gap-2 border-none bg-transparent p-0 text-left text-(--font-color)"
|
|
737
|
+
title={$T("widget.mission.showOnMap")}
|
|
738
|
+
onclick={() => flyTo(field)}><span class="size-2.5 rounded-sm bg-(--green-mark)"></span>{$T("widget.mission.field")} {i + 1}</button
|
|
688
739
|
>
|
|
689
740
|
<span class="flex items-center gap-3">
|
|
690
741
|
<span class="flex items-center gap-1" use:hint={{ text: $T("widget.mission.hint.fieldAngle"), enabled: $hints }}>
|
|
@@ -706,8 +757,17 @@
|
|
|
706
757
|
<span class="text-(--muted-color)">{$T("widget.mission.noField")}</span>
|
|
707
758
|
{/each}
|
|
708
759
|
{#each plan.holes as hole, i}
|
|
709
|
-
<div
|
|
710
|
-
|
|
760
|
+
<div
|
|
761
|
+
class="-mx-1.5 flex items-center justify-between gap-2 rounded-md px-1.5 transition-colors duration-150 hover:bg-(--red-soft)"
|
|
762
|
+
role="listitem"
|
|
763
|
+
onmouseenter={() => (highlighted = { kind: "hole", index: i })}
|
|
764
|
+
onmouseleave={() => (highlighted = null)}
|
|
765
|
+
>
|
|
766
|
+
<button
|
|
767
|
+
class="flex cursor-pointer items-center gap-2 border-none bg-transparent p-0 text-left text-(--font-color)"
|
|
768
|
+
title={$T("widget.mission.showOnMap")}
|
|
769
|
+
onclick={() => flyTo(hole)}><span class="size-2.5 rounded-sm bg-(--red-mark)"></span>{$T("widget.mission.hole")} {i + 1}</button
|
|
770
|
+
>
|
|
711
771
|
<span class="flex items-center gap-2">
|
|
712
772
|
<span class="font-semibold">{areaText(fieldArea(hole, []))}</span>
|
|
713
773
|
<button class="cursor-pointer border-none bg-transparent p-0 text-(--red-text)" onclick={() => removeHole(i)} aria-label="Delete"
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
import * as UI from "../.."
|
|
12
12
|
import { MapLibre, NavigationControl, ScaleControl, FullScreenControl, CustomControl } from "../mapWrapper"
|
|
13
13
|
import MissionLayers from "./MissionLayers.svelte"
|
|
14
|
+
import FitIcon from "../FitIcon.svelte"
|
|
15
|
+
import { createAutoFit, fitMapTo } from "./fit"
|
|
14
16
|
import MissionSim from "./MissionSim.svelte"
|
|
15
17
|
import { missionProgress } from "./progress"
|
|
16
18
|
import MapIcon from "../MapIcon.svelte"
|
|
@@ -49,7 +51,7 @@
|
|
|
49
51
|
onSend,
|
|
50
52
|
}: Props = $props()
|
|
51
53
|
|
|
52
|
-
const hints = hintsSetting("WidgetMap")
|
|
54
|
+
const hints = $derived(hintsSetting("WidgetMap", persistKey))
|
|
53
55
|
let accentStyle = $derived(widgetAccentStyle(componentClass ?? ""))
|
|
54
56
|
|
|
55
57
|
let hasMergedPersisted = false
|
|
@@ -234,6 +236,14 @@
|
|
|
234
236
|
void isDarkMode
|
|
235
237
|
mapColors = readMapColors(rootEl)
|
|
236
238
|
})
|
|
239
|
+
/* «Показать все» в шапке и вписывание при появлении карты */
|
|
240
|
+
const missionPoints = (): LngLat[] => [...(route.start ? [route.start] : []), ...route.points.map((p) => p.point)]
|
|
241
|
+
const fitAll = (animate = true) => fitMapTo(mapInstance, missionPoints(), animate)
|
|
242
|
+
const autoFit = createAutoFit()
|
|
243
|
+
$effect(() => {
|
|
244
|
+
const map = mapInstance
|
|
245
|
+
untrack(() => autoFit(map, () => fitAll(false)))
|
|
246
|
+
})
|
|
237
247
|
|
|
238
248
|
const fmt = (v: number, d = 1) => v.toLocaleString(undefined, { minimumFractionDigits: d, maximumFractionDigits: d })
|
|
239
249
|
/* Кнопки панели на карте: стили кнопок MapLibre (размер 29px, прозрачный фон, без рамки) перебиваются */
|
|
@@ -281,22 +291,31 @@
|
|
|
281
291
|
>
|
|
282
292
|
<WidgetHeader icon={MapIcon} label={label?.name || $T("widget.route.title")} bind:collapsed {hints}>
|
|
283
293
|
{#snippet right()}
|
|
284
|
-
<
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
294
|
+
<div class="flex items-center gap-3">
|
|
295
|
+
<UI.Button
|
|
296
|
+
wrapperClass="w-auto"
|
|
297
|
+
componentClass="bg-transparent px-3"
|
|
298
|
+
content={{ icon: FitIcon, name: $T("constructor.props.map.fitAll"), info: { text: $T("widget.route.fitHint"), side: "bottom" } }}
|
|
299
|
+
disabled={collapsed || missionPoints().length === 0}
|
|
300
|
+
onClick={() => fitAll()}
|
|
301
|
+
/>
|
|
302
|
+
<span class="flex items-center gap-3 text-[12px] font-semibold text-(--muted-color) tabular-nums">
|
|
303
|
+
{#if headerProgress !== null}
|
|
304
|
+
<span class="flex items-center gap-1.5" title={simulate ? $T("widget.sim.title") : $T("widget.mission.progressHint")}>
|
|
305
|
+
<span class="h-1.5 w-14 overflow-hidden rounded-full bg-(--hairline-color)"
|
|
306
|
+
><span class="block h-full rounded-full bg-(--accent-color)" style="width: {Math.round(headerProgress * 100)}%"></span></span
|
|
307
|
+
>
|
|
308
|
+
{Math.round(headerProgress * 100)}%{#if live}
|
|
309
|
+
· {live.finished
|
|
310
|
+
? $T("widget.route.finished")
|
|
311
|
+
: live.target > route.points.length
|
|
312
|
+
? $T("widget.route.returning")
|
|
313
|
+
: `${$T("widget.route.toPoint")} ${live.target}/${route.points.length}`}{/if}
|
|
314
|
+
</span>
|
|
315
|
+
{/if}
|
|
316
|
+
<span>{stats.points} {$T("widget.route.pts")} · {km(stats.length)}</span>
|
|
317
|
+
</span>
|
|
318
|
+
</div>
|
|
300
319
|
{/snippet}
|
|
301
320
|
</WidgetHeader>
|
|
302
321
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- $lib/WidgetMap/mission/MissionLayers.svelte - слои карты генератора миссии: поле и зоны, траектория, черновик контура и ручки вершин; клики и перетаскивание ручек отдаёт наверх -->
|
|
1
|
+
<!-- $lib/WidgetMap/mission/MissionLayers.svelte - слои карты генератора миссии: поле и зоны, траектория, подсветка выбранного в списке контура, черновик контура и ручки вершин; клики и перетаскивание ручек отдаёт наверх -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { onDestroy, untrack } from "svelte"
|
|
4
4
|
import { getMapContext } from "../mapWrapper"
|
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
onHandleRightClick: (id: string) => void
|
|
19
19
|
onCursor: (ll: LngLat | null) => void
|
|
20
20
|
colors: IMapColors /* Маршрут и точки - мастер-цвет, поле, зоны и развороты - цвета по смыслу */
|
|
21
|
+
highlight?: any /* Контур, на который навели в списке «Итога»: поле или зона с kind */
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
let { zones, path, draft, handles, drawing, onMapClick, onMapDblClick, onDrag, onDragEnd, onHandleRightClick, onCursor, colors }: Props = $props()
|
|
24
|
+
let { zones, path, draft, handles, drawing, onMapClick, onMapDblClick, onDrag, onDragEnd, onHandleRightClick, onCursor, colors, highlight }: Props = $props()
|
|
24
25
|
|
|
25
26
|
/* Краски слоёв от цветов виджета: одна таблица и для создания слоёв, и для перекраски */
|
|
26
27
|
const paints = (c: IMapColors): Record<string, Record<string, any>> => {
|
|
@@ -33,6 +34,8 @@
|
|
|
33
34
|
"msn-path-spray": { "line-color": c.green },
|
|
34
35
|
"msn-path-route": { "line-color": c.accent },
|
|
35
36
|
"msn-draft-line": { "line-color": byKind },
|
|
37
|
+
"msn-highlight-fill": { "fill-color": byKind },
|
|
38
|
+
"msn-highlight-line": { "line-color": byKind },
|
|
36
39
|
"msn-handles": {
|
|
37
40
|
"circle-color": ["match", ["get", "role"], "mid", "#ffffff", "start", c.accent, "wp", c.accent, byKind],
|
|
38
41
|
"circle-stroke-color": ["match", ["get", "role"], "mid", ["match", ["get", "kind"], "hole", c.red, "route", c.accent, c.green], "#ffffff"],
|
|
@@ -45,7 +48,7 @@
|
|
|
45
48
|
const mapCtx = getMapContext()
|
|
46
49
|
const map = mapCtx.map!
|
|
47
50
|
|
|
48
|
-
const SOURCES = ["msn-zones", "msn-path", "msn-draft", "msn-handles"]
|
|
51
|
+
const SOURCES = ["msn-zones", "msn-path", "msn-highlight", "msn-draft", "msn-handles"]
|
|
49
52
|
const empty = { type: "FeatureCollection", features: [] }
|
|
50
53
|
|
|
51
54
|
for (const id of SOURCES) mapCtx.addSource(id, { type: "geojson", data: empty as any })
|
|
@@ -97,6 +100,20 @@
|
|
|
97
100
|
layout: { "line-join": "round", "line-cap": "round" },
|
|
98
101
|
paint: { ...initial["msn-path-route"], "line-width": 3 },
|
|
99
102
|
})
|
|
103
|
+
/* Подсветка контура поверх проходов: плотная заливка и толстая обводка, видна и у маленькой зоны */
|
|
104
|
+
mapCtx.addLayer({
|
|
105
|
+
id: "msn-highlight-fill",
|
|
106
|
+
type: "fill",
|
|
107
|
+
source: "msn-highlight",
|
|
108
|
+
paint: { ...initial["msn-highlight-fill"], "fill-opacity": 0.45 },
|
|
109
|
+
})
|
|
110
|
+
mapCtx.addLayer({
|
|
111
|
+
id: "msn-highlight-line",
|
|
112
|
+
type: "line",
|
|
113
|
+
source: "msn-highlight",
|
|
114
|
+
layout: { "line-join": "round" },
|
|
115
|
+
paint: { ...initial["msn-highlight-line"], "line-width": 5 },
|
|
116
|
+
})
|
|
100
117
|
mapCtx.addLayer({
|
|
101
118
|
id: "msn-draft-line",
|
|
102
119
|
type: "line",
|
|
@@ -141,6 +158,7 @@
|
|
|
141
158
|
$effect(() => setData("msn-path", $state.snapshot(path)))
|
|
142
159
|
$effect(() => setData("msn-draft", $state.snapshot(draft)))
|
|
143
160
|
$effect(() => setData("msn-handles", $state.snapshot(handles)))
|
|
161
|
+
$effect(() => setData("msn-highlight", $state.snapshot(highlight) ?? empty))
|
|
144
162
|
|
|
145
163
|
/* Рисование: двойной клик закрывает контур, а не приближает карту */
|
|
146
164
|
$effect(() => {
|
|
@@ -16,6 +16,7 @@ interface Props {
|
|
|
16
16
|
onHandleRightClick: (id: string) => void;
|
|
17
17
|
onCursor: (ll: LngLat | null) => void;
|
|
18
18
|
colors: IMapColors;
|
|
19
|
+
highlight?: any;
|
|
19
20
|
}
|
|
20
21
|
declare const MissionLayers: import("svelte").Component<Props, {}, "">;
|
|
21
22
|
type MissionLayers = ReturnType<typeof MissionLayers>;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
<!-- $lib/WidgetMap/mission/SpinnerIcon.svelte - колесо ожидания размером с родителя: подбор угла
|
|
1
|
+
<!-- $lib/WidgetMap/mission/SpinnerIcon.svelte - колесо ожидания размером с родителя: подбор угла проходов; крутится CSS-анимацией, браузер ведёт её отдельно от расчёта, поэтому она не замирает -->
|
|
2
2
|
<script lang="ts"></script>
|
|
3
3
|
|
|
4
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24"
|
|
4
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" class="animate-spin [animation-duration:0.8s]"
|
|
5
5
|
><path fill="currentColor" d="M12 2A10 10 0 1 0 22 12A10 10 0 0 0 12 2Zm0 18a8 8 0 1 1 8-8A8 8 0 0 1 12 20Z" opacity=".35" /><path
|
|
6
6
|
fill="currentColor"
|
|
7
7
|
d="M20 12h2A10 10 0 0 0 12 2V4A8 8 0 0 1 20 12Z"
|
|
8
|
-
|
|
9
|
-
></svg
|
|
8
|
+
/></svg
|
|
10
9
|
>
|