poe-svelte-ui-lib 1.92.0 → 1.94.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 +21 -12
- package/dist/Accordion/Accordion.svelte +4 -2
- package/dist/Modal.README.md +12 -11
- package/dist/Modal.svelte +1 -1
- package/dist/Table/Table.svelte +1 -1
- package/dist/WidgetAccentPicker.svelte +5 -2
- package/dist/WidgetDeviceInfo/WidgetDeviceInfo.svelte +1 -1
- package/dist/WidgetFileSystem/WidgetFileSystem.svelte +1 -1
- package/dist/WidgetMap/README.md +13 -9
- package/dist/WidgetMap/WidgetMap.svelte +2 -2
- package/dist/WidgetMap/mission/HelpText.svelte +41 -0
- package/dist/WidgetMap/mission/HelpText.svelte.d.ts +6 -0
- package/dist/WidgetMap/mission/MapAgro.svelte +147 -62
- package/dist/WidgetMap/mission/MapRoute.svelte +69 -49
- package/dist/WidgetMap/mission/MissionLayers.svelte +80 -24
- package/dist/WidgetMap/mission/MissionLayers.svelte.d.ts +3 -0
- package/dist/WidgetMap/mission/MissionSim.svelte +19 -9
- package/dist/WidgetMap/mission/MissionSim.svelte.d.ts +2 -0
- package/dist/WidgetMap/mission/SpinnerIcon.svelte +3 -4
- package/dist/WidgetMap/mission/coverage.js +204 -80
- package/dist/WidgetMap/mission/plan.js +92 -34
- package/dist/WidgetPFD/WidgetPFD.svelte +2 -2
- 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 +16 -10
- package/dist/options.js +2 -1
- package/dist/protocols/xPoeDebugView.js +6 -6
- package/dist/theme.css +94 -65
- package/dist/widgetAccent.d.ts +8 -0
- package/dist/widgetAccent.js +12 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,11 +70,11 @@ npm install poe-svelte-ui-lib
|
|
|
70
70
|
|
|
71
71
|
У каждого компонента есть парный `<Компонент>Props.svelte`. Это панель свойств для конструктора DevCloud. Пропсы у всех панелей одинаковые:
|
|
72
72
|
|
|
73
|
-
| Пропс
|
|
74
|
-
|
|
75
|
-
| `component`
|
|
76
|
-
| `onPropertyChange` | Сообщает об изменении свойств, имени, доступа или события
|
|
77
|
-
| `forConstructor`
|
|
73
|
+
| Пропс | Что это |
|
|
74
|
+
| ------------------ | ---------------------------------------------------------------------------- |
|
|
75
|
+
| `component` | Компонент интерфейса и его свойства |
|
|
76
|
+
| `onPropertyChange` | Сообщает об изменении свойств, имени, доступа или события |
|
|
77
|
+
| `forConstructor` | `true` - полная панель конструктора, `false` - упрощённая для редактирования |
|
|
78
78
|
|
|
79
79
|
## Протоколы
|
|
80
80
|
|
|
@@ -104,7 +104,16 @@ npm install poe-svelte-ui-lib
|
|
|
104
104
|
|
|
105
105
|
Текст идёт в три ступени: основной `--font-color`, вторичный `--muted-color`, подсказки `--faint-color`. Все три проходят WCAG AA (не ниже 4,5:1) на любом фоне темы. Текст не делают полупрозрачным (`opacity`), для вторичного текста берут `--muted-color`. Мельче 11px подписи не делают.
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
Цвета ролей взяты у Apple: красный, оранжевый, жёлтый, зелёный, голубой, синий, фиолетовый, розовый и серый. У каждого цвета четыре варианта:
|
|
108
|
+
|
|
109
|
+
- `--*-color` - заливка. Текст на ней белый, а на оранжевой и жёлтой тёмный (`--on-orange`, `--on-yellow`). Компоненты берут его из `--on-accent`, классы `bg-orange` и `bg-yellow` подставляют его сами;
|
|
110
|
+
- `--*-text` - цветной текст;
|
|
111
|
+
- `--*-soft` - мягкий фон блока или строки;
|
|
112
|
+
- `--*-mark` - чистый цвет Apple для полос, точек и индикаторов. Для текста он не годится, жёлтый и голубой на белом не читаются.
|
|
113
|
+
|
|
114
|
+
Акцентный цвет `--accent-color` - это синий Apple.
|
|
115
|
+
|
|
116
|
+
Цветной текст пишут через `--red-text`, `--green-text`, `--orange-text` и так далее (классы `text-(--red-text)`), а не через `--red-color`. Заливки `--*-color` как текст плохо читаются: в тёмной теме они тёмные, а жёлтая и оранжевая слишком светлые на белом.
|
|
108
117
|
|
|
109
118
|
Панели не делают полупрозрачными, на фоне они теряются. Выпадающие меню делают через `ui-panel` с тенью `--elevation-3`.
|
|
110
119
|
|
|
@@ -152,13 +161,13 @@ export const T = createTranslator(translations)
|
|
|
152
161
|
|
|
153
162
|
**Короткое**, по кнопке (i). Лежит в словаре под ключом `component.info.<Тип>`. Его показывают витрина и конструктор DevCloud. Разделы идут в таком порядке, ненужные пропускаются:
|
|
154
163
|
|
|
155
|
-
| Раздел
|
|
156
|
-
|
|
157
|
-
| Назначение
|
|
164
|
+
| Раздел | Что писать |
|
|
165
|
+
| ------------ | ------------------------------------------------------------------------------- |
|
|
166
|
+
| Назначение | Одна-две фразы: что это и когда применять |
|
|
158
167
|
| Пакет данных | Откуда данные, каким пакетом и как часто их слать, что компонент отправляет сам |
|
|
159
|
-
| Ключи
|
|
160
|
-
| Особенности
|
|
161
|
-
| Настройки
|
|
168
|
+
| Ключи | По пункту на ключ: тип, единицы, смысл |
|
|
169
|
+
| Особенности | Поведение, ограничения, советы |
|
|
170
|
+
| Настройки | Что меняется в панели свойств |
|
|
162
171
|
|
|
163
172
|
**Подробное**, `src/lib/<Компонент>/README.md`. Оно открывается на вкладке «Инфо» витрины. Начало повторяет короткое описание и не должно ему противоречить. Дальше идут пропсы, примеры, панель свойств и внутреннее устройство.
|
|
164
173
|
|
|
@@ -29,9 +29,11 @@
|
|
|
29
29
|
<div class="overflow-hidden rounded-[14px] border border-(--hairline-color) bg-(--back-color)">
|
|
30
30
|
<!-- Фон заголовка - мягкий оттенок мастер-цвета (светлый в светлой теме, тёмный в тёмной), текст - мастер-цвет с цветом шрифта 60/40, раскрытое содержимое остаётся на чистом фоне (--back-color). -->
|
|
31
31
|
<!-- Заголовок: кнопка сворачивания на всю ширину и необязательные кнопки справа (actions) - вне неё, чтобы не сворачивать блок -->
|
|
32
|
-
<div
|
|
32
|
+
<div
|
|
33
|
+
class="flex w-full items-center bg-(--accent-soft) transition-colors duration-150 hover:bg-[color-mix(in_srgb,var(--accent-color)_12%,var(--accent-soft))]"
|
|
34
|
+
>
|
|
33
35
|
<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))]
|
|
36
|
+
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))]"
|
|
35
37
|
onclick={toggle}
|
|
36
38
|
>
|
|
37
39
|
<div class="flex min-w-0 flex-1 items-center gap-3">
|
package/dist/Modal.README.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
## Особенности
|
|
8
8
|
|
|
9
9
|
- Подложка затемняет страницу на 40% и размывает её.
|
|
10
|
+
- Окно не выше 90% экрана. Длинное содержимое прокручивается внутри, шапка с крестиком остаётся на месте.
|
|
10
11
|
- Escape и клик мимо окна закрывают только верхнее окно из открытых.
|
|
11
12
|
- Выпадающие списки `Select` внутри окна вынесены в `body`. Клик по ним не считается кликом мимо окна.
|
|
12
13
|
- Содержимое передаётся сниппетом `main`, обычные дочерние элементы не подойдут. Подвал `footer` необязателен.
|
|
@@ -14,16 +15,16 @@
|
|
|
14
15
|
|
|
15
16
|
## Пропсы
|
|
16
17
|
|
|
17
|
-
| Название
|
|
18
|
-
|
|
19
|
-
| `isOpen`
|
|
20
|
-
| `title`
|
|
21
|
-
| `width`
|
|
22
|
-
| `wrapperClass` | `string`
|
|
23
|
-
| `mainClass`
|
|
24
|
-
| `main`
|
|
25
|
-
| `footer`
|
|
26
|
-
| `onCancel`
|
|
18
|
+
| Название | Тип | По умолчанию | Описание |
|
|
19
|
+
| -------------- | ------------ | ------------ | ---------------------------------------------------------- |
|
|
20
|
+
| `isOpen` | `boolean` | `false` | Открыто ли окно. Можно связать через `bind:isOpen` |
|
|
21
|
+
| `title` | `string` | | Заголовок |
|
|
22
|
+
| `width` | `string` | `""` | Ширина, например `"600px"`. По умолчанию из `wrapperClass` |
|
|
23
|
+
| `wrapperClass` | `string` | `""` | Классы окна |
|
|
24
|
+
| `mainClass` | `string` | `""` | Классы области содержимого |
|
|
25
|
+
| `main` | `Snippet` | обязателен | Содержимое |
|
|
26
|
+
| `footer` | `Snippet` | | Подвал, обычно кнопки |
|
|
27
|
+
| `onCancel` | `() => void` | закрыть окно | Окно закрывают крестиком, кликом мимо или Escape |
|
|
27
28
|
|
|
28
29
|
## Пример
|
|
29
30
|
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
|
|
53
54
|
```ts
|
|
54
55
|
ModalStack.subscribe // список id в порядке открытия
|
|
55
|
-
ModalStack.open(id)
|
|
56
|
+
ModalStack.open(id) // добавить в конец
|
|
56
57
|
ModalStack.close(id) // убрать
|
|
57
58
|
```
|
|
58
59
|
|
package/dist/Modal.svelte
CHANGED
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
<div
|
|
83
83
|
data-modal
|
|
84
84
|
class={twMerge(
|
|
85
|
-
"flex w-300 flex-col overflow-hidden rounded-[20px] border border-(--hairline-color) bg-(--back-color) shadow-(--elevation-3)",
|
|
85
|
+
"flex max-h-[90vh] w-300 flex-col overflow-hidden rounded-[20px] border border-(--hairline-color) bg-(--back-color) shadow-(--elevation-3)",
|
|
86
86
|
wrapperClass,
|
|
87
87
|
)}
|
|
88
88
|
style="width: {width}; z-index: {zIndex};"
|
package/dist/Table/Table.svelte
CHANGED
|
@@ -380,7 +380,7 @@
|
|
|
380
380
|
{#if isDropdownOpen?.x === j && isDropdownOpen.y === i && isDropdownOpen.index === index}
|
|
381
381
|
{@const cords = document.getElementById(`select${i}-${j}-${index}`)?.getBoundingClientRect()}
|
|
382
382
|
<div
|
|
383
|
-
class="fixed z-50 overflow-
|
|
383
|
+
class="fixed z-50 max-h-[40vh] overflow-y-auto rounded-[10px] border border-(--hairline-color) bg-(--back-color) p-1 shadow-(--elevation-2)"
|
|
384
384
|
style="top: calc({cords?.bottom}px + 4px); left: {cords?.left}px; width: {cords?.width}px;"
|
|
385
385
|
transition:slide={{ duration: selectSlideDuration }}
|
|
386
386
|
>
|
|
@@ -14,13 +14,16 @@
|
|
|
14
14
|
onUpdate: (value: string) => void
|
|
15
15
|
wrapperClass?: string
|
|
16
16
|
} = $props()
|
|
17
|
+
|
|
18
|
+
/* bg-max не показываем: без выбора виджет и так синий, это цвет по умолчанию */
|
|
19
|
+
const options = $derived($optionsStore.COLOR_OPTIONS.filter((o) => o.value !== "bg-max"))
|
|
17
20
|
</script>
|
|
18
21
|
|
|
19
22
|
<Select
|
|
20
23
|
{wrapperClass}
|
|
21
24
|
label={{ name: $T("ui.accent") }}
|
|
22
25
|
type="buttons"
|
|
23
|
-
options
|
|
24
|
-
value={
|
|
26
|
+
{options}
|
|
27
|
+
value={options.find((o) => o.value === value) ?? options.find((o) => o.value === "bg-blue")}
|
|
25
28
|
onUpdate={(option) => onUpdate((option as IOption<string>).value ?? "")}
|
|
26
29
|
/>
|
|
@@ -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
|
@@ -60,18 +60,19 @@ Telemetry_SendObject(g_telemetry_stream, g_mcfg.DevSN, "Data", dev, sizeof(dev)
|
|
|
60
60
|
- MapLibre загружается с CDN один раз на страницу. Без интернета видна заглушка «Карта не загружена».
|
|
61
61
|
- Источник подложки (CARTO и OpenStreetMap) указывать обязательно. Подпись свёрнута в кнопку «i» в углу карты.
|
|
62
62
|
- Сворачивание, мастер-цвет и кнопка «?» с подсказками работают как у `WidgetWiFi`.
|
|
63
|
+
- Мастер-цвет красит весь виджет: кнопки, линию маршрута, точки, точку взлёта, переходы и аппарат в имитации. Цвета со смыслом не меняются: поля зелёные, запретные зоны красные, развороты оранжевые, «Очистить», удаление и ошибка отправки красные.
|
|
63
64
|
|
|
64
65
|
## Режим имитации
|
|
65
66
|
|
|
66
67
|
`isTest` включает имитацию шести аппаратов `SIM-1`-`SIM-6` без устройства. Они летают над одним районом Минска, в пределах пары километров, и обновляются раз в полсекунды. На связи всегда три аппарата. Через 40-100 секунд один из них пропадает, и его сразу сменяет тот, кто молчит дольше всех. Поэтому видно все состояния маркера: свежий, красный устаревший, скрытый и снова появившийся. При включении карта переходит к этому району. Пока режим включён, `value` и `groups` не учитываются. В конструкторе все остальные свойства становятся бледными и недоступными, пока имитацию не выключат.
|
|
67
68
|
|
|
68
|
-
В режимах «Маршрут» и «Агро» `isTest` включает имитацию полёта по рассчитанной траектории. Аппарат летит по маршруту со скоростями его точек или по проходам «Агро» и оставляет
|
|
69
|
+
В режимах «Маршрут» и «Агро» `isTest` включает имитацию полёта по рассчитанной траектории. Аппарат летит по маршруту со скоростями его точек или по проходам «Агро» и оставляет след цвета виджета. В «Агро» маркер - квадрокоптер, он зелёный, пока идёт распыление. Панель внизу карты по центру показывает время полёта, процент пройденного пути, высоту, скорость и распыление; там же запуск и пауза, ускорение ×1, ×5, ×10 или ×25 (по умолчанию ×5) и полоса прокрутки пути: её перетаскивание ставит полёт на паузу, чтобы рассмотреть любое место траектории. Полёт запускается вручную кнопкой ▶ и останавливается в конце пути; повторный запуск начинает его заново. При любом изменении маршрута или полей аппарат возвращается в начало и встаёт на паузу. Аппараты из `groups` в это время не рисуются.
|
|
69
70
|
|
|
70
71
|
## Режим «Маршрут»
|
|
71
72
|
|
|
72
73
|
`mode="route"` - обычный планировщик миссии: точки по порядку облёта, у каждой своя высота и скорость. Аппараты из `groups` рисуются поверх маршрута, как в режиме «Объекты».
|
|
73
74
|
|
|
74
|
-
1.
|
|
75
|
+
1. **«Редактор точек»** на карте включает правку. Клик по карте добавляет точку в конец маршрута. Новая точка берёт высоту и скорость предыдущей, первая получает 30 м и 8 м/с. Esc или двойной клик заканчивают правку.
|
|
75
76
|
2. В режиме правки точки можно перетаскивать. Потяните за середину отрезка, чтобы вставить точку между соседними. Правый клик удаляет точку. Без правки на карте видны только номера точек, их не сдвинуть случайно. Точку взлёта `H` можно перетаскивать всегда.
|
|
76
77
|
3. Блок **«Итог»**: сверху расчёт (число точек, длина пути, время, наибольшая высота), ниже список точек с высотой, скоростью, расстоянием от предыдущей точки и удалением; порядок меняется перетаскиванием строки за ручку, как в списках панелей свойств, под ним флажок возврата с посадкой (RTL). Клик по номеру показывает точку на карте.
|
|
77
78
|
|
|
@@ -93,18 +94,21 @@ Telemetry_SendObject(g_telemetry_stream, g_mcfg.DevSN, "Data", dev, sizeof(dev)
|
|
|
93
94
|
|
|
94
95
|
`mode="agro"` превращает карту в генератор миссии для агродрона. Оператор обводит на карте одно или несколько полей и запретные зоны, задаёт угол проходов и ширину захвата. Карта строит траекторию, которая покрывает все поля, обходит зоны и выключает распылитель на разворотах и переходах. Готовую миссию можно скачать, скопировать или отправить на борт в формате QGC WPL 110 для автопилота ArduPilot.
|
|
95
96
|
|
|
96
|
-
Аппараты из `groups` рисуются поверх миссии так же, как в режиме «Объекты»: маркер, карточка по клику и траектория, если она включена. План хранит владелец карты через `plan` и `onPlanChange`, миссия уходит наружу через `onSend`. Без `plan` показываются
|
|
97
|
+
Аппараты из `groups` рисуются поверх миссии так же, как в режиме «Объекты»: маркер, карточка по клику и траектория, если она включена. План хранит владелец карты через `plan` и `onPlanChange`, миссия уходит наружу через `onSend`. Без `plan` показываются три демо-поля под Минском: большое сложной формы с прорезью и выступами, рощей, опорой ЛЭП и постройкой, среднее с выступами, крюком и прудом, малое с вырезом и деревом. Между полями стоит большая треугольная зона, внизу зона, которая задевает все три поля; перелёты их обходят. Ширина захвата по умолчанию 5 м, отступ от границ 2 м. Ширина захвата по умолчанию 5 м, отступ от границ 2 м.
|
|
97
98
|
|
|
98
99
|
### Как пользоваться
|
|
99
100
|
|
|
100
|
-
1.
|
|
101
|
-
2.
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
1. **«Редактор полей»** на карте: кликами по карте обведите участок. Контур замыкается кликом по первой точке, двойным кликом, Enter или кнопкой «Замкнуть». Backspace убирает последнюю точку, Esc сбрасывает начатый контур. Следующий контур станет ещё одним полем.
|
|
102
|
+
2. **«Редактор запретных зон»** на карте: обведите места, где нельзя лететь и распылять: деревья, опоры ЛЭП, постройки, водоёмы. Зон может быть сколько угодно. Зона может лежать внутри поля, выходить за его край или стоять вне полей. Из проходов вырезается только часть зоны внутри поля, и площадь поля уменьшается только на эту часть. Зона относится к каждому полю, которое задевает.
|
|
103
|
+
|
|
104
|
+
**Важно.** Перелёты от точки взлёта к полю, между полями и домой обходят все зоны с отступом, в том числе стоящие вне полей. Опоры ЛЭП, вышки, деревья и постройки на пути между полями обязательно обводите запретными зонами. Иначе аппарат полетит напрямую и может в них врезаться.
|
|
105
|
+
|
|
106
|
+
3. **Точка взлёта** (цвета виджета) видна всегда, её можно перетащить в любой момент.
|
|
107
|
+
4. В блоке **«Маршрут и внесение»** задайте ширину захвата, отступ от границ, высоту, скорость, норму внесения и объём бака. У каждого поля свой угол проходов: новое поле сразу получает лучший угол, «Авторасчёт» пересчитывает углы всех полей приближениями по полному расчёту траектории: шаг 5° по всем направлениям и вдоль сторон поля, затем 1° около трёх лучших, затем 0,1° около победителя; лучший угол даёт наименьшую цену: время облёта плюс около 4 с на разворот плюс штраф за недолив, где метр недолитого прохода стоит как три метра полёта. Пока идёт расчёт, на карте видно колесо ожидания «Расчёт оптимального маршрута» с полосой и процентом (на большом поле это несколько секунд), а ползунок «Угол для всех полей» ставит всем один угол, например чтобы проходы шли вдоль рядов посадки. Угол отдельного поля правится в его строке в блоке «Итог». Кнопка «i» справа от «Авторасчёта» открывает подробное описание алгоритма: проходы, ячейки, порядок облёта, штрафы и поиск угла приближениями. Текст лежит в словаре под ключом `widget.mission.autoInfo`.
|
|
104
108
|
5. Блок **«Итог»** показывает контуры, площадь к обработке, покрытие, длину пути, время, расход раствора и число дозаправок.
|
|
105
109
|
6. **«Скачать»**, **«Копировать»** или **«Отправить»** миссию.
|
|
106
110
|
|
|
107
|
-
Точки контуров видны и перетаскиваются, только пока
|
|
111
|
+
Точки контуров видны и перетаскиваются, только пока включён «Редактор полей» или «Редактор запретных зон». Потяните за середину стороны, чтобы добавить точку. Правый клик удаляет точку. Поля и зоны удаляются крестиком в блоке «Итог». Там же видно, где какое поле или зона: при наведении на строку контур подсвечивается на карте, нажатие на название приближает к нему карту. Подписей на самой карте нет, маленькой зоне их некуда поставить. Режим выключается той же кнопкой, кнопкой «Готово» или Esc.
|
|
108
112
|
|
|
109
113
|
### Как строится траектория
|
|
110
114
|
|
|
@@ -170,7 +174,7 @@ Telemetry_SendObject(g_telemetry_stream, g_mcfg.DevSN, "Data", dev, sizeof(dev)
|
|
|
170
174
|
| `onSend` | `(mission, plan) => void \| Promise` | | Отправка миссии, `mission` - текст QGC WPL 110 |
|
|
171
175
|
| `label.name` | `string` | | Заголовок. Пусто - «Карта» на языке пользователя |
|
|
172
176
|
| `wrapperClass` | `string` | `""` | Классы обёртки |
|
|
173
|
-
| `componentClass` | `string` | `""` | Мастер-цвет `bg-*`
|
|
177
|
+
| `componentClass` | `string` | `""` | Мастер-цвет `bg-*` всего виджета |
|
|
174
178
|
| `collapsed` | `boolean` | `false` | Свёрнут ли |
|
|
175
179
|
| `persistKey` | `string` | | Ключ для запоминания свёрнутого состояния |
|
|
176
180
|
|
|
@@ -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
|
|
|
@@ -415,7 +415,7 @@
|
|
|
415
415
|
|
|
416
416
|
<button
|
|
417
417
|
class="flex cursor-pointer items-center gap-1 rounded-lg border px-2 py-0.5 text-xs font-semibold transition {isFollowed
|
|
418
|
-
? 'border-(--accent-color) bg-(--accent-color) text-
|
|
418
|
+
? 'border-(--accent-color) bg-(--accent-color) text-(--on-accent)'
|
|
419
419
|
: 'border-(--hairline-color) bg-transparent opacity-80 hover:opacity-100'}"
|
|
420
420
|
aria-pressed={isFollowed}
|
|
421
421
|
onclick={() => toggleFollow(board.group)}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<!-- $lib/WidgetMap/mission/HelpText.svelte - справка из простого текста: строки "## " - заголовки, "- " - пункты списка, остальное - абзацы, **жирный** внутри строки -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
let { text }: { text: string } = $props()
|
|
4
|
+
|
|
5
|
+
type Block = { kind: "h"; text: string } | { kind: "p"; text: string } | { kind: "ul"; items: string[] }
|
|
6
|
+
|
|
7
|
+
let blocks = $derived.by(() => {
|
|
8
|
+
const out: Block[] = []
|
|
9
|
+
for (const raw of text.split("\n")) {
|
|
10
|
+
const line = raw.trim()
|
|
11
|
+
if (!line) continue
|
|
12
|
+
if (line.startsWith("## ")) out.push({ kind: "h", text: line.slice(3) })
|
|
13
|
+
else if (line.startsWith("- ")) {
|
|
14
|
+
const last = out[out.length - 1]
|
|
15
|
+
if (last?.kind === "ul") last.items.push(line.slice(2))
|
|
16
|
+
else out.push({ kind: "ul", items: [line.slice(2)] })
|
|
17
|
+
} else out.push({ kind: "p", text: line })
|
|
18
|
+
}
|
|
19
|
+
return out
|
|
20
|
+
})
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
{#snippet inline(line: string)}
|
|
24
|
+
{#each line.split("**") as part, i}{#if i % 2}<strong class="font-semibold">{part}</strong>{:else}{part}{/if}{/each}
|
|
25
|
+
{/snippet}
|
|
26
|
+
|
|
27
|
+
<div class="flex flex-col gap-1.5 text-[14px] leading-snug text-(--font-color)">
|
|
28
|
+
{#each blocks as block}
|
|
29
|
+
{#if block.kind === "h"}
|
|
30
|
+
<h3 class="m-0 mt-2.5 text-[15px] font-semibold">{@render inline(block.text)}</h3>
|
|
31
|
+
{:else if block.kind === "ul"}
|
|
32
|
+
<ul class="m-0 flex list-none flex-col gap-0.5 p-0">
|
|
33
|
+
{#each block.items as item}
|
|
34
|
+
<li class="relative m-0 pl-4 before:absolute before:left-0 before:text-(--accent-color) before:content-['•']">{@render inline(item)}</li>
|
|
35
|
+
{/each}
|
|
36
|
+
</ul>
|
|
37
|
+
{:else}
|
|
38
|
+
<p class="m-0">{@render inline(block.text)}</p>
|
|
39
|
+
{/if}
|
|
40
|
+
{/each}
|
|
41
|
+
</div>
|