poe-svelte-ui-lib 1.43.0 → 1.44.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 +39 -5
- package/dist/Accordion/Accordion.svelte +18 -10
- package/dist/Accordion/Accordion.svelte.d.ts +1 -1
- package/dist/Accordion/AccordionProps.svelte +9 -0
- package/dist/Accordion/README.md +17 -15
- package/dist/Button/ButtonProps.svelte +7 -18
- package/dist/CommonSnippets.svelte +3 -16
- package/dist/CopiedChip.svelte +2 -1
- package/dist/Input/Input.svelte +7 -6
- package/dist/Input/InputProps.svelte +2 -25
- 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 +0 -10
- package/dist/Slider/Slider.svelte +5 -4
- package/dist/Switch/SwitchProps.svelte +1 -19
- package/dist/Table/Table.svelte +1 -1
- package/dist/Table/TableProps.svelte +1 -21
- 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/Widget/Widget.svelte +5 -4
- package/dist/Widget/WidgetProps.svelte +0 -37
- 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 +337 -6
- 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,
|
|
@@ -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>)}
|
|
@@ -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/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,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import TextsEditor from "../TextsEditor.svelte"
|
|
2
3
|
import { getContext } from "svelte"
|
|
3
4
|
import { T } from "../locales/i18n"
|
|
4
5
|
import type { IInputProps, UIComponent, IOption, IUIComponentHandler } from "../types"
|
|
@@ -111,11 +112,7 @@
|
|
|
111
112
|
{/snippet}
|
|
112
113
|
|
|
113
114
|
{#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
|
-
/>
|
|
115
|
+
<TextsEditor {component} {onPropertyChange} />
|
|
119
116
|
{/snippet}
|
|
120
117
|
|
|
121
118
|
{#snippet InputLabelAlign()}
|
|
@@ -257,22 +254,6 @@
|
|
|
257
254
|
{/if}
|
|
258
255
|
{/snippet}
|
|
259
256
|
|
|
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
257
|
{#snippet InputSettings()}
|
|
277
258
|
{#if component.properties.type !== "bitMode"}
|
|
278
259
|
<UI.Select
|
|
@@ -381,8 +362,6 @@
|
|
|
381
362
|
</PropsGroup>
|
|
382
363
|
<PropsGroup label={$T("constructor.props.group.value")}>
|
|
383
364
|
{@render InputValue()}
|
|
384
|
-
{@render InputPlaceholder()}
|
|
385
|
-
{@render InputInfo()}
|
|
386
365
|
{@render InputSettings()}
|
|
387
366
|
<CommonSnippets snippet="BitModeInfo" {component} {onPropertyChange} />
|
|
388
367
|
</PropsGroup>
|
|
@@ -410,8 +389,6 @@
|
|
|
410
389
|
<CommonSnippets snippet="BitModeInfo" {component} {onPropertyChange} />
|
|
411
390
|
</PropsGroup>
|
|
412
391
|
<PropsGroup label={$T("constructor.props.group.behavior")}>
|
|
413
|
-
{@render InputPlaceholder()}
|
|
414
|
-
{@render InputInfo()}
|
|
415
392
|
{@render InputAutocomplete()}
|
|
416
393
|
{@render InputSettings()}
|
|
417
394
|
{@render InputDisabled()}
|
|
@@ -39,21 +39,6 @@
|
|
|
39
39
|
{ name: $T("constructor.props.joystick.roll.axe"), info: $T("constructor.props.joystick.axes.info"), regExp: /^[\p{L}0-9\-_"':{}]*$/u },
|
|
40
40
|
]}
|
|
41
41
|
<div class="flex flex-col gap-1">
|
|
42
|
-
<UI.Input
|
|
43
|
-
label={{ name: axesOptions[index].name }}
|
|
44
|
-
value={axe.name}
|
|
45
|
-
help={{ info: axesOptions[index].info, regExp: axesOptions[index].regExp }}
|
|
46
|
-
maxlength={20}
|
|
47
|
-
onUpdate={(value) => {
|
|
48
|
-
updateProperty(
|
|
49
|
-
"axes",
|
|
50
|
-
component.properties.axes.map((a: any, i: number) => (i === index ? { ...a, name: value } : a)),
|
|
51
|
-
component,
|
|
52
|
-
onPropertyChange,
|
|
53
|
-
)
|
|
54
|
-
}}
|
|
55
|
-
/>
|
|
56
|
-
|
|
57
42
|
<CommonSnippets
|
|
58
43
|
snippet="MinMaxStep"
|
|
59
44
|
initialValue={{
|
package/dist/Modal.svelte
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<!-- $lib/Modal.svelte — радиус 20, шапка и подвал через линии,
|
|
2
2
|
подложка 40% + blur. Логика стека, Escape и клика вне окна не тронута. -->
|
|
3
3
|
<script lang="ts">
|
|
4
|
+
import { T } from "./locales/i18n"
|
|
4
5
|
import { type Snippet } from "svelte"
|
|
5
6
|
import { fade, scale } from "svelte/transition"
|
|
6
7
|
import { twMerge } from "tailwind-merge"
|
|
@@ -96,7 +97,7 @@
|
|
|
96
97
|
<button
|
|
97
98
|
class="flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-lg text-(--faint-color) transition-colors duration-150 hover:bg-(--container-color) hover:text-(--font-color) [&_svg]:size-4"
|
|
98
99
|
onclick={onCancel}
|
|
99
|
-
aria-label="
|
|
100
|
+
aria-label={$T("ui.close")}
|
|
100
101
|
>
|
|
101
102
|
<CrossIcon />
|
|
102
103
|
</button>
|
|
@@ -5,15 +5,17 @@
|
|
|
5
5
|
|
|
6
6
|
const randomLevelData = (n: number) => Array.from({ length: n }, () => Math.round(Math.random() * 100))
|
|
7
7
|
|
|
8
|
+
/* Демо-уровни, когда levels не заданы; name — ключ перевода */
|
|
8
9
|
export const DEFAULT_LEVELS: IPeriodChartLevel[] = [
|
|
9
|
-
{ name: "
|
|
10
|
-
{ name: "
|
|
11
|
-
{ name: "
|
|
12
|
-
{ name: "
|
|
10
|
+
{ name: "ui.period.year", data: randomLevelData(12) },
|
|
11
|
+
{ name: "ui.period.month", data: randomLevelData(31) },
|
|
12
|
+
{ name: "ui.period.day", data: randomLevelData(24) },
|
|
13
|
+
{ name: "ui.period.hour", data: randomLevelData(6) },
|
|
13
14
|
]
|
|
14
15
|
</script>
|
|
15
16
|
|
|
16
17
|
<script lang="ts">
|
|
18
|
+
import { T } from "../locales/i18n"
|
|
17
19
|
import { twMerge } from "tailwind-merge"
|
|
18
20
|
import type { IPeriodChartProps } from "../types"
|
|
19
21
|
|
|
@@ -23,7 +25,7 @@
|
|
|
23
25
|
вызовы, которым нужен цвет, по-прежнему могут передать bg-* явно. */
|
|
24
26
|
wrapperClass = "",
|
|
25
27
|
label = { name: "", class: "" },
|
|
26
|
-
levels
|
|
28
|
+
levels: levelsProp,
|
|
27
29
|
unit = "",
|
|
28
30
|
onLevelChange = () => {},
|
|
29
31
|
}: IPeriodChartProps = $props()
|
|
@@ -35,6 +37,7 @@
|
|
|
35
37
|
if (currentLevelIndex >= levels.length) currentLevelIndex = 0
|
|
36
38
|
})
|
|
37
39
|
|
|
40
|
+
const levels = $derived(levelsProp ?? DEFAULT_LEVELS.map((level) => ({ ...level, name: $T(level.name) })))
|
|
38
41
|
const currentLevel = $derived<IPeriodChartLevel | undefined>(levels[currentLevelIndex])
|
|
39
42
|
const values = $derived(currentLevel?.data ?? [])
|
|
40
43
|
|
|
@@ -103,7 +106,7 @@
|
|
|
103
106
|
<!-- Столбчатый график -->
|
|
104
107
|
<div class="relative flex w-full flex-col gap-1.5 rounded-[12px] border border-(--hairline-color) bg-(--back-color) p-3.5">
|
|
105
108
|
{#if values.length === 0}
|
|
106
|
-
<div class="flex h-40 w-full items-center justify-center text-[13px] text-(--faint-color)"
|
|
109
|
+
<div class="flex h-40 w-full items-center justify-center text-[13px] text-(--faint-color)">{$T("ui.no_data")}</div>
|
|
107
110
|
{:else}
|
|
108
111
|
<div class="flex w-full gap-2">
|
|
109
112
|
<!-- Шкала Y: подписи нулевой высоты, центр каждой ровно на своей линии сетки -->
|
|
@@ -49,14 +49,6 @@
|
|
|
49
49
|
}
|
|
50
50
|
</script>
|
|
51
51
|
|
|
52
|
-
{#snippet PeriodChartUnit()}
|
|
53
|
-
<UI.Input
|
|
54
|
-
label={{ name: $T("constructor.props.units") }}
|
|
55
|
-
value={component.properties.unit}
|
|
56
|
-
onUpdate={(value) => updateProperty("unit", value, component, onPropertyChange)}
|
|
57
|
-
/>
|
|
58
|
-
{/snippet}
|
|
59
|
-
|
|
60
52
|
{#snippet PeriodChartLevels()}
|
|
61
53
|
<PropsGroup label={$T("constructor.props.periodchart.title")} wrapperClass="mt-3">
|
|
62
54
|
{#snippet headerActions()}
|
|
@@ -85,16 +77,6 @@
|
|
|
85
77
|
onUpdate={(updatedArray) => updateLevels(updatedArray)}
|
|
86
78
|
/>
|
|
87
79
|
<div class="flex flex-wrap items-end gap-2">
|
|
88
|
-
<UI.Input
|
|
89
|
-
label={{ name: $T("constructor.props.optionname") }}
|
|
90
|
-
wrapperClass="min-w-28 flex-1"
|
|
91
|
-
value={level.name}
|
|
92
|
-
onUpdate={(value) => {
|
|
93
|
-
const levels = [...(component.properties?.levels || [])]
|
|
94
|
-
levels[index] = { ...levels[index], name: value as string }
|
|
95
|
-
updateLevels(levels)
|
|
96
|
-
}}
|
|
97
|
-
/>
|
|
98
80
|
<UI.Select
|
|
99
81
|
label={{ name: $T("constructor.props.variable") }}
|
|
100
82
|
wrapperClass="min-w-32 flex-1"
|
|
@@ -153,9 +135,6 @@
|
|
|
153
135
|
<CommonSnippets snippet="Colors" initialValue={{ color: initialColor, uselessColors: ["bg-max"] }} {component} {onPropertyChange} />
|
|
154
136
|
<CommonSnippets snippet="Label" {component} {onPropertyChange} />
|
|
155
137
|
</PropsGroup>
|
|
156
|
-
<PropsGroup label={$T("constructor.props.group.value")}>
|
|
157
|
-
{@render PeriodChartUnit()}
|
|
158
|
-
</PropsGroup>
|
|
159
138
|
</div>
|
|
160
139
|
{@render PeriodChartLevels()}
|
|
161
140
|
{:else}
|
|
@@ -168,7 +147,6 @@
|
|
|
168
147
|
<PropsGroup label={$T("constructor.props.group.value")}>
|
|
169
148
|
<CommonSnippets snippet="WrapperClass" {component} {onPropertyChange} />
|
|
170
149
|
<CommonSnippets snippet="Colors" initialValue={{ color: initialColor, uselessColors: ["bg-max"] }} {component} {onPropertyChange} />
|
|
171
|
-
{@render PeriodChartUnit()}
|
|
172
150
|
</PropsGroup>
|
|
173
151
|
</div>
|
|
174
152
|
{@render PeriodChartLevels()}
|