poe-svelte-ui-lib 1.11.3 → 1.12.1
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/Map/Map.svelte +71 -15
- package/dist/Map/Map.svelte.d.ts +1 -1
- package/dist/Map/MapProps.svelte +35 -2
- package/dist/Map/MapProps.svelte.d.ts +2 -2
- package/dist/Map/README.md +8 -6
- package/dist/Map/mapWrapper/Line.svelte +59 -0
- package/dist/Map/mapWrapper/Line.svelte.d.ts +10 -0
- package/dist/Map/mapWrapper/index.d.ts +1 -0
- package/dist/Map/mapWrapper/index.js +1 -0
- package/dist/locales/translations.js +2 -0
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/Map/Map.svelte
CHANGED
|
@@ -4,14 +4,22 @@
|
|
|
4
4
|
import { onDestroy, onMount } from "svelte"
|
|
5
5
|
import { fade } from "svelte/transition"
|
|
6
6
|
import { twMerge } from "tailwind-merge"
|
|
7
|
-
import { MapLibre, NavigationControl, ScaleControl, GeolocateControl, FullScreenControl, Marker, Popup, CustomControl } from "./mapWrapper"
|
|
7
|
+
import { MapLibre, NavigationControl, ScaleControl, GeolocateControl, FullScreenControl, Marker, Popup, Line, CustomControl } from "./mapWrapper"
|
|
8
8
|
|
|
9
|
-
let {
|
|
9
|
+
let {
|
|
10
|
+
id = crypto.randomUUID(),
|
|
11
|
+
label = { name: "", class: "" },
|
|
12
|
+
data = $bindable(),
|
|
13
|
+
markerIcon,
|
|
14
|
+
trackEnabled = $bindable(false),
|
|
15
|
+
trackLength = $bindable(1000),
|
|
16
|
+
}: IMapProps = $props()
|
|
10
17
|
|
|
11
18
|
interface MapDevice extends IDeviceGNSS {
|
|
12
19
|
isFresh: boolean
|
|
13
20
|
staleTimeoutId: number | null
|
|
14
21
|
removeTimeoutId: number | null
|
|
22
|
+
track: [number, number][]
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
let devices: MapDevice[] = $state([])
|
|
@@ -47,10 +55,13 @@
|
|
|
47
55
|
// Обработка входящих данных
|
|
48
56
|
$effect(() => {
|
|
49
57
|
if (data) {
|
|
58
|
+
/* (0, 0) — признак отсутствия реального GPS-фикса, в траекторию такие точки не добавляем */
|
|
59
|
+
const hasFix = data.NavLat !== 0 || data.NavLon !== 0
|
|
50
60
|
const idx = devices.findIndex((d) => d.DevSN === data?.DevSN)
|
|
51
61
|
if (idx !== -1) {
|
|
52
62
|
/* Обновление существующего */
|
|
53
|
-
|
|
63
|
+
const track = hasFix ? [...devices[idx].track, [data.NavLon, data.NavLat] as [number, number]].slice(-trackLength) : devices[idx].track
|
|
64
|
+
devices[idx] = { ...devices[idx], ...data, track }
|
|
54
65
|
} else {
|
|
55
66
|
/* Новое устройство */
|
|
56
67
|
const newDevice: MapDevice = {
|
|
@@ -58,6 +69,7 @@
|
|
|
58
69
|
isFresh: true,
|
|
59
70
|
staleTimeoutId: null,
|
|
60
71
|
removeTimeoutId: null,
|
|
72
|
+
track: hasFix ? [[data.NavLon, data.NavLat]] : [],
|
|
61
73
|
}
|
|
62
74
|
devices.push(newDevice)
|
|
63
75
|
}
|
|
@@ -66,6 +78,15 @@
|
|
|
66
78
|
}
|
|
67
79
|
})
|
|
68
80
|
|
|
81
|
+
// Подрезает уже накопленные траектории при уменьшении trackLength
|
|
82
|
+
$effect(() => {
|
|
83
|
+
if (trackLength > 0) {
|
|
84
|
+
devices.forEach((device) => {
|
|
85
|
+
if (device.track.length > trackLength) device.track = device.track.slice(-trackLength)
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
69
90
|
const handleThemeChange = (event: CustomEvent) => {
|
|
70
91
|
isDarkMode = !event.detail.currentTheme
|
|
71
92
|
}
|
|
@@ -85,6 +106,15 @@
|
|
|
85
106
|
}
|
|
86
107
|
})
|
|
87
108
|
|
|
109
|
+
const trackLengthOptions: { label: string; value: number }[] = [
|
|
110
|
+
{ label: "100", value: 100 },
|
|
111
|
+
{ label: "500", value: 500 },
|
|
112
|
+
{ label: "1000", value: 1000 },
|
|
113
|
+
{ label: "5000", value: 5000 },
|
|
114
|
+
{ label: "10000", value: 10000 },
|
|
115
|
+
{ label: "50000", value: 50000 },
|
|
116
|
+
]
|
|
117
|
+
|
|
88
118
|
const timeoutOptions: { label: string; value: number }[] = [
|
|
89
119
|
{ label: "30 sec", value: 30_000 },
|
|
90
120
|
{ label: "1 min", value: 60_000 },
|
|
@@ -117,21 +147,47 @@
|
|
|
117
147
|
<FullScreenControl />
|
|
118
148
|
|
|
119
149
|
<CustomControl position="top-left">
|
|
120
|
-
<div class="flex items-center gap-
|
|
121
|
-
<label
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
150
|
+
<div class="flex flex-wrap items-center gap-3 px-2 py-1 text-black">
|
|
151
|
+
<label class="flex items-center gap-1.5 text-sm font-medium">
|
|
152
|
+
<input type="checkbox" bind:checked={trackEnabled} />
|
|
153
|
+
{$T("constructor.props.map.track")}
|
|
154
|
+
</label>
|
|
155
|
+
|
|
156
|
+
{#if trackEnabled}
|
|
157
|
+
<div class="flex items-center gap-2">
|
|
158
|
+
<label for="trackLength" class="text-sm font-medium">{$T("constructor.props.map.trackLength")}</label>
|
|
159
|
+
<select id="trackLength" class="rounded px-2 py-1 text-sm" bind:value={trackLength}>
|
|
160
|
+
{#each trackLengthOptions as opt}
|
|
161
|
+
<option value={opt.value}>{opt.label}</option>
|
|
162
|
+
{/each}
|
|
163
|
+
</select>
|
|
164
|
+
</div>
|
|
165
|
+
{/if}
|
|
166
|
+
|
|
167
|
+
<div class="flex items-center gap-2">
|
|
168
|
+
<label for="timeout" class="text-sm font-medium">{$T("constructor.props.map.timeout")}</label>
|
|
169
|
+
<select
|
|
170
|
+
id="timeout"
|
|
171
|
+
class="rounded px-2 py-1 text-sm"
|
|
172
|
+
bind:value={markerTimeout}
|
|
173
|
+
onchange={(e) => changeTimeout(parseInt((e.target as HTMLSelectElement).value))}
|
|
174
|
+
>
|
|
175
|
+
{#each timeoutOptions as opt}
|
|
176
|
+
<option value={opt.value}>{opt.label}</option>
|
|
177
|
+
{/each}
|
|
178
|
+
</select>
|
|
179
|
+
</div>
|
|
132
180
|
</div>
|
|
133
181
|
</CustomControl>
|
|
134
182
|
|
|
183
|
+
{#if trackEnabled}
|
|
184
|
+
{#each devices as device (device.DevSN)}
|
|
185
|
+
{#if device.track.length >= 2}
|
|
186
|
+
<Line id={`track-${device.DevSN}`} coordinates={device.track} color={device.isFresh ? "#22c55e" : "#ef4444"} />
|
|
187
|
+
{/if}
|
|
188
|
+
{/each}
|
|
189
|
+
{/if}
|
|
190
|
+
|
|
135
191
|
{#each devices as device}
|
|
136
192
|
<Marker lnglat={{ lng: device.NavLon, lat: device.NavLat }}>
|
|
137
193
|
{#snippet content()}
|
package/dist/Map/Map.svelte.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { IMapProps } from "../types";
|
|
2
|
-
declare const Map: import("svelte").Component<IMapProps, {}, "data">;
|
|
2
|
+
declare const Map: import("svelte").Component<IMapProps, {}, "data" | "trackEnabled" | "trackLength">;
|
|
3
3
|
type Map = ReturnType<typeof Map>;
|
|
4
4
|
export default Map;
|
package/dist/Map/MapProps.svelte
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { T } from "../locales/i18n"
|
|
3
|
-
import { type UIComponent, type
|
|
3
|
+
import { type UIComponent, type IMapProps, updateProperty, type IUIComponentHandler } from "../types"
|
|
4
4
|
import { ICONS } from "../icons"
|
|
5
5
|
import { optionsStore } from "../options"
|
|
6
6
|
import { getContext } from "svelte"
|
|
7
7
|
import CommonSnippets from "../CommonSnippets.svelte"
|
|
8
|
+
import { Switch, Select } from ".."
|
|
9
|
+
|
|
10
|
+
const TRACK_LENGTH_OPTIONS = [100, 500, 1000, 5000, 10000, 50000].map((value) => ({ id: crypto.randomUUID(), value, name: String(value) }))
|
|
8
11
|
|
|
9
12
|
const {
|
|
10
13
|
component,
|
|
11
14
|
onPropertyChange,
|
|
12
15
|
forConstructor = true,
|
|
13
16
|
} = $props<{
|
|
14
|
-
component: UIComponent & { properties: Partial<
|
|
17
|
+
component: UIComponent & { properties: Partial<IMapProps> }
|
|
15
18
|
onPropertyChange: (updates: Partial<{ properties?: string | object; name?: string; access?: string; eventHandler?: IUIComponentHandler }>) => void
|
|
16
19
|
forConstructor?: boolean
|
|
17
20
|
}>()
|
|
@@ -48,6 +51,21 @@
|
|
|
48
51
|
{component}
|
|
49
52
|
{onPropertyChange}
|
|
50
53
|
/>
|
|
54
|
+
<Switch
|
|
55
|
+
label={{ name: $T("constructor.props.map.track") }}
|
|
56
|
+
value={component.properties.trackEnabled ? 1 : 0}
|
|
57
|
+
options={[{ id: crypto.randomUUID(), value: 0, class: "" }]}
|
|
58
|
+
onChange={(value) => updateProperty("trackEnabled", Boolean(value), component, onPropertyChange)}
|
|
59
|
+
/>
|
|
60
|
+
{#if component.properties.trackEnabled}
|
|
61
|
+
<Select
|
|
62
|
+
label={{ name: $T("constructor.props.map.trackLength") }}
|
|
63
|
+
type="input"
|
|
64
|
+
options={TRACK_LENGTH_OPTIONS}
|
|
65
|
+
value={TRACK_LENGTH_OPTIONS.find((opt) => opt.value === (component.properties.trackLength ?? 1000))}
|
|
66
|
+
onUpdate={(option) => updateProperty("trackLength", (option as { value: number }).value, component, onPropertyChange)}
|
|
67
|
+
/>
|
|
68
|
+
{/if}
|
|
51
69
|
</div>
|
|
52
70
|
</div>
|
|
53
71
|
{:else}
|
|
@@ -73,6 +91,21 @@
|
|
|
73
91
|
{component}
|
|
74
92
|
{onPropertyChange}
|
|
75
93
|
/>
|
|
94
|
+
<Switch
|
|
95
|
+
label={{ name: $T("constructor.props.map.track") }}
|
|
96
|
+
value={component.properties.trackEnabled ? 1 : 0}
|
|
97
|
+
options={[{ id: crypto.randomUUID(), value: 0, class: "" }]}
|
|
98
|
+
onChange={(value) => updateProperty("trackEnabled", Boolean(value), component, onPropertyChange)}
|
|
99
|
+
/>
|
|
100
|
+
{#if component.properties.trackEnabled}
|
|
101
|
+
<Select
|
|
102
|
+
label={{ name: $T("constructor.props.map.trackLength") }}
|
|
103
|
+
type="input"
|
|
104
|
+
options={TRACK_LENGTH_OPTIONS}
|
|
105
|
+
value={TRACK_LENGTH_OPTIONS.find((opt) => opt.value === (component.properties.trackLength ?? 1000))}
|
|
106
|
+
onUpdate={(option) => updateProperty("trackLength", (option as { value: number }).value, component, onPropertyChange)}
|
|
107
|
+
/>
|
|
108
|
+
{/if}
|
|
76
109
|
</div>
|
|
77
110
|
</div>
|
|
78
111
|
{/if}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type UIComponent, type
|
|
1
|
+
import { type UIComponent, type IMapProps, type IUIComponentHandler } from "../types";
|
|
2
2
|
type $$ComponentProps = {
|
|
3
3
|
component: UIComponent & {
|
|
4
|
-
properties: Partial<
|
|
4
|
+
properties: Partial<IMapProps>;
|
|
5
5
|
};
|
|
6
6
|
onPropertyChange: (updates: Partial<{
|
|
7
7
|
properties?: string | object;
|
package/dist/Map/README.md
CHANGED
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
## Пропсы
|
|
8
8
|
|
|
9
|
-
| Название
|
|
10
|
-
|
|
|
11
|
-
| id
|
|
12
|
-
| label
|
|
13
|
-
| data
|
|
14
|
-
| markerIcon
|
|
9
|
+
| Название | Тип | По умолчанию | Описание |
|
|
10
|
+
| ------------ | ----------------------------------------------------- | ----------------------- | ------------------------------------------------------------------ |
|
|
11
|
+
| id | string | crypto.randomUUID() | Уникальный идентификатор компонента |
|
|
12
|
+
| label | { name?: string; class?: string } | { name: "", class: "" } | Настройки заголовка компонента |
|
|
13
|
+
| data | IDeviceGNSS | $bindable() | Данные GPS устройства для отображения на карте |
|
|
14
|
+
| markerIcon | ConstructorOfATypedSvelteComponent \| string \| null | undefined | Иконка для маркера устройства |
|
|
15
|
+
| trackEnabled | boolean | $bindable(false) | Включает отрисовку траектории движения устройств |
|
|
16
|
+
| trackLength | number | $bindable(1000) | Максимальное количество последних точек в траектории на устройство (100 / 500 / 1000 / 5000 / 10000 / 50000) |
|
|
15
17
|
|
|
16
18
|
### Вложенные типы
|
|
17
19
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onDestroy } from "svelte"
|
|
3
|
+
import { getMapContext } from "./contexts.svelte.js"
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
id: string
|
|
7
|
+
coordinates: [number, number][]
|
|
8
|
+
color?: string
|
|
9
|
+
width?: number
|
|
10
|
+
opacity?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let { id, coordinates, color = "#3b82f6", width = 3, opacity = 0.85 }: Props = $props()
|
|
14
|
+
|
|
15
|
+
const mapCtx = getMapContext()
|
|
16
|
+
if (!mapCtx.map) throw new Error("Map instance is not initialized.")
|
|
17
|
+
|
|
18
|
+
let added = false
|
|
19
|
+
|
|
20
|
+
const toGeoJSON = () => ({
|
|
21
|
+
type: "Feature" as const,
|
|
22
|
+
geometry: { type: "LineString" as const, coordinates: $state.snapshot(coordinates) },
|
|
23
|
+
properties: {},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
$effect(() => {
|
|
27
|
+
if (!mapCtx.map || added) return
|
|
28
|
+
|
|
29
|
+
mapCtx.addSource(id, { type: "geojson", data: toGeoJSON() })
|
|
30
|
+
mapCtx.addLayer({
|
|
31
|
+
id,
|
|
32
|
+
type: "line",
|
|
33
|
+
source: id,
|
|
34
|
+
layout: { "line-join": "round", "line-cap": "round" },
|
|
35
|
+
paint: { "line-color": color, "line-width": width, "line-opacity": opacity },
|
|
36
|
+
})
|
|
37
|
+
added = true
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
$effect(() => {
|
|
41
|
+
if (!added || !mapCtx.map) return
|
|
42
|
+
const source = mapCtx.map.getSource(id) as any
|
|
43
|
+
source?.setData(toGeoJSON())
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
$effect(() => {
|
|
47
|
+
if (!added || !mapCtx.map || !mapCtx.map.getLayer(id)) return
|
|
48
|
+
mapCtx.map.setPaintProperty(id, "line-color", color)
|
|
49
|
+
mapCtx.map.setPaintProperty(id, "line-width", width)
|
|
50
|
+
mapCtx.map.setPaintProperty(id, "line-opacity", opacity)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
onDestroy(() => {
|
|
54
|
+
if (added) {
|
|
55
|
+
mapCtx.removeLayer(id)
|
|
56
|
+
mapCtx.removeSource(id)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
</script>
|
|
@@ -5,5 +5,6 @@ export { default as GeolocateControl } from "./controls/GeolocateControl.svelte"
|
|
|
5
5
|
export { default as FullScreenControl } from "./controls/FullScreenControl.svelte";
|
|
6
6
|
export { default as Marker } from "./Marker.svelte";
|
|
7
7
|
export { default as Popup } from "./Popup.svelte";
|
|
8
|
+
export { default as Line } from "./Line.svelte";
|
|
8
9
|
export { default as CustomControl } from "./controls/CustomControl.svelte";
|
|
9
10
|
export { getMapContext, getSourceContext, getMarkerContext } from "./contexts.svelte.js";
|
|
@@ -5,5 +5,6 @@ export { default as GeolocateControl } from "./controls/GeolocateControl.svelte"
|
|
|
5
5
|
export { default as FullScreenControl } from "./controls/FullScreenControl.svelte";
|
|
6
6
|
export { default as Marker } from "./Marker.svelte";
|
|
7
7
|
export { default as Popup } from "./Popup.svelte";
|
|
8
|
+
export { default as Line } from "./Line.svelte";
|
|
8
9
|
export { default as CustomControl } from "./controls/CustomControl.svelte";
|
|
9
10
|
export { getMapContext, getSourceContext, getMarkerContext } from "./contexts.svelte.js";
|
|
@@ -170,6 +170,8 @@ const translations = {
|
|
|
170
170
|
"constructor.props.multiselect": "Мультивыбор",
|
|
171
171
|
"constructor.props.access": "Доступ (не для владельца)",
|
|
172
172
|
"constructor.props.map.timeout": "Таймаут маркеров:",
|
|
173
|
+
"constructor.props.map.track": "Траектория",
|
|
174
|
+
"constructor.props.map.trackLength": "Точек в траектории:",
|
|
173
175
|
"constructor.props.joystick.pitch.axe": "Ocь ⮃",
|
|
174
176
|
"constructor.props.joystick.roll.axe": "Ось ↶ ↷",
|
|
175
177
|
"constructor.props.joystick.yaw.axe": "Ось ⮂",
|
package/dist/types.d.ts
CHANGED