poe-svelte-ui-lib 1.43.0 → 1.44.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/README.md +39 -5
- package/dist/Accordion/Accordion.svelte +18 -10
- package/dist/Accordion/Accordion.svelte.d.ts +1 -1
- package/dist/Accordion/AccordionProps.svelte +10 -1
- package/dist/Accordion/README.md +17 -15
- package/dist/Button/ButtonProps.svelte +7 -18
- package/dist/CommonSnippets.svelte +5 -18
- package/dist/CopiedChip.svelte +2 -1
- package/dist/Graph/Graph.svelte +12 -15
- package/dist/Graph/GraphProps.svelte +1 -1
- package/dist/Graph/README.md +1 -0
- package/dist/Input/Input.svelte +7 -6
- package/dist/Input/InputProps.svelte +13 -28
- package/dist/Joystick/JoystickProps.svelte +0 -15
- package/dist/Modal.svelte +2 -1
- package/dist/PeriodChart/PeriodChart.svelte +9 -6
- package/dist/PeriodChart/PeriodChartProps.svelte +0 -22
- package/dist/ProgressBar/ProgressBarProps.svelte +2 -16
- package/dist/PropsListModal.svelte +2 -1
- package/dist/Select/SelectProps.svelte +2 -12
- package/dist/Slider/Slider.svelte +5 -4
- package/dist/Switch/SwitchProps.svelte +2 -20
- package/dist/Table/Table.svelte +1 -1
- package/dist/Table/TableProps.svelte +6 -26
- package/dist/Tabs/README.md +1 -1
- package/dist/Tabs/Tabs.svelte +7 -9
- package/dist/Tabs/Tabs.svelte.d.ts +1 -1
- package/dist/Tabs/TabsProps.svelte +3 -10
- package/dist/TextField/TextFieldProps.svelte +2 -5
- package/dist/TextsEditor.svelte +86 -0
- package/dist/TextsEditor.svelte.d.ts +13 -0
- package/dist/VideoViewer/VideoViewerProps.svelte +1 -1
- package/dist/Widget/Widget.svelte +5 -4
- package/dist/Widget/WidgetProps.svelte +1 -38
- package/dist/WidgetAccentPicker.svelte +3 -2
- package/dist/WidgetDeviceInfo/README.md +10 -10
- package/dist/WidgetDeviceInfo/WidgetDeviceInfo.svelte +22 -16
- package/dist/WidgetDeviceInfo/WidgetDeviceInfoProps.svelte +7 -10
- package/dist/WidgetFileSystem/README.md +16 -16
- package/dist/WidgetFileSystem/WidgetFileSystem.svelte +29 -19
- package/dist/WidgetFileSystem/WidgetFileSystemProps.svelte +9 -12
- package/dist/WidgetMap/README.md +15 -15
- package/dist/WidgetMap/WidgetMap.svelte +2 -2
- package/dist/WidgetMap/WidgetMapProps.svelte +5 -8
- package/dist/WidgetPFD/README.md +14 -14
- package/dist/WidgetPFD/WidgetPFD.svelte +106 -131
- package/dist/WidgetPFD/WidgetPFDProps.svelte +10 -13
- package/dist/WidgetRGBStrip/README.md +12 -12
- package/dist/WidgetRGBStrip/WidgetRGBStrip.svelte +27 -31
- package/dist/WidgetRGBStrip/WidgetRGBStripProps.svelte +3 -6
- package/dist/WidgetStackInfo/README.md +13 -13
- package/dist/WidgetStackInfo/WidgetStackInfo.svelte +35 -25
- package/dist/WidgetStackInfo/WidgetStackInfoProps.svelte +8 -11
- package/dist/WidgetWiFi/WidgetWiFi.svelte +56 -20
- package/dist/WidgetWiFi/WidgetWiFiProps.svelte +9 -12
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/locales/i18n.d.ts +2 -1
- package/dist/locales/i18n.js +4 -3
- package/dist/locales/localize.d.ts +13 -0
- package/dist/locales/localize.js +70 -0
- package/dist/locales/translations.js +338 -35
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
# poe-svelte-ui-lib
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Библиотека компонентов на Svelte 5 (руны) для интерфейсов устройств компании. Один и тот же компонент выглядит и ведёт себя одинаково везде, где он используется:
|
|
4
|
+
|
|
5
|
+
- **DevCloud** — облако: конструктор GUI устройства, панель управления, документация;
|
|
6
|
+
- **WebApp** — локальный веб-интерфейс прошивок ProdFactory-ESP, встроенный в устройство;
|
|
7
|
+
- **SYP-DevTerminal** — настольный терминал разработчика.
|
|
8
|
+
|
|
9
|
+
Витрина с живыми примерами: [oleg-perevyshin.github.io/ElementsUI](https://oleg-perevyshin.github.io/ElementsUI/).
|
|
10
|
+
|
|
11
|
+
## Состав
|
|
12
|
+
|
|
13
|
+
**Смарт-виджеты** — готовые блоки под конкретную задачу, работают с группой полей прошивки по префиксу, переменные привязывать не нужно; шапка со сворачиванием и мастер-цветом:
|
|
14
|
+
|
|
15
|
+
- `WidgetWiFi` — настройка сети: режим, клиент, точка доступа, поиск сетей;
|
|
16
|
+
- `WidgetDeviceInfo` — паспорт устройства и основные настройки, перезагрузка;
|
|
17
|
+
- `WidgetFileSystem` — память, список файлов, удаление и закачка;
|
|
18
|
+
- `WidgetStackInfo` — отладка: свободная память и запас стека задач;
|
|
19
|
+
- `WidgetRGBStrip` — адресная светодиодная лента: цвет, эффекты, параметры ленты;
|
|
20
|
+
- `WidgetPFD` — пилотажный дисплей: авиагоризонт, ленты, маршрут, связь, сообщения автопилота;
|
|
21
|
+
- `WidgetMap` — карта объектов с траекториями, несколько групп-объектов на одной карте.
|
|
22
|
+
|
|
23
|
+
**Компоненты** — привязываются к одной переменной устройства: `Input`, `Select`, `Switch`, `Slider`, `Button`, `Joystick`, `ProgressBar`, `Graph`, `PeriodChart`, `Table`, `FileAttach`, `VideoViewer`, `TextField`, `Widget` (карточка одного значения).
|
|
24
|
+
|
|
25
|
+
**Раскладка** — `Accordion`, `Tabs`, `Divider`, `Carousel`, `Modal`.
|
|
26
|
+
|
|
27
|
+
## Как устроено
|
|
28
|
+
|
|
29
|
+
- **Компонент только отображает и сообщает.** Он получает значения через пропсы и отдаёт изменения колбэками (`onUpdate`, `onChange`, `onSave`…). С сетью, WebSocket и `DeviceStore` работает приложение (`GUIPreview` в DevCloud и WebApp): значения переменных устройства хранятся под ключами `<ModSN>|<ключ>`, смарт-виджеты читают свою группу (`MAV.Lat`, `CFG.DevName`…).
|
|
30
|
+
- **Конструктор свойств.** У каждого компонента есть `<Компонент>Props.svelte` — панель свойств для конструктора DevCloud.
|
|
31
|
+
- **Описания.** Короткое описание (i) — `component.info.<Тип>` в переводах (ru/en/zh), его показывают конструктор облака и витрина; развёрнутое — README рядом с исходником компонента.
|
|
32
|
+
- **Тема** — один файл `poe-svelte-ui-lib/theme.css` для всех приложений: палитра светлой и тёмной темы, типографика, Markdown, роли заливки.
|
|
4
33
|
|
|
5
34
|
## Установка
|
|
6
35
|
|
|
@@ -11,7 +40,7 @@ npm install poe-svelte-ui-lib
|
|
|
11
40
|
### Требования (peerDependencies)
|
|
12
41
|
|
|
13
42
|
- `svelte ^5.0.0`
|
|
14
|
-
- `tailwindcss ^4.0.0` — компоненты стилизуются
|
|
43
|
+
- `tailwindcss ^4.0.0` — компоненты стилизуются Tailwind-утилитами и CSS-переменными темы; сама тема поставляется файлом `theme.css`
|
|
15
44
|
|
|
16
45
|
### Подключение стилей и темы
|
|
17
46
|
|
|
@@ -26,7 +55,7 @@ npm install poe-svelte-ui-lib
|
|
|
26
55
|
/* дальше — только собственные отличия приложения, например :root { min-width: 640px; } */
|
|
27
56
|
```
|
|
28
57
|
|
|
29
|
-
**Предпросмотр темы** —
|
|
58
|
+
**Предпросмотр темы** — вкладка «Тема» на главной витрины (`src/routes/ThemePreview.svelte`): типовой интерфейс облака (шапка устройства, палитра токенов, кнопки во всех ролях, поля, таблица, виджеты, типографика, Markdown); тема переключается кнопкой в шапке витрины. Правьте `theme.css` и смотрите результат там.
|
|
30
59
|
|
|
31
60
|
Текст в Markdown, таблицы и код переносятся по ширине контейнера — горизонтальной прокрутки быть не должно, только вертикальная. Markdown рендерится с `breaks: true` (как в облаке), поэтому абзац и пункт списка пишутся одной строкой — ручной перенос внутри абзаца превращается в разрыв строки.
|
|
32
61
|
|
|
@@ -48,7 +77,7 @@ npm install poe-svelte-ui-lib
|
|
|
48
77
|
|
|
49
78
|
Необязательный токен `--table-header-color` — фон шапки `Table` (по умолчанию `--container-color`); смарт-виджеты задают его сами из мастер-цвета.
|
|
50
79
|
|
|
51
|
-
Справочник по регулярным выражениям
|
|
80
|
+
Справочник по регулярным выражениям для проверки полей ввода — вкладка «Регулярные выражения» на главной витрины (`src/routes/RegExp.md`). Протокол обмена с устройством описан в документации DevCloud (разделы «Протокол устройства» и «Обмен данными с устройством»).
|
|
52
81
|
|
|
53
82
|
## Использование
|
|
54
83
|
|
|
@@ -63,7 +92,7 @@ npm install poe-svelte-ui-lib
|
|
|
63
92
|
<UI.Button content={{ name: "Сохранить" }} onClick={() => console.log(value)} />
|
|
64
93
|
```
|
|
65
94
|
|
|
66
|
-
Полный список компонентов и типов — в `src/lib/index.ts`. У каждого компонента есть README рядом с исходником (`src/lib/<Component>/README.md`, для `Modal`/`Dragging` — `src/lib/<Component>.README.md`) с описанием пропсов, примерами и внутренним устройством; живая витрина
|
|
95
|
+
Полный список компонентов и типов — в `src/lib/index.ts`. У каждого компонента есть README рядом с исходником (`src/lib/<Component>/README.md`, для `Modal`/`Dragging` — `src/lib/<Component>.README.md`) с описанием пропсов, примерами и внутренним устройством; живая витрина — `npm run dev`: на главной вкладки «Обзор» (этот README), «Тема» (предпросмотр `theme.css`) и «Регулярные выражения», дальше страница на каждый компонент.
|
|
67
96
|
|
|
68
97
|
**Витрина повторяет конструктор DevCloud:** слева компонент и код (прижат к низу), справа панель свойств шириной `24rem` с тем же оформлением, что в облаке, — переполнение подписей видно сразу. Кнопка (i) в шапке панели открывает описание компонента.
|
|
69
98
|
|
|
@@ -81,6 +110,11 @@ npm install poe-svelte-ui-lib
|
|
|
81
110
|
|
|
82
111
|
**README компонента** (`src/lib/<Компонент>/README.md`, вкладка «Инфо» витрины) — развёрнутая документация для разработчика. Первые разделы повторяют описание (i) и не должны ему противоречить, дальше идут разработческие: `## Назначение` (плюс заметка о размерах) → `## Пакет данных` (плюс `### Протокол устройства` / пример кода прошивки) → `## Ключи` (таблица «Ключ / Описание», те же формулировки, что в (i)) → `## Особенности` (поведение, ограничения) → `## Пропсы` (таблица «Название / Тип / По умолчанию / Описание», колбэки — `### Колбэки`) → `## Примеры` (Svelte) → `## Конструктор свойств (<Компонент>Props.svelte)` → `## Внутреннее устройство`.
|
|
83
112
|
|
|
113
|
+
**Языки (ru/en/zh) — два слоя:**
|
|
114
|
+
|
|
115
|
+
- **Встроенные тексты компонентов** (подписи полей виджетов, кнопки, подсказки, `aria-label`) — только через словарь `src/lib/locales/translations.ts` и `$T("ключ")`; строка с числами — параметрами: `$T("widget.fs.files", { n: files.length })` на ключ `"Файлов: {n}"`. Русских литералов в разметке и скриптах компонентов быть не должно. Списки вариантов с подписями — `$derived`, иначе не переключатся при смене языка. Исключение — шкалы прибора `WidgetPFD`: там общепринятые авиационные сокращения (`SPD`, `ALT`, `AS`, `VS`, `NO DATA`) на всех языках, переводятся только подсказки и строки состояния.
|
|
116
|
+
- **Тексты, введённые в конструкторе** (`label.name`, `placeholder`, `options[].name`, вкладки, заголовки колонок, подписи переключателя и т.п.) — тип `LocalizedText` (`locales/localize.ts`): простая строка (только `ru`) или объект `{ ru, en, zh }`; при отсутствии перевода показывается `ru`, затем первый непустой. Компоненты получают уже готовые строки: приложение разрешает описание GUI функцией `localizeDeep(UIBlocks, язык)` перед рендером. Окна свойств (`*Props.svelte`) тексты не показывают и не правят — у каждого компонента одна кнопка «Тексты и переводы» (`TextsEditor`): модальное окно само собирает все тексты компонента (`collectTexts` по полям `name`, `label`, `labels`, `placeholder`, `text`, `info`, `title`, `captionLeft`/`captionRight`, `units`/`unit`, `footer`, `hiddenInfo`, `alt`; технические `id`, `class`, `icon`, `keys`, данные — никогда; у `Switch` варианты — короткие метки битов битового режима, их имя правится в окне свойств и не переводится — `NOT_TEXTS` в `TextsEditor.svelte`) и даёт по три поля RU/EN/ZH. Сохранение (`packLocalized`): пусто — `""`, только RU — строка, иначе объект из заполненных языков. Читая текст в окне свойств для логики, берите `localizeText(поле, "ru")` — поле может быть объектом. Заголовок смарт-виджета по умолчанию пустой — виджет показывает своё название из словаря на текущем языке.
|
|
117
|
+
|
|
84
118
|
**Иконки вариантов панелей свойств** — `src/lib/libIcons/propsIcons.ts` (контурные SVG 24×24 строками), подставляются в `IOption.icon` в `options.ts`.
|
|
85
119
|
|
|
86
120
|
## Разработка
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
<!-- $lib/Accordion/Accordion.svelte — заголовок
|
|
2
|
-
|
|
1
|
+
<!-- $lib/Accordion/Accordion.svelte — заголовок не увеличивается при раскрытии, радиус 14.
|
|
2
|
+
Мастер-цвет (componentClass) — как у смарт-виджетов: иконка и текст заголовка цветом, фон заголовка — его мягким оттенком. -->
|
|
3
3
|
<script lang="ts">
|
|
4
4
|
import { slide } from "svelte/transition"
|
|
5
5
|
import type { IAccordionProps } from "../types"
|
|
6
6
|
import { twMerge } from "tailwind-merge"
|
|
7
|
+
import { widgetAccentStyle } from "../widgetAccent"
|
|
7
8
|
|
|
8
9
|
let {
|
|
9
10
|
id = crypto.randomUUID(),
|
|
10
|
-
isOpen = false,
|
|
11
|
+
isOpen = $bindable(false),
|
|
11
12
|
wrapperClass = "",
|
|
13
|
+
componentClass = "",
|
|
12
14
|
size = { width: 1, height: 1 },
|
|
13
15
|
label = { name: "", class: "text-left", icon: null },
|
|
14
16
|
children,
|
|
@@ -18,18 +20,24 @@
|
|
|
18
20
|
const toggle = () => (isOpen = !isOpen)
|
|
19
21
|
</script>
|
|
20
22
|
|
|
21
|
-
<div
|
|
23
|
+
<div
|
|
24
|
+
id={`${id}-${crypto.randomUUID().slice(0, 6)}`}
|
|
25
|
+
class={twMerge(`w-full`, wrapperClass)}
|
|
26
|
+
style={widgetAccentStyle(componentClass)}
|
|
27
|
+
transition:slide={{ duration: 200 }}
|
|
28
|
+
>
|
|
22
29
|
<div class="overflow-hidden rounded-[14px] border border-(--hairline-color) bg-(--back-color)">
|
|
23
|
-
<!--
|
|
24
|
-
|
|
25
|
-
светлом фоне (--back-color) — там часто лежат вложенные карточки/поля. -->
|
|
30
|
+
<!-- Фон заголовка — мягкий оттенок мастер-цвета (светлый в светлой теме, тёмный в тёмной), текст — мастер-цвет с цветом шрифта 60/40,
|
|
31
|
+
раскрытое содержимое остаётся на чистом фоне (--back-color). -->
|
|
26
32
|
<button
|
|
27
|
-
class="flex w-full cursor-pointer items-center justify-between gap-3 bg-(--
|
|
33
|
+
class="flex w-full cursor-pointer items-center justify-between gap-3 bg-(--accent-soft) px-4 py-3 text-left text-[color-mix(in_srgb,var(--accent-color)_60%,var(--font-color))] transition-colors duration-150 hover:bg-[color-mix(in_srgb,var(--accent-color)_12%,var(--accent-soft))]"
|
|
28
34
|
onclick={toggle}
|
|
29
35
|
>
|
|
30
36
|
<div class="flex min-w-0 flex-1 items-center gap-3">
|
|
31
37
|
{#if label?.icon}
|
|
32
|
-
<span
|
|
38
|
+
<span
|
|
39
|
+
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"
|
|
40
|
+
>
|
|
33
41
|
{#if typeof label?.icon === "string"}
|
|
34
42
|
{@html label.icon}
|
|
35
43
|
{:else}
|
|
@@ -43,7 +51,7 @@
|
|
|
43
51
|
|
|
44
52
|
<svg
|
|
45
53
|
xmlns="http://www.w3.org/2000/svg"
|
|
46
|
-
class="h-[14px] w-[14px] shrink-0 text-(--
|
|
54
|
+
class="h-[14px] w-[14px] shrink-0 text-[color-mix(in_srgb,var(--accent-color)_60%,var(--font-color))] transition-transform duration-200"
|
|
47
55
|
style="transform: rotate({isOpen ? 180 : 0}deg)"
|
|
48
56
|
viewBox="0 0 24 24"
|
|
49
57
|
>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { IAccordionProps } from "../types";
|
|
2
|
-
declare const Accordion: import("svelte").Component<IAccordionProps, {}, "">;
|
|
2
|
+
declare const Accordion: import("svelte").Component<IAccordionProps, {}, "isOpen">;
|
|
3
3
|
type Accordion = ReturnType<typeof Accordion>;
|
|
4
4
|
export default Accordion;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import CrossIcon from "../libIcons/CrossIcon.svelte"
|
|
10
10
|
import CommonSnippets from "../CommonSnippets.svelte"
|
|
11
11
|
import PropsGroup from "../PropsGroup.svelte"
|
|
12
|
+
import WidgetAccentPicker from "../WidgetAccentPicker.svelte"
|
|
12
13
|
|
|
13
14
|
const {
|
|
14
15
|
component,
|
|
@@ -88,7 +89,7 @@
|
|
|
88
89
|
label={{ name: $T("constructor.props.open") }}
|
|
89
90
|
value={component.properties.isOpen}
|
|
90
91
|
options={[{ id: crypto.randomUUID(), value: 0, class: "" }]}
|
|
91
|
-
onChange={(value) => updateProperty("isOpen", value, component, onPropertyChange)}
|
|
92
|
+
onChange={(value) => updateProperty("isOpen", value === 1, component, onPropertyChange)}
|
|
92
93
|
/>
|
|
93
94
|
{/snippet}
|
|
94
95
|
|
|
@@ -113,6 +114,10 @@
|
|
|
113
114
|
/>
|
|
114
115
|
</PropsGroup>
|
|
115
116
|
<PropsGroup label={$T("constructor.props.group.appearance")} contentClass="items-center">
|
|
117
|
+
<WidgetAccentPicker
|
|
118
|
+
value={component.properties.componentClass ?? ""}
|
|
119
|
+
onUpdate={(value) => updateProperty("componentClass", value, component, onPropertyChange)}
|
|
120
|
+
/>
|
|
116
121
|
{@render AccordionBackgroundImage()}
|
|
117
122
|
</PropsGroup>
|
|
118
123
|
</div>
|
|
@@ -141,6 +146,10 @@
|
|
|
141
146
|
{@render AccordionIsOpen()}
|
|
142
147
|
</PropsGroup>
|
|
143
148
|
<PropsGroup label={$T("constructor.props.group.appearance")} contentClass="items-center">
|
|
149
|
+
<WidgetAccentPicker
|
|
150
|
+
value={component.properties.componentClass ?? ""}
|
|
151
|
+
onUpdate={(value) => updateProperty("componentClass", value, component, onPropertyChange)}
|
|
152
|
+
/>
|
|
144
153
|
{@render AccordionBackgroundImage()}
|
|
145
154
|
</PropsGroup>
|
|
146
155
|
</div>
|
package/dist/Accordion/README.md
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
- Перетащите компоненты внутрь — у аккордеона своя сетка, её размер задаётся при создании и ресайзе
|
|
14
14
|
- Заголовок с иконкой открывает и закрывает содержимое
|
|
15
|
+
- Мастер-цвет — как у смарт-виджетов (`widgetAccentStyle`): иконка, текст и стрелка заголовка — цветом из палитры, смешанным с цветом шрифта (читается в обеих темах), фон заголовка — его мягким оттенком (`--<цвет>-soft`: очень светлый в светлой теме, тёмный в тёмной); без выбора — акцент темы. Цвет из `label.class` перекрывает цвет текста
|
|
15
16
|
- По высоте занимает одну строку сетки и растягивается по содержимому
|
|
16
17
|
|
|
17
18
|
### Адаптивность
|
|
@@ -30,20 +31,21 @@
|
|
|
30
31
|
|
|
31
32
|
## Пропсы
|
|
32
33
|
|
|
33
|
-
| Название
|
|
34
|
-
|
|
|
35
|
-
| `id`
|
|
36
|
-
| `isOpen`
|
|
37
|
-
| `wrapperClass`
|
|
38
|
-
| `
|
|
39
|
-
| `size
|
|
40
|
-
| `size.
|
|
41
|
-
| `
|
|
42
|
-
| `label
|
|
43
|
-
| `label.
|
|
44
|
-
| `label.
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
34
|
+
| Название | Тип | По умолчанию | Описание |
|
|
35
|
+
| ---------------- | ------------------------------------------------------ | --------------------- | ---------------------------------------------------------------------------------------- |
|
|
36
|
+
| `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
|
|
37
|
+
| `isOpen` | `boolean` | `false` | Состояние открытия/закрытия; `$bindable` — `bind:isOpen` получает раскрытие по клику |
|
|
38
|
+
| `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для обертки компонента |
|
|
39
|
+
| `componentClass` | `string` | `""` | Мастер-цвет: роль `bg-red`, `bg-green`... из палитры `COLOR_OPTIONS`; `""` — акцент темы |
|
|
40
|
+
| `size` | `object` | `-` | Размеры сетки содержимого (количество колонок и строк) |
|
|
41
|
+
| `size.height` | `number` | `1` | Количество строк в сетке содержимого |
|
|
42
|
+
| `size.width` | `number` | `1` | Количество колонок в сетке содержимого |
|
|
43
|
+
| `label` | `object` | `-` | Настройки заголовка аккордеона |
|
|
44
|
+
| `label.name` | `string` | `""` | Текст заголовка аккордеона |
|
|
45
|
+
| `label.class` | `string` | `"text-left"` | CSS-классы для стилизации текста заголовка |
|
|
46
|
+
| `label.icon` | `ConstructorOfATypedSvelteComponent \| string \| null` | `null` | Иконка, отображаемая в заголовке (SVG строка или компонент) |
|
|
47
|
+
| `children` | `Snippet` | `undefined` | Слот для содержимого, которое будет отображаться при открытии |
|
|
48
|
+
| `image` | `string` | `""` | URL изображения для фона содержимого аккордеона |
|
|
47
49
|
|
|
48
50
|
### Колбэки
|
|
49
51
|
|
|
@@ -121,7 +123,7 @@
|
|
|
121
123
|
|
|
122
124
|
### Что настраивается
|
|
123
125
|
|
|
124
|
-
- Заголовок, иконка, «Открыт по умолчанию» — состояние при загрузке интерфейса
|
|
126
|
+
- Заголовок, иконка, мастер-цвет заголовка, «Открыт по умолчанию» — состояние при загрузке интерфейса
|
|
125
127
|
- Картинка фона — изображение под содержимым (например схема устройства), поверх неё располагаются компоненты
|
|
126
128
|
|
|
127
129
|
## Внутреннее устройство
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<!-- $lib/ElementsUI/ButtonProps.svelte -->
|
|
2
2
|
<script lang="ts">
|
|
3
|
+
import TextsEditor from "../TextsEditor.svelte"
|
|
4
|
+
import { localizeText } from "../locales/localize"
|
|
3
5
|
import { T } from "../locales/i18n"
|
|
4
6
|
import { type UIComponent, type IButtonProps, type IOption, updateProperty, type IUIComponentHandler } from "../types"
|
|
5
7
|
import * as UI from ".."
|
|
@@ -40,11 +42,11 @@
|
|
|
40
42
|
)
|
|
41
43
|
const updateIconProperty = (propertyName: string, value: string) => {
|
|
42
44
|
updateProperty(propertyName, value, component, onPropertyChange)
|
|
43
|
-
if (component.properties.content.icon && !component.properties.content.name
|
|
45
|
+
if (component.properties.content.icon && !localizeText(component.properties.content.name, "ru").trim())
|
|
44
46
|
updateProperty("componentClass", twMerge((component.properties as UI.IButtonProps).componentClass, "w-fit p-2"), component, onPropertyChange)
|
|
45
47
|
else if (
|
|
46
|
-
(component.properties.content.icon && component.properties.content.name
|
|
47
|
-
(!component.properties.content.icon && !component.properties.content.name
|
|
48
|
+
(component.properties.content.icon && localizeText(component.properties.content.name, "ru").trim()) ||
|
|
49
|
+
(!component.properties.content.icon && !localizeText(component.properties.content.name, "ru").trim())
|
|
48
50
|
)
|
|
49
51
|
updateProperty("componentClass", twMerge((component.properties as UI.IButtonProps).componentClass, "w-full px-1 py-1"), component, onPropertyChange)
|
|
50
52
|
}
|
|
@@ -100,15 +102,11 @@
|
|
|
100
102
|
{/snippet}
|
|
101
103
|
|
|
102
104
|
{#snippet ButtonName()}
|
|
103
|
-
<
|
|
104
|
-
label={{ name: $T("constructor.props.name") }}
|
|
105
|
-
value={component.properties.content.name}
|
|
106
|
-
onUpdate={(value) => updateIconProperty("content.name", value as string)}
|
|
107
|
-
/>
|
|
105
|
+
<TextsEditor {component} {onPropertyChange} />
|
|
108
106
|
{/snippet}
|
|
109
107
|
|
|
110
108
|
{#snippet ButtonHeight()}
|
|
111
|
-
{#if !(!component.properties.content.name
|
|
109
|
+
{#if !(!localizeText(component.properties.content.name, "ru").trim() && component.properties.content.icon)}
|
|
112
110
|
<UI.Select
|
|
113
111
|
label={{ name: $T("constructor.props.height") }}
|
|
114
112
|
type="buttons"
|
|
@@ -120,14 +118,6 @@
|
|
|
120
118
|
{/if}
|
|
121
119
|
{/snippet}
|
|
122
120
|
|
|
123
|
-
{#snippet ButtonInfo()}
|
|
124
|
-
<UI.Input
|
|
125
|
-
label={{ name: $T("constructor.props.info") }}
|
|
126
|
-
value={component.properties.content.info.text}
|
|
127
|
-
onUpdate={(value) => updateProperty("content.info.text", value as string, component, onPropertyChange)}
|
|
128
|
-
/>
|
|
129
|
-
{/snippet}
|
|
130
|
-
|
|
131
121
|
{#snippet ButtonInfoSide()}
|
|
132
122
|
<UI.Select
|
|
133
123
|
label={{ name: $T("constructor.props.info.side") }}
|
|
@@ -197,7 +187,6 @@
|
|
|
197
187
|
</PropsGroup>
|
|
198
188
|
<PropsGroup label={$T("constructor.props.group.content")}>
|
|
199
189
|
{@render ButtonName()}
|
|
200
|
-
{@render ButtonInfo()}
|
|
201
190
|
{@render ButtonInfoSide()}
|
|
202
191
|
</PropsGroup>
|
|
203
192
|
<PropsGroup label={$T("constructor.props.group.appearance")}>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { optionsStore } from "./options"
|
|
6
6
|
import { updateProperty } from "./types"
|
|
7
7
|
import { twMerge } from "tailwind-merge"
|
|
8
|
+
import TextsEditor from "./TextsEditor.svelte"
|
|
8
9
|
|
|
9
10
|
let {
|
|
10
11
|
snippet = "",
|
|
@@ -87,21 +88,7 @@
|
|
|
87
88
|
{/snippet}
|
|
88
89
|
|
|
89
90
|
{#snippet Label()}
|
|
90
|
-
<
|
|
91
|
-
label={{ name: $T("constructor.props.label") }}
|
|
92
|
-
value={(
|
|
93
|
-
component.properties as
|
|
94
|
-
| UI.IAccordionProps
|
|
95
|
-
| UI.IInputProps
|
|
96
|
-
| UI.ISwitchProps
|
|
97
|
-
| UI.ISliderProps
|
|
98
|
-
| UI.IGraphProps
|
|
99
|
-
| UI.ITableProps<object>
|
|
100
|
-
| UI.IFileAttachProps
|
|
101
|
-
| UI.IJoystickProps
|
|
102
|
-
).label?.name}
|
|
103
|
-
onUpdate={(value) => updateProperty("label.name", value as string, component, onPropertyChange)}
|
|
104
|
-
/>
|
|
91
|
+
<TextsEditor {component} {onPropertyChange} />
|
|
105
92
|
{/snippet}
|
|
106
93
|
|
|
107
94
|
{#snippet LabelAlign(initialAlign: UI.IOption<string>)}
|
|
@@ -172,7 +159,7 @@
|
|
|
172
159
|
label={{ name: $T("constructor.props.disabled") }}
|
|
173
160
|
value={(component.properties as UI.IInputProps | UI.ISwitchProps | UI.ISliderProps | UI.IFileAttachProps)?.disabled ? 1 : 0}
|
|
174
161
|
options={[{ id: crypto.randomUUID(), value: 0, class: "" }]}
|
|
175
|
-
onChange={(value) => updateProperty("disabled", value, component, onPropertyChange)}
|
|
162
|
+
onChange={(value) => updateProperty("disabled", value === 1, component, onPropertyChange)}
|
|
176
163
|
/>
|
|
177
164
|
{/snippet}
|
|
178
165
|
|
|
@@ -194,7 +181,7 @@
|
|
|
194
181
|
label={{ name: $T("constructor.props.readonly") }}
|
|
195
182
|
value={Number((component.properties as UI.IInputProps | UI.IJoystickProps | UI.IWidgetProps).readonly)}
|
|
196
183
|
options={[{ id: crypto.randomUUID(), value: 0, class: "" }]}
|
|
197
|
-
onChange={(value) => updateProperty("readonly", value, component, onPropertyChange)}
|
|
184
|
+
onChange={(value) => updateProperty("readonly", value === 1, component, onPropertyChange)}
|
|
198
185
|
/>
|
|
199
186
|
{/snippet}
|
|
200
187
|
|
|
@@ -203,7 +190,7 @@
|
|
|
203
190
|
<UI.TextField
|
|
204
191
|
wrapperClass="mt-5 shadow-(--border-shadow-color) bg-(--blue-color)/20"
|
|
205
192
|
content={{
|
|
206
|
-
name: "
|
|
193
|
+
name: $T("ui.bit_range"),
|
|
207
194
|
}}
|
|
208
195
|
background
|
|
209
196
|
/>
|
package/dist/CopiedChip.svelte
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
ComponentExample — опечатка в классе позиционирования (right-1..5,
|
|
6
6
|
невалидный Tailwind-класс, физически ничего не позиционировал). -->
|
|
7
7
|
<script lang="ts">
|
|
8
|
+
import { T } from "./locales/i18n"
|
|
8
9
|
import { fade } from "svelte/transition"
|
|
9
10
|
|
|
10
11
|
let { show = false, class: className = "" }: { show?: boolean; class?: string } = $props()
|
|
@@ -15,6 +16,6 @@
|
|
|
15
16
|
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-color)"
|
|
16
17
|
transition:fade={{ duration: 150 }}
|
|
17
18
|
>
|
|
18
|
-
|
|
19
|
+
{$T("ui.copied")}
|
|
19
20
|
</span>
|
|
20
21
|
{/if}
|
package/dist/Graph/Graph.svelte
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
</script>
|
|
16
16
|
|
|
17
17
|
<script lang="ts">
|
|
18
|
+
import { T } from "../locales/i18n"
|
|
18
19
|
import { onMount } from "svelte"
|
|
19
20
|
import Select from "../Select/Select.svelte"
|
|
20
21
|
import type { IGraphDataObject, IGraphProps } from "../types"
|
|
@@ -416,27 +417,23 @@
|
|
|
416
417
|
{#if !isHistoryMode}
|
|
417
418
|
<!-- Развертка по горизонтали -->
|
|
418
419
|
<Select
|
|
419
|
-
label={{ name: "
|
|
420
|
+
label={{ name: $T("ui.graph.refresh"), class: "" }}
|
|
420
421
|
options={REFRESH_OPTIONS}
|
|
421
422
|
value={REFRESH_OPTIONS.find((o) => o.value == selectedRefreshRate)}
|
|
422
423
|
onUpdate={(value) => (selectedRefreshRate = (value as IOption).value as number)}
|
|
423
424
|
/>
|
|
424
425
|
{/if}
|
|
425
426
|
|
|
426
|
-
<!-- Переменные и их значение -->
|
|
427
|
-
<div>
|
|
428
|
-
<h5 class="
|
|
429
|
-
<
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
</tr>
|
|
437
|
-
{/each}
|
|
438
|
-
</tbody>
|
|
439
|
-
</table>
|
|
427
|
+
<!-- Переменные и их значение: сетка, а не таблица — имя обрезается «…», значение не переносится -->
|
|
428
|
+
<div class="flex flex-col gap-1">
|
|
429
|
+
<h5 class="text-[12px] font-semibold text-(--muted-color)">{$T("ui.graph.values")}</h5>
|
|
430
|
+
<div class="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-x-2 gap-y-1 text-[13px]">
|
|
431
|
+
{#each graphData as data, i (data.id)}
|
|
432
|
+
<span class="size-2.5 rounded-full" style="background-color: {data.color}"></span>
|
|
433
|
+
<span class="truncate font-semibold" title={data.name}>{data.name}</span>
|
|
434
|
+
<span class="text-right whitespace-nowrap tabular-nums">{currentValues[i]?.toFixed(2)}</span>
|
|
435
|
+
{/each}
|
|
436
|
+
</div>
|
|
440
437
|
</div>
|
|
441
438
|
</div>
|
|
442
439
|
</div>
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
label={{ name: $T("constructor.props.istest") }}
|
|
36
36
|
value={component.properties.isTest}
|
|
37
37
|
options={[{ id: crypto.randomUUID(), value: 0, class: "" }]}
|
|
38
|
-
onChange={(value) => updateProperty("isTest", value, component, onPropertyChange)}
|
|
38
|
+
onChange={(value) => updateProperty("isTest", value === 1, component, onPropertyChange)}
|
|
39
39
|
/>
|
|
40
40
|
{/snippet}
|
|
41
41
|
|
package/dist/Graph/README.md
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
## Особенности
|
|
20
20
|
|
|
21
21
|
- Для накопленных по периодам значений (энергия за час/сутки) лучше «Диаграмма по периодам»
|
|
22
|
+
- Справа от графика — развёртка и текущие значения серий: каждая серия в одну строку (цвет, имя, значение), длинное имя обрезается «…» с полным именем во всплывающей подсказке
|
|
22
23
|
|
|
23
24
|
### Адаптивность
|
|
24
25
|
|
package/dist/Input/Input.svelte
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
кольцо фокуса вместо размытой тени. Вся логика (regExp, roundToClean,
|
|
3
3
|
handleInputChange, счётчик, копирование) не тронута. -->
|
|
4
4
|
<script lang="ts">
|
|
5
|
+
import { T } from "../locales/i18n"
|
|
5
6
|
import type { IInputProps } from "../types"
|
|
6
7
|
import { twMerge } from "tailwind-merge"
|
|
7
8
|
import InfoIcon from "../libIcons/InfoIcon.svelte"
|
|
@@ -142,7 +143,7 @@
|
|
|
142
143
|
type="button"
|
|
143
144
|
class="absolute right-1.5 flex size-7 cursor-pointer items-center justify-center rounded-lg border-none bg-transparent text-(--faint-color) transition-colors duration-150 hover:bg-(--container-color) hover:text-(--font-color)"
|
|
144
145
|
onclick={() => (showPassword = !showPassword)}
|
|
145
|
-
aria-label={showPassword ? "
|
|
146
|
+
aria-label={showPassword ? $T("ui.password.hide") : $T("ui.password.show")}
|
|
146
147
|
>
|
|
147
148
|
{#if showPassword}
|
|
148
149
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
|
|
@@ -176,7 +177,7 @@
|
|
|
176
177
|
isCopied = true
|
|
177
178
|
setTimeout(() => (isCopied = false), 1000)
|
|
178
179
|
}}
|
|
179
|
-
aria-label="
|
|
180
|
+
aria-label={$T("ui.copy_text")}
|
|
180
181
|
>
|
|
181
182
|
{#if !isCopied}
|
|
182
183
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
|
@@ -208,7 +209,7 @@
|
|
|
208
209
|
value = roundToClean(Number(value) + (number.step ?? 1))
|
|
209
210
|
onUpdate(value as number)
|
|
210
211
|
}}
|
|
211
|
-
aria-label="
|
|
212
|
+
aria-label={$T("ui.increase")}>▲</button
|
|
212
213
|
>
|
|
213
214
|
<button
|
|
214
215
|
class="flex h-1/2 w-full items-center justify-center text-[11px] leading-none text-(--muted-color) transition-colors duration-150 hover:bg-(--container-color) hover:text-(--font-color)"
|
|
@@ -223,7 +224,7 @@
|
|
|
223
224
|
value = roundToClean(Number(value) - (number.step ?? 1))
|
|
224
225
|
onUpdate(value as number)
|
|
225
226
|
}}
|
|
226
|
-
aria-label="
|
|
227
|
+
aria-label={$T("ui.decrease")}>▼</button
|
|
227
228
|
>
|
|
228
229
|
</div>
|
|
229
230
|
{/if}
|
|
@@ -236,7 +237,7 @@
|
|
|
236
237
|
: ''} {disabled ? 'opacity-50' : 'cursor-pointer hover:text-(--accent-color)'}"
|
|
237
238
|
onmouseenter={() => (showInfo = true)}
|
|
238
239
|
onmouseleave={() => (showInfo = false)}
|
|
239
|
-
aria-label={showInfo ? "
|
|
240
|
+
aria-label={showInfo ? $T("ui.info.hide") : $T("ui.info.show")}
|
|
240
241
|
>
|
|
241
242
|
<InfoIcon />
|
|
242
243
|
</button>
|
|
@@ -251,6 +252,6 @@
|
|
|
251
252
|
лишнего зазора, когда ошибки нет) и не абсолютным позиционированием (не наезжает на
|
|
252
253
|
соседний контент при плотной вертикальной раскладке) — просто раздвигает то, что ниже,
|
|
253
254
|
как везде в вебе. -->
|
|
254
|
-
<span class="block text-[12px] font-semibold text-(--red-color)"
|
|
255
|
+
<span class="block text-[12px] font-semibold text-(--red-color)">{$T("ui.invalid_format")}</span>
|
|
255
256
|
{/if}
|
|
256
257
|
</div>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import TextsEditor from "../TextsEditor.svelte"
|
|
2
3
|
import { getContext } from "svelte"
|
|
3
4
|
import { T } from "../locales/i18n"
|
|
5
|
+
import translations from "../locales/translations"
|
|
6
|
+
import { LANGUAGE_CODES } from "../locales/localize"
|
|
4
7
|
import type { IInputProps, UIComponent, IOption, IUIComponentHandler } from "../types"
|
|
5
8
|
import * as UI from ".."
|
|
6
9
|
import { optionsStore, findColorOption } from "../options"
|
|
@@ -33,6 +36,12 @@
|
|
|
33
36
|
)
|
|
34
37
|
|
|
35
38
|
/* Обновление свойства */
|
|
39
|
+
/* Подсказка битового режима «Максимальное число: N» сразу на всех языках — иначе записалась бы на языке интерфейса конструктора */
|
|
40
|
+
const maxNumInfo = (range: { start: number; end: number }) => {
|
|
41
|
+
const max = Math.pow(2, range.end - range.start + 1) - 1
|
|
42
|
+
return Object.fromEntries(LANGUAGE_CODES.map((lang, i) => [lang, `${translations["constructor.props.maxnum"][i]}: ${max}`]))
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
const updateProperty = (path: string, value: string | object | boolean | number | RegExp) => {
|
|
37
46
|
const newProperties = JSON.parse(JSON.stringify(component.properties))
|
|
38
47
|
if (path === "regExp") {
|
|
@@ -111,11 +120,7 @@
|
|
|
111
120
|
{/snippet}
|
|
112
121
|
|
|
113
122
|
{#snippet InputLabel()}
|
|
114
|
-
<
|
|
115
|
-
label={{ name: $T("constructor.props.label") }}
|
|
116
|
-
value={component.properties.label.name}
|
|
117
|
-
onUpdate={(value) => updateProperty("label.name", value as string)}
|
|
118
|
-
/>
|
|
123
|
+
<TextsEditor {component} {onPropertyChange} />
|
|
119
124
|
{/snippet}
|
|
120
125
|
|
|
121
126
|
{#snippet InputLabelAlign()}
|
|
@@ -141,7 +146,7 @@
|
|
|
141
146
|
updateProperty("number.minNum", 0)
|
|
142
147
|
updateProperty("number.maxNum", Math.pow(2, component.properties.range.end - component.properties.range.start + 1) - 1)
|
|
143
148
|
updateProperty("number.step", 1)
|
|
144
|
-
updateProperty("help.info",
|
|
149
|
+
updateProperty("help.info", maxNumInfo(component.properties.range))
|
|
145
150
|
} else {
|
|
146
151
|
updateProperty("help.info", "")
|
|
147
152
|
}
|
|
@@ -250,29 +255,13 @@
|
|
|
250
255
|
updateProperty("number.minNum", 0)
|
|
251
256
|
updateProperty("number.maxNum", Math.pow(2, component.properties.range.end - component.properties.range.start + 1) - 1)
|
|
252
257
|
updateProperty("number.step", 1)
|
|
253
|
-
updateProperty("help.info",
|
|
258
|
+
updateProperty("help.info", maxNumInfo({ start: value[0] as number, end: value[1] as number }))
|
|
254
259
|
}
|
|
255
260
|
}}
|
|
256
261
|
/>
|
|
257
262
|
{/if}
|
|
258
263
|
{/snippet}
|
|
259
264
|
|
|
260
|
-
{#snippet InputPlaceholder()}
|
|
261
|
-
<UI.Input
|
|
262
|
-
label={{ name: $T("constructor.props.placeholder") }}
|
|
263
|
-
value={component.properties.placeholder as string}
|
|
264
|
-
onUpdate={(value) => updateProperty("placeholder", value)}
|
|
265
|
-
/>
|
|
266
|
-
{/snippet}
|
|
267
|
-
|
|
268
|
-
{#snippet InputInfo()}
|
|
269
|
-
<UI.Input
|
|
270
|
-
label={{ name: $T("constructor.props.info") }}
|
|
271
|
-
value={component.properties.help.info as string}
|
|
272
|
-
onUpdate={(value) => updateProperty("help.info", value)}
|
|
273
|
-
/>
|
|
274
|
-
{/snippet}
|
|
275
|
-
|
|
276
265
|
{#snippet InputSettings()}
|
|
277
266
|
{#if component.properties.type !== "bitMode"}
|
|
278
267
|
<UI.Select
|
|
@@ -368,7 +357,7 @@
|
|
|
368
357
|
label={{ name: $T("constructor.props.disabled") }}
|
|
369
358
|
value={component.properties.disabled}
|
|
370
359
|
options={[{ id: crypto.randomUUID(), value: 0, class: "" }]}
|
|
371
|
-
onChange={(value) => updateProperty("disabled", value)}
|
|
360
|
+
onChange={(value) => updateProperty("disabled", value === 1)}
|
|
372
361
|
/>
|
|
373
362
|
{/snippet}
|
|
374
363
|
|
|
@@ -381,8 +370,6 @@
|
|
|
381
370
|
</PropsGroup>
|
|
382
371
|
<PropsGroup label={$T("constructor.props.group.value")}>
|
|
383
372
|
{@render InputValue()}
|
|
384
|
-
{@render InputPlaceholder()}
|
|
385
|
-
{@render InputInfo()}
|
|
386
373
|
{@render InputSettings()}
|
|
387
374
|
<CommonSnippets snippet="BitModeInfo" {component} {onPropertyChange} />
|
|
388
375
|
</PropsGroup>
|
|
@@ -410,8 +397,6 @@
|
|
|
410
397
|
<CommonSnippets snippet="BitModeInfo" {component} {onPropertyChange} />
|
|
411
398
|
</PropsGroup>
|
|
412
399
|
<PropsGroup label={$T("constructor.props.group.behavior")}>
|
|
413
|
-
{@render InputPlaceholder()}
|
|
414
|
-
{@render InputInfo()}
|
|
415
400
|
{@render InputAutocomplete()}
|
|
416
401
|
{@render InputSettings()}
|
|
417
402
|
{@render InputDisabled()}
|