poe-svelte-ui-lib 1.75.0 → 1.76.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/FileAttach/FileAttach.svelte +13 -1
- package/dist/FileAttach/README.md +32 -18
- package/dist/WidgetMap/WidgetMap.svelte +12 -2
- package/dist/WidgetMap/mapWrapper/Line.svelte +21 -16
- package/dist/WidgetMap/mapWrapper/MapLibre.svelte +9 -1
- package/dist/WidgetMap/mapWrapper/MapLibre.svelte.d.ts +1 -0
- package/dist/types.d.ts +6 -1
- package/package.json +1 -1
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
import { T } from "../locales/i18n"
|
|
3
3
|
import type { IFileAttachProps } from "../types"
|
|
4
4
|
import { twMerge } from "tailwind-merge"
|
|
5
|
+
import Button from "../Button/Button.svelte"
|
|
6
|
+
|
|
7
|
+
/* Иконка «открыть файл» для type "button" — документ со стрелкой вверх, контур 1.75 как у остальных кнопок */
|
|
8
|
+
const OPEN_FILE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><path d="M12 18v-6"/><path d="m9 15 3-3 3 3"/></svg>`
|
|
5
9
|
|
|
6
10
|
let {
|
|
7
11
|
id = crypto.randomUUID(),
|
|
@@ -13,6 +17,8 @@
|
|
|
13
17
|
imageSize = { height: "10rem", width: "10rem", fitMode: "cover", form: "square" },
|
|
14
18
|
disabled = false,
|
|
15
19
|
currentImage = $bindable(""),
|
|
20
|
+
info,
|
|
21
|
+
icon,
|
|
16
22
|
onChange = () => {},
|
|
17
23
|
}: IFileAttachProps = $props()
|
|
18
24
|
|
|
@@ -45,6 +51,8 @@
|
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
onChange(event, file)
|
|
54
|
+
/* Кнопка «Открыть файл» — повторный выбор того же файла тоже должен срабатывать */
|
|
55
|
+
if (type === "button") input.value = ""
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
const triggerFileInput = () => {
|
|
@@ -60,7 +68,11 @@
|
|
|
60
68
|
<span class={twMerge(`w-full text-[12px] font-semibold text-(--muted-color)`, label.class)}>{label.name}</span>
|
|
61
69
|
{/if}
|
|
62
70
|
|
|
63
|
-
{#if type === "
|
|
71
|
+
{#if type === "button"}
|
|
72
|
+
<!-- Иконочная кнопка с подсказкой, как UI.Button; выбор файла — скрытый input за ней -->
|
|
73
|
+
<Button {componentClass} {disabled} content={{ icon: icon ?? OPEN_FILE_ICON, info }} onClick={triggerFileInput} />
|
|
74
|
+
<input id={ID} type="file" class="hidden" {accept} {disabled} onchange={handleFileChange} />
|
|
75
|
+
{:else if type === "image"}
|
|
64
76
|
<div class="relative">
|
|
65
77
|
<button
|
|
66
78
|
class="flex items-center justify-center overflow-hidden {imageSize.form === 'circle' ? 'rounded-full' : 'rounded-[12px]'}
|
|
@@ -17,6 +17,18 @@
|
|
|
17
17
|
|
|
18
18
|
## Особенности
|
|
19
19
|
|
|
20
|
+
- `type="button"` — иконочная кнопка «Открыть файл» (скрытый `input` за `UI.Button`) с подсказкой `info`, для панелей инструментов: открыть файл для редактирования рядом с кнопками «Сохранить в файл», «Загрузить с сервера»; сохранение в файл — обычная `UI.Button`
|
|
21
|
+
|
|
22
|
+
```svelte
|
|
23
|
+
<UI.FileAttach
|
|
24
|
+
type="button"
|
|
25
|
+
accept=".yaml,.gz"
|
|
26
|
+
componentClass={TOOL_BUTTON}
|
|
27
|
+
info={{ text: "Открыть файл", side: "top" }}
|
|
28
|
+
onChange={(event, file) => open(file)}
|
|
29
|
+
/>
|
|
30
|
+
```
|
|
31
|
+
|
|
20
32
|
- Для списка файлов, удаления и прогресса закачки используйте виджет «Файловая система»
|
|
21
33
|
|
|
22
34
|
### Адаптивность
|
|
@@ -35,24 +47,26 @@
|
|
|
35
47
|
|
|
36
48
|
## Пропсы
|
|
37
49
|
|
|
38
|
-
| Название | Тип | По умолчанию | Описание
|
|
39
|
-
| ------------------- | -------------------------------------------- | --------------------- |
|
|
40
|
-
| `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента
|
|
41
|
-
| `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для обертки компонента
|
|
42
|
-
| `componentClass` | `string` | `""` | Дополнительные CSS-классы для элемента с именем выбранного файла
|
|
43
|
-
| `label` | `object` | `-` | Настройки заголовка компонента
|
|
44
|
-
| `label.name` | `string` | `""` | Текст заголовка компонента
|
|
45
|
-
| `label.class` | `string` | `""` | CSS-классы для стилизации текста заголовка
|
|
46
|
-
| `type` | `"file" \| "image"`
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `imageSize
|
|
51
|
-
| `imageSize.
|
|
52
|
-
| `imageSize.
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
50
|
+
| Название | Тип | По умолчанию | Описание |
|
|
51
|
+
| ------------------- | -------------------------------------------- | --------------------- | ---------------------------------------------------------------------------------- |
|
|
52
|
+
| `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
|
|
53
|
+
| `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для обертки компонента |
|
|
54
|
+
| `componentClass` | `string` | `""` | Дополнительные CSS-классы для элемента с именем выбранного файла |
|
|
55
|
+
| `label` | `object` | `-` | Настройки заголовка компонента |
|
|
56
|
+
| `label.name` | `string` | `""` | Текст заголовка компонента |
|
|
57
|
+
| `label.class` | `string` | `""` | CSS-классы для стилизации текста заголовка |
|
|
58
|
+
| `type` | `"file" \| "image" \| "button"` | `"file"` | Тип компонента: поле выбора файла, изображение или иконочная кнопка «Открыть файл» |
|
|
59
|
+
| `info` | `{ text, side }` | `-` | Только `button`: всплывающая подсказка, как у `UI.Button` |
|
|
60
|
+
| `icon` | `Component \| string` | документ со стрелкой | Только `button`: своя иконка (компонент или SVG-строка) |
|
|
61
|
+
| `accept` | `string` | `"*/*"` | MIME-типы файлов, которые можно выбрать |
|
|
62
|
+
| `imageSize` | `object` | `-` | Настройки отображения изображения |
|
|
63
|
+
| `imageSize.height` | `string` | `"10rem"` | Высота области отображения изображения |
|
|
64
|
+
| `imageSize.width` | `string` | `"10rem"` | Ширина области отображения изображения |
|
|
65
|
+
| `imageSize.fitMode` | `"cover" \| "contain"` | `"cover"` | Режим масштабирования изображения |
|
|
66
|
+
| `imageSize.form` | `"square" \| "circle"` | `"square"` | Форма области отображения изображения |
|
|
67
|
+
| `disabled` | `boolean` | `false` | Состояние отключения компонента |
|
|
68
|
+
| `currentImage` | `string \| null` | `""` | Текущее изображение (для связывания) |
|
|
69
|
+
| `onChange` | `(event: Event, file: File \| null) => void` | `() => {}` | Обработчик изменения файла |
|
|
56
70
|
|
|
57
71
|
### Колбэки
|
|
58
72
|
|
|
@@ -70,6 +70,15 @@
|
|
|
70
70
|
let boards: Board[] = $state([])
|
|
71
71
|
let now = $state(Date.now())
|
|
72
72
|
let mapInstance: any = $state()
|
|
73
|
+
/* Вид карты (центр, масштаб, поворот) переживает сворачивание: тело виджета пересоздаёт карту, и она открывается там, где её оставили */
|
|
74
|
+
let view = $state.raw({ center: { lat: 30, lng: 0 }, zoom: 1.5, bearing: 0 })
|
|
75
|
+
$effect(() => {
|
|
76
|
+
const map = mapInstance
|
|
77
|
+
if (!map) return
|
|
78
|
+
const save = () => (view = { center: map.getCenter(), zoom: map.getZoom(), bearing: map.getBearing() })
|
|
79
|
+
map.on("moveend", save)
|
|
80
|
+
return () => map.off("moveend", save)
|
|
81
|
+
})
|
|
73
82
|
let isCopied = $state(false)
|
|
74
83
|
let isDarkMode = $state(false)
|
|
75
84
|
|
|
@@ -211,8 +220,9 @@
|
|
|
211
220
|
bind:map={mapInstance}
|
|
212
221
|
class="h-full w-full overflow-hidden rounded-[14px] border border-(--hairline-color)"
|
|
213
222
|
style={isDarkMode ? "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json" : "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"}
|
|
214
|
-
zoom={
|
|
215
|
-
center={
|
|
223
|
+
zoom={view.zoom}
|
|
224
|
+
center={view.center}
|
|
225
|
+
bearing={view.bearing}
|
|
216
226
|
maxPitch={0}
|
|
217
227
|
>
|
|
218
228
|
<NavigationControl />
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { onDestroy } from "svelte"
|
|
2
|
+
import { onDestroy, untrack } from "svelte"
|
|
3
3
|
import { getMapContext } from "./contexts.svelte.js"
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
@@ -23,31 +23,36 @@
|
|
|
23
23
|
properties: {},
|
|
24
24
|
})
|
|
25
25
|
|
|
26
|
+
/* Источник и слой — один раз; пока стиль карты грузится, добавление ждёт в очереди waitForStyleLoaded */
|
|
26
27
|
$effect(() => {
|
|
27
28
|
if (!mapCtx.map || added) return
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
untrack(() => {
|
|
30
|
+
mapCtx.addSource(id, { type: "geojson", data: toGeoJSON() })
|
|
31
|
+
mapCtx.addLayer({
|
|
32
|
+
id,
|
|
33
|
+
type: "line",
|
|
34
|
+
source: id,
|
|
35
|
+
layout: { "line-join": "round", "line-cap": "round" },
|
|
36
|
+
paint: { "line-color": color, "line-width": width, "line-opacity": opacity },
|
|
37
|
+
})
|
|
36
38
|
})
|
|
37
39
|
added = true
|
|
38
40
|
})
|
|
39
41
|
|
|
42
|
+
/* Координаты и оформление читаются до любых проверок — иначе эффект не подписывается на них и, запущенный до загрузки стиля, больше не срабатывает */
|
|
40
43
|
$effect(() => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
const data = toGeoJSON()
|
|
45
|
+
if (!added) return
|
|
46
|
+
mapCtx.waitForStyleLoaded((map) => (map.getSource(id) as any)?.setData(data))
|
|
44
47
|
})
|
|
45
48
|
|
|
46
49
|
$effect(() => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
mapCtx.map
|
|
50
|
-
|
|
50
|
+
const paint = { "line-color": color, "line-width": width, "line-opacity": opacity }
|
|
51
|
+
if (!added) return
|
|
52
|
+
mapCtx.waitForStyleLoaded((map) => {
|
|
53
|
+
if (!map.getLayer(id)) return
|
|
54
|
+
for (const [name, value] of Object.entries(paint)) map.setPaintProperty(id, name, value)
|
|
55
|
+
})
|
|
51
56
|
})
|
|
52
57
|
|
|
53
58
|
onDestroy(() => {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
style?: any | string
|
|
10
10
|
zoom?: number
|
|
11
11
|
center?: any
|
|
12
|
+
bearing?: number
|
|
12
13
|
maxPitch?: number
|
|
13
14
|
map?: any
|
|
14
15
|
children?: Snippet<[any]>
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
style = { version: 8, sources: {}, layers: [] },
|
|
20
21
|
zoom = $bindable(undefined),
|
|
21
22
|
center = $bindable(undefined),
|
|
23
|
+
bearing = 0,
|
|
22
24
|
maxPitch = 60,
|
|
23
25
|
map = $bindable(undefined),
|
|
24
26
|
children,
|
|
@@ -45,13 +47,18 @@
|
|
|
45
47
|
target.on("sourcedata", tryCollapse)
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
/* Стиль, с которым создана или на который переключена карта — setStyle только при его реальной смене */
|
|
51
|
+
let appliedStyle: any = undefined
|
|
52
|
+
|
|
48
53
|
const createMap = () => {
|
|
49
54
|
if (!window.maplibregl || map || !container) return
|
|
55
|
+
appliedStyle = style
|
|
50
56
|
map = new window.maplibregl.Map({
|
|
51
57
|
container,
|
|
52
58
|
style,
|
|
53
59
|
zoom,
|
|
54
60
|
center,
|
|
61
|
+
bearing,
|
|
55
62
|
maxPitch,
|
|
56
63
|
attributionControl: { compact: true },
|
|
57
64
|
})
|
|
@@ -63,7 +70,8 @@
|
|
|
63
70
|
$effect(createMap)
|
|
64
71
|
|
|
65
72
|
$effect(() => {
|
|
66
|
-
if (map && style) {
|
|
73
|
+
if (map && style && style !== appliedStyle) {
|
|
74
|
+
appliedStyle = style
|
|
67
75
|
mapCtx.setStyle(style)
|
|
68
76
|
}
|
|
69
77
|
})
|
package/dist/types.d.ts
CHANGED
|
@@ -116,8 +116,13 @@ export interface IFileAttachProps {
|
|
|
116
116
|
name?: string;
|
|
117
117
|
class?: string;
|
|
118
118
|
};
|
|
119
|
-
type?: "file" | "image";
|
|
119
|
+
type?: "file" | "image" | "button";
|
|
120
120
|
accept?: string;
|
|
121
|
+
info?: {
|
|
122
|
+
text: string;
|
|
123
|
+
side: "top" | "bottom" | "left" | "right";
|
|
124
|
+
};
|
|
125
|
+
icon?: ConstructorOfATypedSvelteComponent | string | null;
|
|
121
126
|
imageSize?: {
|
|
122
127
|
height?: string;
|
|
123
128
|
width?: string;
|