poe-svelte-ui-lib 1.35.1 → 1.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Divider/README.md +8 -8
- package/dist/Dragging.README.md +9 -9
- package/dist/Input/README.md +29 -29
- package/dist/Map/MapProps.svelte +1 -3
- package/dist/Modal.README.md +10 -10
- package/dist/Table/TableProps.svelte +211 -213
- package/dist/WidgetDeviceInfo/WidgetDeviceInfo.svelte +1 -4
- package/dist/WidgetDeviceInfo/WidgetDeviceInfoProps.svelte +1 -12
- package/dist/WidgetRGBStrip/LightIcon.svelte +12 -0
- package/dist/WidgetRGBStrip/LightIcon.svelte.d.ts +18 -0
- package/dist/WidgetRGBStrip/README.md +60 -0
- package/dist/WidgetRGBStrip/WidgetRGBStrip.svelte +185 -0
- package/dist/WidgetRGBStrip/WidgetRGBStrip.svelte.d.ts +4 -0
- package/dist/WidgetRGBStrip/WidgetRGBStripProps.svelte +33 -0
- package/dist/WidgetRGBStrip/WidgetRGBStripProps.svelte.d.ts +14 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/locales/translations.js +5 -0
- package/dist/types.d.ts +27 -4
- package/package.json +1 -1
package/dist/Divider/README.md
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
## Пропсы
|
|
8
8
|
|
|
9
|
-
| Название | Тип | По умолчанию | Описание
|
|
10
|
-
| -------------- | ------------------------------- | --------------------- |
|
|
11
|
-
| `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента
|
|
12
|
-
| `wrapperClass` | `string` | `""` | CSS-классы для обёртки компонента
|
|
13
|
-
| `type` | `"line" \| "empty"` | `"line"` | `line` — видимая полоса, `empty` — чистый отступ без полосы
|
|
14
|
-
| `width` | `number` | `1` | Толщина полосы в пикселях (применяется только при `type="line"`)
|
|
15
|
-
| `color` | `string` | `""` | Класс цвета полосы из общей палитры (`bg-max`, `bg-red`, `bg-yellow`, `bg-green`, `bg-blue`, `bg-purple`, `bg-gray`); при пустом значении полоса красится в нейтральный `--hairline-color`, при заданном `bg-*` — в `--bg-color` (как в `Button.svelte`)
|
|
16
|
-
| `anchor` | `"top" \| "center" \| "bottom"` | `"center"` | Положение полосы внутри выделенной компоненту высоты сетки
|
|
9
|
+
| Название | Тип | По умолчанию | Описание |
|
|
10
|
+
| -------------- | ------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
|
|
12
|
+
| `wrapperClass` | `string` | `""` | CSS-классы для обёртки компонента |
|
|
13
|
+
| `type` | `"line" \| "empty"` | `"line"` | `line` — видимая полоса, `empty` — чистый отступ без полосы |
|
|
14
|
+
| `width` | `number` | `1` | Толщина полосы в пикселях (применяется только при `type="line"`) |
|
|
15
|
+
| `color` | `string` | `""` | Класс цвета полосы из общей палитры (`bg-max`, `bg-red`, `bg-yellow`, `bg-green`, `bg-blue`, `bg-purple`, `bg-gray`); при пустом значении полоса красится в нейтральный `--hairline-color`, при заданном `bg-*` — в `--bg-color` (как в `Button.svelte`) |
|
|
16
|
+
| `anchor` | `"top" \| "center" \| "bottom"` | `"center"` | Положение полосы внутри выделенной компоненту высоты сетки |
|
|
17
17
|
|
|
18
18
|
## События
|
|
19
19
|
|
package/dist/Dragging.README.md
CHANGED
|
@@ -8,15 +8,15 @@
|
|
|
8
8
|
|
|
9
9
|
## Пропсы
|
|
10
10
|
|
|
11
|
-
| Название | Тип
|
|
12
|
-
| ---------------- |
|
|
13
|
-
| `wrapperClass` | `string`
|
|
14
|
-
| `container` | `HTMLDivElement`
|
|
15
|
-
| `array` | `any[]`
|
|
16
|
-
| `elementsId` | `string`
|
|
17
|
-
| `elementIndex` | `number`
|
|
18
|
-
| `containerIndex` | `number`
|
|
19
|
-
| `onUpdate` | `(arr: any[], index?: number \| null) => void` | `undefined`
|
|
11
|
+
| Название | Тип | По умолчанию | Описание |
|
|
12
|
+
| ---------------- | ---------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `wrapperClass` | `string` | `undefined` | CSS-классы для обёртки ручки перетаскивания |
|
|
14
|
+
| `container` | `HTMLDivElement` | `-` (обязателен) | Контейнер, внутри которого ищутся элементы списка (по атрибуту `id`, см. ниже) для определения точки вставки |
|
|
15
|
+
| `array` | `any[]` | `-` (обязателен) | Массив элементов списка — мутируется на месте (`splice`) в процессе перетаскивания |
|
|
16
|
+
| `elementsId` | `string` | `"item"` | Префикс `id` DOM-элементов списка: ожидается `#{elementsId}-{index}` или `#{elementsId}-{index}-{containerIndex}` |
|
|
17
|
+
| `elementIndex` | `number` | `-` (обязателен) | Индекс элемента, которому принадлежит эта ручка, в `array` |
|
|
18
|
+
| `containerIndex` | `number` | `undefined` | Индекс контейнера — нужен, когда на странице несколько независимых списков и id элементов включают ещё и номер контейнера |
|
|
19
|
+
| `onUpdate` | `(arr: any[], index?: number \| null) => void` | `undefined` | Вызывается дважды за одно перетаскивание: сразу после извлечения элемента (`array` без него) и по завершении (`array` с элементом на новом месте, вторым аргументом — `containerIndex`) |
|
|
20
20
|
|
|
21
21
|
## События
|
|
22
22
|
|
package/dist/Input/README.md
CHANGED
|
@@ -6,35 +6,35 @@
|
|
|
6
6
|
|
|
7
7
|
## Пропсы
|
|
8
8
|
|
|
9
|
-
| Название | Тип | По умолчанию | Описание
|
|
10
|
-
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
|
|
11
|
-
| `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента
|
|
12
|
-
| `type` | `"text" \| "password" \| "number" \| "text-area" \| "bitMode"` | `"text"` | Тип поля ввода: `text` — обычный текст, `password` — скрытый ввод с кнопкой показа, `number` — числовое поле со стрелками, `text-area` — многострочное поле, `bitMode` — числовое поле, диапазон которого рассчитывается из `range`
|
|
13
|
-
| `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для внешней обёртки компонента
|
|
14
|
-
| `label` | `object` | `-` | Настройки подписи компонента
|
|
15
|
-
| `label.name` | `string` | `""` | Текст заголовка
|
|
16
|
-
| `label.class` | `string` | `""` | CSS-классы для стилизации заголовка
|
|
17
|
-
| `componentClass` | `string` | `""` | Дополнительные CSS-классы для самого элемента ввода (`input`/`textarea`)
|
|
18
|
-
| `disabled` | `boolean` | `false` | Отключает ввод: блокирует взаимодействие с элементом и визуально затемняет компонент
|
|
19
|
-
| `readonly` | `boolean` | `false` | Режим только для чтения: позволяет выделять и копировать текст, но запрещает редактирование
|
|
20
|
-
| `value` | `string \| number` | `undefined` | Текущее значение поля; поддерживает двустороннее связывание (`$bindable`)
|
|
21
|
-
| `range` | `object` | `undefined` | ⚠️ Читается только конструкторской панелью `InputProps.svelte` (для расчёта `number.maxNum`, `2^(end - start + 1) - 1`) — сам `Input.svelte` проп `range` не использует. На голом `<UI.Input type="bitMode">` диапазон задаётся напрямую через `number.maxNum`
|
|
22
|
-
| `range.start` | `number` | `undefined` | Начальный индекс бита (только для конструкторской панели)
|
|
23
|
-
| `range.end` | `number` | `undefined` | Конечный индекс бита (только для конструкторской панели)
|
|
24
|
-
| `maxlength` | `number` | `100` | Максимальное количество символов, разрешённых для ввода
|
|
25
|
-
| `textareaRows` | `number` | `3` | Количество видимых строк для типа `text-area`
|
|
26
|
-
| `placeholder` | `string` | `""` | Текст-подсказка, отображаемый в пустом поле ввода
|
|
27
|
-
| `isValid` | `boolean` | `true` | Состояние валидации: при `false` поле подсвечивается красным; поддерживает двустороннее связывание (`$bindable`)
|
|
28
|
-
| `number` | `object` | `-` | Настройки для числового ввода
|
|
29
|
-
| `number.minNum` | `number` | `-1000000` | Минимальное значение диапазона
|
|
30
|
-
| `number.maxNum` | `number` | `1000000` | Максимальное значение диапазона
|
|
31
|
-
| `number.step` | `number` | `1` | Шаг изменения значения стрелками
|
|
32
|
-
| `help` | `object` | `-` | Дополнительные настройки поля
|
|
33
|
-
| `help.info` | `string` | `""` | Текст всплывающей подсказки
|
|
34
|
-
| `help.copyButton` | `boolean` | `false` | Показать кнопку копирования значения
|
|
35
|
-
| `help.regExp` | `string \| RegExp` | `undefined` | Регулярное выражение для валидации ввода
|
|
36
|
-
| `help.autocomplete` | `"on" \| "off" \| "given-name" \| "family-name" \| "nickname" \| "username" \| "new-password" \| "current-password" \| "organization-title" \| "country-name" \| "address-level1" \| "address-level2" \| "street-address" \| "postal-code" \| "email" \| "tel" \| null` | `"off"` | Атрибут автозаполнения браузера
|
|
37
|
-
| `onUpdate` | `(value: string \| number) => void` | `undefined` | Callback-функция, вызываемая при изменении значения поля; передаёт новое значение
|
|
9
|
+
| Название | Тип | По умолчанию | Описание |
|
|
10
|
+
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
|
|
12
|
+
| `type` | `"text" \| "password" \| "number" \| "text-area" \| "bitMode"` | `"text"` | Тип поля ввода: `text` — обычный текст, `password` — скрытый ввод с кнопкой показа, `number` — числовое поле со стрелками, `text-area` — многострочное поле, `bitMode` — числовое поле, диапазон которого рассчитывается из `range` |
|
|
13
|
+
| `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для внешней обёртки компонента |
|
|
14
|
+
| `label` | `object` | `-` | Настройки подписи компонента |
|
|
15
|
+
| `label.name` | `string` | `""` | Текст заголовка |
|
|
16
|
+
| `label.class` | `string` | `""` | CSS-классы для стилизации заголовка |
|
|
17
|
+
| `componentClass` | `string` | `""` | Дополнительные CSS-классы для самого элемента ввода (`input`/`textarea`) |
|
|
18
|
+
| `disabled` | `boolean` | `false` | Отключает ввод: блокирует взаимодействие с элементом и визуально затемняет компонент |
|
|
19
|
+
| `readonly` | `boolean` | `false` | Режим только для чтения: позволяет выделять и копировать текст, но запрещает редактирование |
|
|
20
|
+
| `value` | `string \| number` | `undefined` | Текущее значение поля; поддерживает двустороннее связывание (`$bindable`) |
|
|
21
|
+
| `range` | `object` | `undefined` | ⚠️ Читается только конструкторской панелью `InputProps.svelte` (для расчёта `number.maxNum`, `2^(end - start + 1) - 1`) — сам `Input.svelte` проп `range` не использует. На голом `<UI.Input type="bitMode">` диапазон задаётся напрямую через `number.maxNum` |
|
|
22
|
+
| `range.start` | `number` | `undefined` | Начальный индекс бита (только для конструкторской панели) |
|
|
23
|
+
| `range.end` | `number` | `undefined` | Конечный индекс бита (только для конструкторской панели) |
|
|
24
|
+
| `maxlength` | `number` | `100` | Максимальное количество символов, разрешённых для ввода |
|
|
25
|
+
| `textareaRows` | `number` | `3` | Количество видимых строк для типа `text-area` |
|
|
26
|
+
| `placeholder` | `string` | `""` | Текст-подсказка, отображаемый в пустом поле ввода |
|
|
27
|
+
| `isValid` | `boolean` | `true` | Состояние валидации: при `false` поле подсвечивается красным; поддерживает двустороннее связывание (`$bindable`) |
|
|
28
|
+
| `number` | `object` | `-` | Настройки для числового ввода |
|
|
29
|
+
| `number.minNum` | `number` | `-1000000` | Минимальное значение диапазона |
|
|
30
|
+
| `number.maxNum` | `number` | `1000000` | Максимальное значение диапазона |
|
|
31
|
+
| `number.step` | `number` | `1` | Шаг изменения значения стрелками |
|
|
32
|
+
| `help` | `object` | `-` | Дополнительные настройки поля |
|
|
33
|
+
| `help.info` | `string` | `""` | Текст всплывающей подсказки |
|
|
34
|
+
| `help.copyButton` | `boolean` | `false` | Показать кнопку копирования значения |
|
|
35
|
+
| `help.regExp` | `string \| RegExp` | `undefined` | Регулярное выражение для валидации ввода |
|
|
36
|
+
| `help.autocomplete` | `"on" \| "off" \| "given-name" \| "family-name" \| "nickname" \| "username" \| "new-password" \| "current-password" \| "organization-title" \| "country-name" \| "address-level1" \| "address-level2" \| "street-address" \| "postal-code" \| "email" \| "tel" \| null` | `"off"` | Атрибут автозаполнения браузера |
|
|
37
|
+
| `onUpdate` | `(value: string \| number) => void` | `undefined` | Callback-функция, вызываемая при изменении значения поля; передаёт новое значение |
|
|
38
38
|
|
|
39
39
|
## События
|
|
40
40
|
|
package/dist/Map/MapProps.svelte
CHANGED
|
@@ -29,9 +29,7 @@
|
|
|
29
29
|
),
|
|
30
30
|
)
|
|
31
31
|
|
|
32
|
-
const initialColor = $derived(
|
|
33
|
-
findColorOption($optionsStore.COLOR_OPTIONS, component.properties.wrapperClass),
|
|
34
|
-
)
|
|
32
|
+
const initialColor = $derived(findColorOption($optionsStore.COLOR_OPTIONS, component.properties.wrapperClass))
|
|
35
33
|
</script>
|
|
36
34
|
|
|
37
35
|
{#if forConstructor}
|
package/dist/Modal.README.md
CHANGED
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
|
|
9
9
|
## Пропсы
|
|
10
10
|
|
|
11
|
-
| Название | Тип
|
|
12
|
-
| -------------- |
|
|
13
|
-
| `isOpen` | `boolean`
|
|
14
|
-
| `title` | `string`
|
|
15
|
-
| `wrapperClass` | `string`
|
|
16
|
-
| `mainClass` | `string`
|
|
17
|
-
| `width` | `string`
|
|
18
|
-
| `main` | `Snippet`
|
|
19
|
-
| `footer` | `Snippet`
|
|
20
|
-
| `onCancel` | `() => void` | `() => (isOpen = false)`
|
|
11
|
+
| Название | Тип | По умолчанию | Описание |
|
|
12
|
+
| -------------- | ------------ | ------------------------ | ---------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `isOpen` | `boolean` | `false` | Открыто ли модальное окно; поддерживает двустороннее связывание (`$bindable`) |
|
|
14
|
+
| `title` | `string` | `undefined` | Заголовок в шапке окна |
|
|
15
|
+
| `wrapperClass` | `string` | `""` | CSS-классы для окна (например для другой ширины/фона) |
|
|
16
|
+
| `mainClass` | `string` | `""` | CSS-классы для контейнера содержимого (область под `main`) |
|
|
17
|
+
| `width` | `string` | `""` | Явная ширина окна (CSS-значение, например `"600px"`); по умолчанию берётся из `wrapperClass`/`w-300` |
|
|
18
|
+
| `main` | `Snippet` | `-` (обязателен) | Содержимое окна |
|
|
19
|
+
| `footer` | `Snippet` | `undefined` | Содержимое подвала (обычно кнопки действий, выравниваются в ряд справа налево) |
|
|
20
|
+
| `onCancel` | `() => void` | `() => (isOpen = false)` | Вызывается при закрытии окна (крестик, клик вне окна, `Escape`) |
|
|
21
21
|
|
|
22
22
|
## События
|
|
23
23
|
|
|
@@ -169,9 +169,7 @@
|
|
|
169
169
|
}
|
|
170
170
|
}}
|
|
171
171
|
/>
|
|
172
|
-
<div
|
|
173
|
-
class="py-1.5 pl-9 grid grid-cols-[minmax(9rem,14rem)_minmax(5rem,7rem)_minmax(18rem,22rem)_2rem_2rem_1fr] items-end gap-1.5"
|
|
174
|
-
>
|
|
172
|
+
<div class="py-1.5 pl-9 grid grid-cols-[minmax(9rem,14rem)_minmax(5rem,7rem)_minmax(18rem,22rem)_2rem_2rem_1fr] items-end gap-1.5">
|
|
175
173
|
<UI.Input
|
|
176
174
|
label={{ name: $T("constructor.props.table.columns.label") }}
|
|
177
175
|
value={column.label?.name}
|
|
@@ -253,229 +251,229 @@
|
|
|
253
251
|
}}
|
|
254
252
|
/>
|
|
255
253
|
<div class="flex min-w-0 flex-col gap-2">
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}}
|
|
316
|
-
/>
|
|
317
|
-
</div>
|
|
318
|
-
{:else if content.type == "button"}
|
|
319
|
-
{@const button = content.data}
|
|
320
|
-
<!-- Строка 1 — как выглядит кнопка, строка 2 — какую команду она шлёт -->
|
|
321
|
-
<div class="flex flex-wrap items-end gap-2">
|
|
322
|
-
<UI.Input
|
|
323
|
-
wrapperClass="min-w-28 flex-1"
|
|
324
|
-
label={{ name: $T("constructor.props.name") }}
|
|
325
|
-
value={button.name}
|
|
326
|
-
onUpdate={(value) => updateContentProperty(columnIndex, index, "name", value)}
|
|
327
|
-
/>
|
|
328
|
-
<div class="relative min-w-32 flex-1">
|
|
329
|
-
<CommonSnippets
|
|
330
|
-
snippet="IconsLib"
|
|
331
|
-
initialValue={{
|
|
332
|
-
name: $T("constructor.props.table.type.icon"),
|
|
333
|
-
icon: column.content[index].data.icon,
|
|
334
|
-
updateProperty: (icon: string) => updateContentProperty(columnIndex, index, "icon", icon),
|
|
335
|
-
icons: { array: ICONS },
|
|
254
|
+
{#if content.type == "text"}
|
|
255
|
+
{@const text = content.data}
|
|
256
|
+
<div class="flex flex-wrap items-end gap-2">
|
|
257
|
+
<UI.Input
|
|
258
|
+
wrapperClass="min-w-32 flex-1"
|
|
259
|
+
label={{ name: $T("constructor.props.table.text.key") }}
|
|
260
|
+
value={text.key}
|
|
261
|
+
help={{ regExp: /^[0-9a-zA-Z_.-]{0,16}$/ }}
|
|
262
|
+
onUpdate={(value) => {
|
|
263
|
+
updateTableHeader(
|
|
264
|
+
columnIndex,
|
|
265
|
+
"content",
|
|
266
|
+
column.content.map((textItem: any, i: number) =>
|
|
267
|
+
i == index ? { ...textItem, data: { ...textItem.data, key: value } } : textItem,
|
|
268
|
+
),
|
|
269
|
+
)
|
|
270
|
+
}}
|
|
271
|
+
/>
|
|
272
|
+
<UI.Select
|
|
273
|
+
wrapperClass="min-w-60 flex-[2]"
|
|
274
|
+
label={{ name: $T("constructor.props.tablecolumn.settings") }}
|
|
275
|
+
type="buttons"
|
|
276
|
+
multiSelect={true}
|
|
277
|
+
value={$optionsStore.TABLE_TEXT_SETTING_OPTIONS.slice(0, forConstructor ? 3 : 5).filter((opt) => {
|
|
278
|
+
if (column.content[index].data[opt.value]) return opt
|
|
279
|
+
})}
|
|
280
|
+
options={$optionsStore.TABLE_TEXT_SETTING_OPTIONS.slice(0, forConstructor ? 3 : 5)}
|
|
281
|
+
onUpdate={(value) => changeColumnSettings(value, columnIndex, index)}
|
|
282
|
+
/>
|
|
283
|
+
</div>
|
|
284
|
+
{:else if content.type == "select"}
|
|
285
|
+
{@const select = content.data}
|
|
286
|
+
<div class="flex flex-wrap items-end gap-2">
|
|
287
|
+
<UI.Input
|
|
288
|
+
wrapperClass="min-w-32 flex-1"
|
|
289
|
+
label={{ name: $T("constructor.props.table.select.key") }}
|
|
290
|
+
value={select.key}
|
|
291
|
+
help={{ regExp: /^[0-9a-zA-Z_.-]{0,16}$/ }}
|
|
292
|
+
onUpdate={(value) => {
|
|
293
|
+
updateTableHeader(
|
|
294
|
+
columnIndex,
|
|
295
|
+
"content",
|
|
296
|
+
column.content.map((textItem: any, i: number) =>
|
|
297
|
+
i == index ? { ...textItem, data: { ...textItem.data, key: value } } : textItem,
|
|
298
|
+
),
|
|
299
|
+
)
|
|
300
|
+
// updateTableBody()
|
|
301
|
+
}}
|
|
302
|
+
/>
|
|
303
|
+
<UI.Input
|
|
304
|
+
wrapperClass="min-w-40 flex-1"
|
|
305
|
+
label={{ name: $T("constructor.props.table.select.keys") }}
|
|
306
|
+
value={select?.keyCol ?? ""}
|
|
307
|
+
maxlength={500}
|
|
308
|
+
help={{ info: $T("constructor.props.table.select.keys.info"), regExp: /^[a-zA-Z0-9\-_ ]{0,500}$/ }}
|
|
309
|
+
onUpdate={(value) => {
|
|
310
|
+
const headers = [...component.properties.header]
|
|
311
|
+
headers[columnIndex].content[index].data = { ...headers[columnIndex].content[index].data, keyCol: value }
|
|
312
|
+
updateProperty("header", headers, component, onPropertyChange)
|
|
336
313
|
}}
|
|
337
|
-
{component}
|
|
338
|
-
{onPropertyChange}
|
|
339
314
|
/>
|
|
340
315
|
</div>
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
316
|
+
{:else if content.type == "button"}
|
|
317
|
+
{@const button = content.data}
|
|
318
|
+
<!-- Строка 1 — как выглядит кнопка, строка 2 — какую команду она шлёт -->
|
|
319
|
+
<div class="flex flex-wrap items-end gap-2">
|
|
320
|
+
<UI.Input
|
|
321
|
+
wrapperClass="min-w-28 flex-1"
|
|
322
|
+
label={{ name: $T("constructor.props.name") }}
|
|
323
|
+
value={button.name}
|
|
324
|
+
onUpdate={(value) => updateContentProperty(columnIndex, index, "name", value)}
|
|
345
325
|
/>
|
|
326
|
+
<div class="relative min-w-32 flex-1">
|
|
327
|
+
<CommonSnippets
|
|
328
|
+
snippet="IconsLib"
|
|
329
|
+
initialValue={{
|
|
330
|
+
name: $T("constructor.props.table.type.icon"),
|
|
331
|
+
icon: column.content[index].data.icon,
|
|
332
|
+
updateProperty: (icon: string) => updateContentProperty(columnIndex, index, "icon", icon),
|
|
333
|
+
icons: { array: ICONS },
|
|
334
|
+
}}
|
|
335
|
+
{component}
|
|
336
|
+
{onPropertyChange}
|
|
337
|
+
/>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="flex min-w-72 flex-[2] items-end gap-1">
|
|
340
|
+
<UI.Button
|
|
341
|
+
wrapperClass="w-8 shrink-0"
|
|
342
|
+
content={{ icon: InfoIcon, info: { text: $T("constructor.props.button.colors.hint"), side: "right" } }}
|
|
343
|
+
/>
|
|
344
|
+
<UI.Select
|
|
345
|
+
wrapperClass="min-w-0 flex-1"
|
|
346
|
+
label={{ name: $T("constructor.props.colors") }}
|
|
347
|
+
type="buttons"
|
|
348
|
+
options={$optionsStore.COLOR_OPTIONS.filter((option) => option.value !== "bg-max")}
|
|
349
|
+
value={findColorOption($optionsStore.COLOR_OPTIONS, button.class ?? component.properties.wrapperClass)}
|
|
350
|
+
onUpdate={(option) => {
|
|
351
|
+
if (button.class === (option as UI.IOption).value) updateContentProperty(columnIndex, index, "class", "bg-transparent")
|
|
352
|
+
else updateContentProperty(columnIndex, index, "class", (option as UI.IOption).value)
|
|
353
|
+
}}
|
|
354
|
+
/>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
<div class="flex flex-wrap items-end gap-2">
|
|
346
358
|
<UI.Select
|
|
347
|
-
wrapperClass="min-w-
|
|
348
|
-
label={{ name: $T("constructor.props.
|
|
359
|
+
wrapperClass="min-w-56 flex-1"
|
|
360
|
+
label={{ name: $T("constructor.props.header") }}
|
|
349
361
|
type="buttons"
|
|
350
|
-
|
|
351
|
-
|
|
362
|
+
value={$optionsStore.HEADER_OPTIONS.find((h) => h.value === button.eventHandler?.Header)}
|
|
363
|
+
options={$optionsStore.HEADER_OPTIONS}
|
|
352
364
|
onUpdate={(option) => {
|
|
353
|
-
|
|
354
|
-
|
|
365
|
+
const handler = button.eventHandler
|
|
366
|
+
handler.Header = (option as UI.IOption).value as string
|
|
367
|
+
updateContentProperty(columnIndex, index, "eventHandler", handler)
|
|
368
|
+
}}
|
|
369
|
+
/>
|
|
370
|
+
<UI.Input
|
|
371
|
+
wrapperClass="min-w-28 flex-1"
|
|
372
|
+
label={{ name: $T("constructor.props.argument") }}
|
|
373
|
+
value={button.eventHandler?.Argument}
|
|
374
|
+
onUpdate={(value) => {
|
|
375
|
+
const handler = button.eventHandler
|
|
376
|
+
handler.Argument = value as string
|
|
377
|
+
updateContentProperty(columnIndex, index, "eventHandler", handler)
|
|
378
|
+
}}
|
|
379
|
+
/>
|
|
380
|
+
<UI.Input
|
|
381
|
+
wrapperClass="min-w-40 flex-[2]"
|
|
382
|
+
label={{ name: $T("constructor.props.table.keys") }}
|
|
383
|
+
value={button.eventHandler?.Variables.join(" ")}
|
|
384
|
+
maxlength={500}
|
|
385
|
+
help={{ info: $T("constructor.props.table.keys.info"), regExp: /^[a-zA-Z0-9\-._ ]{0,500}$/ }}
|
|
386
|
+
onUpdate={(value) => {
|
|
387
|
+
const handler = { ...button.eventHandler }
|
|
388
|
+
handler.Variables = (value as string).trim().split(/\s+/)
|
|
389
|
+
updateContentProperty(columnIndex, index, "eventHandler", handler)
|
|
355
390
|
}}
|
|
356
391
|
/>
|
|
357
392
|
</div>
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
<
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
value={button.eventHandler?.Argument}
|
|
376
|
-
onUpdate={(value) => {
|
|
377
|
-
const handler = button.eventHandler
|
|
378
|
-
handler.Argument = value as string
|
|
379
|
-
updateContentProperty(columnIndex, index, "eventHandler", handler)
|
|
380
|
-
}}
|
|
381
|
-
/>
|
|
382
|
-
<UI.Input
|
|
383
|
-
wrapperClass="min-w-40 flex-[2]"
|
|
384
|
-
label={{ name: $T("constructor.props.table.keys") }}
|
|
385
|
-
value={button.eventHandler?.Variables.join(" ")}
|
|
386
|
-
maxlength={500}
|
|
387
|
-
help={{ info: $T("constructor.props.table.keys.info"), regExp: /^[a-zA-Z0-9\-._ ]{0,500}$/ }}
|
|
388
|
-
onUpdate={(value) => {
|
|
389
|
-
const handler = { ...button.eventHandler }
|
|
390
|
-
handler.Variables = (value as string).trim().split(/\s+/)
|
|
391
|
-
updateContentProperty(columnIndex, index, "eventHandler", handler)
|
|
392
|
-
}}
|
|
393
|
-
/>
|
|
394
|
-
</div>
|
|
395
|
-
{:else if content.type == "progressBar"}
|
|
396
|
-
{@const progressBar = content.data}
|
|
397
|
-
<div class="flex flex-wrap items-end gap-2">
|
|
398
|
-
<UI.Input
|
|
399
|
-
wrapperClass="min-w-28 flex-1"
|
|
400
|
-
label={{ name: $T("constructor.props.table.progressBar.key") }}
|
|
401
|
-
value={progressBar.key}
|
|
402
|
-
help={{ regExp: /^[0-9a-zA-Z_.-]{0,16}$/ }}
|
|
403
|
-
onUpdate={(value) => {
|
|
404
|
-
updateTableHeader(
|
|
405
|
-
columnIndex,
|
|
406
|
-
"content",
|
|
407
|
-
column.content.map((textItem: any, i: number) =>
|
|
408
|
-
i == index ? { ...textItem, data: { ...textItem.data, key: value } } : textItem,
|
|
409
|
-
),
|
|
410
|
-
)
|
|
411
|
-
// updateTableBody()
|
|
412
|
-
}}
|
|
413
|
-
/>
|
|
414
|
-
<UI.Input
|
|
415
|
-
wrapperClass="min-w-20 flex-1"
|
|
416
|
-
label={{ name: $T("constructor.props.min") }}
|
|
417
|
-
value={(progressBar?.minNum as number) ?? 0}
|
|
418
|
-
type="number"
|
|
419
|
-
onUpdate={(value) => updateContentProperty(columnIndex, index, "minNum", value as string)}
|
|
420
|
-
/>
|
|
421
|
-
<UI.Input
|
|
422
|
-
wrapperClass="min-w-20 flex-1"
|
|
423
|
-
label={{ name: $T("constructor.props.max") }}
|
|
424
|
-
value={(progressBar?.maxNum as number) ?? 100}
|
|
425
|
-
type="number"
|
|
426
|
-
onUpdate={(value) => updateContentProperty(columnIndex, index, "maxNum", value as string)}
|
|
427
|
-
/>
|
|
428
|
-
<UI.Input
|
|
429
|
-
wrapperClass="min-w-24 flex-1"
|
|
430
|
-
label={{ name: $T("constructor.props.units") }}
|
|
431
|
-
value={progressBar?.units}
|
|
432
|
-
onUpdate={(value) => updateContentProperty(columnIndex, index, "units", value as string)}
|
|
433
|
-
/>
|
|
434
|
-
</div>
|
|
435
|
-
{:else if !forConstructor && content.type == "image"}
|
|
436
|
-
{@const image = content.data}
|
|
437
|
-
<div class="flex flex-wrap items-end gap-2">
|
|
438
|
-
<div class="relative min-w-32">
|
|
439
|
-
<CommonSnippets
|
|
440
|
-
snippet="IconsLib"
|
|
441
|
-
initialValue={{
|
|
442
|
-
name: $T("constructor.props.table.columns.defaultIcon"),
|
|
443
|
-
icon: image?.defaultIcon ?? "",
|
|
444
|
-
updateProperty: (icon: string) => {
|
|
445
|
-
updateContentProperty(columnIndex, index, "defaultIcon", icon)
|
|
446
|
-
if (!image?.width || image?.width === "0rem") updateContentProperty(columnIndex, index, "width", "1rem")
|
|
447
|
-
if (!image?.width || image?.height === "0rem") updateContentProperty(columnIndex, index, "height", "1rem")
|
|
448
|
-
},
|
|
449
|
-
icons: { array: ICONS },
|
|
393
|
+
{:else if content.type == "progressBar"}
|
|
394
|
+
{@const progressBar = content.data}
|
|
395
|
+
<div class="flex flex-wrap items-end gap-2">
|
|
396
|
+
<UI.Input
|
|
397
|
+
wrapperClass="min-w-28 flex-1"
|
|
398
|
+
label={{ name: $T("constructor.props.table.progressBar.key") }}
|
|
399
|
+
value={progressBar.key}
|
|
400
|
+
help={{ regExp: /^[0-9a-zA-Z_.-]{0,16}$/ }}
|
|
401
|
+
onUpdate={(value) => {
|
|
402
|
+
updateTableHeader(
|
|
403
|
+
columnIndex,
|
|
404
|
+
"content",
|
|
405
|
+
column.content.map((textItem: any, i: number) =>
|
|
406
|
+
i == index ? { ...textItem, data: { ...textItem.data, key: value } } : textItem,
|
|
407
|
+
),
|
|
408
|
+
)
|
|
409
|
+
// updateTableBody()
|
|
450
410
|
}}
|
|
451
|
-
|
|
452
|
-
|
|
411
|
+
/>
|
|
412
|
+
<UI.Input
|
|
413
|
+
wrapperClass="min-w-20 flex-1"
|
|
414
|
+
label={{ name: $T("constructor.props.min") }}
|
|
415
|
+
value={(progressBar?.minNum as number) ?? 0}
|
|
416
|
+
type="number"
|
|
417
|
+
onUpdate={(value) => updateContentProperty(columnIndex, index, "minNum", value as string)}
|
|
418
|
+
/>
|
|
419
|
+
<UI.Input
|
|
420
|
+
wrapperClass="min-w-20 flex-1"
|
|
421
|
+
label={{ name: $T("constructor.props.max") }}
|
|
422
|
+
value={(progressBar?.maxNum as number) ?? 100}
|
|
423
|
+
type="number"
|
|
424
|
+
onUpdate={(value) => updateContentProperty(columnIndex, index, "maxNum", value as string)}
|
|
425
|
+
/>
|
|
426
|
+
<UI.Input
|
|
427
|
+
wrapperClass="min-w-24 flex-1"
|
|
428
|
+
label={{ name: $T("constructor.props.units") }}
|
|
429
|
+
value={progressBar?.units}
|
|
430
|
+
onUpdate={(value) => updateContentProperty(columnIndex, index, "units", value as string)}
|
|
431
|
+
/>
|
|
432
|
+
</div>
|
|
433
|
+
{:else if !forConstructor && content.type == "image"}
|
|
434
|
+
{@const image = content.data}
|
|
435
|
+
<div class="flex flex-wrap items-end gap-2">
|
|
436
|
+
<div class="relative min-w-32">
|
|
437
|
+
<CommonSnippets
|
|
438
|
+
snippet="IconsLib"
|
|
439
|
+
initialValue={{
|
|
440
|
+
name: $T("constructor.props.table.columns.defaultIcon"),
|
|
441
|
+
icon: image?.defaultIcon ?? "",
|
|
442
|
+
updateProperty: (icon: string) => {
|
|
443
|
+
updateContentProperty(columnIndex, index, "defaultIcon", icon)
|
|
444
|
+
if (!image?.width || image?.width === "0rem") updateContentProperty(columnIndex, index, "width", "1rem")
|
|
445
|
+
if (!image?.width || image?.height === "0rem") updateContentProperty(columnIndex, index, "height", "1rem")
|
|
446
|
+
},
|
|
447
|
+
icons: { array: ICONS },
|
|
448
|
+
}}
|
|
449
|
+
{component}
|
|
450
|
+
{onPropertyChange}
|
|
451
|
+
/>
|
|
452
|
+
</div>
|
|
453
|
+
<UI.Input
|
|
454
|
+
wrapperClass="min-w-32 flex-1"
|
|
455
|
+
label={{ name: $T("constructor.props.table.columns.class") }}
|
|
456
|
+
value={image?.class}
|
|
457
|
+
onUpdate={(value) => updateContentProperty(columnIndex, index, "class", value)}
|
|
458
|
+
/>
|
|
459
|
+
<UI.Input
|
|
460
|
+
wrapperClass="min-w-20 flex-1"
|
|
461
|
+
label={{ name: $T("constructor.props.table.columns.image.width"), class: "px-0" }}
|
|
462
|
+
type="number"
|
|
463
|
+
number={{ minNum: 0, maxNum: 1000, step: 1 }}
|
|
464
|
+
value={Number(image?.width?.replace("rem", "") ?? 0)}
|
|
465
|
+
onUpdate={(value) => updateContentProperty(columnIndex, index, "width", `${value ?? 0}rem`)}
|
|
466
|
+
/>
|
|
467
|
+
<UI.Input
|
|
468
|
+
wrapperClass="min-w-20 flex-1"
|
|
469
|
+
label={{ name: $T("constructor.props.table.columns.image.height"), class: "px-0" }}
|
|
470
|
+
type="number"
|
|
471
|
+
number={{ minNum: 0, maxNum: 1000, step: 1 }}
|
|
472
|
+
value={Number(image?.height?.replace("rem", "") ?? 0)}
|
|
473
|
+
onUpdate={(value) => updateContentProperty(columnIndex, index, "height", `${value ?? 0}rem`)}
|
|
453
474
|
/>
|
|
454
475
|
</div>
|
|
455
|
-
|
|
456
|
-
wrapperClass="min-w-32 flex-1"
|
|
457
|
-
label={{ name: $T("constructor.props.table.columns.class") }}
|
|
458
|
-
value={image?.class}
|
|
459
|
-
onUpdate={(value) => updateContentProperty(columnIndex, index, "class", value)}
|
|
460
|
-
/>
|
|
461
|
-
<UI.Input
|
|
462
|
-
wrapperClass="min-w-20 flex-1"
|
|
463
|
-
label={{ name: $T("constructor.props.table.columns.image.width"), class: "px-0" }}
|
|
464
|
-
type="number"
|
|
465
|
-
number={{ minNum: 0, maxNum: 1000, step: 1 }}
|
|
466
|
-
value={Number(image?.width?.replace("rem", "") ?? 0)}
|
|
467
|
-
onUpdate={(value) => updateContentProperty(columnIndex, index, "width", `${value ?? 0}rem`)}
|
|
468
|
-
/>
|
|
469
|
-
<UI.Input
|
|
470
|
-
wrapperClass="min-w-20 flex-1"
|
|
471
|
-
label={{ name: $T("constructor.props.table.columns.image.height"), class: "px-0" }}
|
|
472
|
-
type="number"
|
|
473
|
-
number={{ minNum: 0, maxNum: 1000, step: 1 }}
|
|
474
|
-
value={Number(image?.height?.replace("rem", "") ?? 0)}
|
|
475
|
-
onUpdate={(value) => updateContentProperty(columnIndex, index, "height", `${value ?? 0}rem`)}
|
|
476
|
-
/>
|
|
477
|
-
</div>
|
|
478
|
-
{/if}
|
|
476
|
+
{/if}
|
|
479
477
|
</div>
|
|
480
478
|
<UI.Button
|
|
481
479
|
wrapperClass="w-8 shrink-0"
|
|
@@ -79,10 +79,7 @@
|
|
|
79
79
|
}
|
|
80
80
|
</script>
|
|
81
81
|
|
|
82
|
-
<div
|
|
83
|
-
class={twMerge("flex w-full flex-col gap-4 rounded-2xl border border-(--hairline-color) bg-(--container-color) p-4", wrapperClass)}
|
|
84
|
-
style={accentStyle}
|
|
85
|
-
>
|
|
82
|
+
<div class={twMerge("flex w-full flex-col gap-4 rounded-2xl border border-(--hairline-color) bg-(--container-color) p-4", wrapperClass)} style={accentStyle}>
|
|
86
83
|
<!-- Заголовок -->
|
|
87
84
|
<WidgetHeader icon={CpuIcon} label={label?.name ?? "Информация об устройстве"} bind:collapsed>
|
|
88
85
|
{#snippet right()}
|
|
@@ -10,18 +10,7 @@
|
|
|
10
10
|
import PropsGroup from "../PropsGroup.svelte"
|
|
11
11
|
import { optionsStore } from "../options"
|
|
12
12
|
|
|
13
|
-
const DEVICE_INFO_KEYS: (keyof IWidgetDeviceInfoConfig)[] = [
|
|
14
|
-
"DevSN",
|
|
15
|
-
"DevID",
|
|
16
|
-
"DevFW",
|
|
17
|
-
"RunCnt",
|
|
18
|
-
"DevName",
|
|
19
|
-
"HostName",
|
|
20
|
-
"WebUser",
|
|
21
|
-
"WebPsw",
|
|
22
|
-
"StaIP",
|
|
23
|
-
"ApIP",
|
|
24
|
-
]
|
|
13
|
+
const DEVICE_INFO_KEYS: (keyof IWidgetDeviceInfoConfig)[] = ["DevSN", "DevID", "DevFW", "RunCnt", "DevName", "HostName", "WebUser", "WebPsw", "StaIP", "ApIP"]
|
|
25
14
|
|
|
26
15
|
const { component, onPropertyChange } = $props<{
|
|
27
16
|
component: UIComponent & { properties: Partial<IWidgetDeviceInfoProps> }
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script lang="ts"></script>
|
|
2
|
+
|
|
3
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"
|
|
4
|
+
><path
|
|
5
|
+
fill="none"
|
|
6
|
+
stroke="currentColor"
|
|
7
|
+
stroke-linecap="round"
|
|
8
|
+
stroke-linejoin="round"
|
|
9
|
+
stroke-width="2"
|
|
10
|
+
d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5M9 18h6M10 22h4"
|
|
11
|
+
/></svg
|
|
12
|
+
>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const LightIcon: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type LightIcon = InstanceType<typeof LightIcon>;
|
|
18
|
+
export default LightIcon;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# WidgetRGBStrip Component
|
|
2
|
+
|
|
3
|
+
## Описание
|
|
4
|
+
|
|
5
|
+
> **Размеры.** Ширина — 100% контейнера (в конструкторе — ширина ячейки сетки), без собственного ограничения; высота — по содержимому, в сетке конструктора виджет занимает одну строку. Мастер-цвет (`componentClass`) перекрашивает весь виджет.
|
|
6
|
+
|
|
7
|
+
Смарт-виджет адресной светодиодной ленты. Работает с полями группы `LST` ровно в том виде, в каком их
|
|
8
|
+
отдаёт драйвер `RGB_Strip` (ProdFactory-ESP). Компонент ничего не знает про DeviceStore/WebSocket —
|
|
9
|
+
только `value` и колбэки `onChange`/`onSave`.
|
|
10
|
+
|
|
11
|
+
- **Свечение** — палитра цвета, белый канал (только для модели SK6812), эффект и скорость. Каждое
|
|
12
|
+
изменение сразу уходит в `onChange`.
|
|
13
|
+
- **Оборудование** — количество светодиодов (1…`MaxCount`), модель чипа и порядок цветов.
|
|
14
|
+
Редактируются локально и отправляются одним пакетом по «Сохранить»: прошивка пересоздаёт драйвер
|
|
15
|
+
ленты. Пока правок нет, поля показывают свежие значения с устройства.
|
|
16
|
+
- В шапке — образец текущего цвета.
|
|
17
|
+
|
|
18
|
+
## Протокол устройства
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
"LST.Color": [255, 120, 0, 0], // R, G, B, W
|
|
22
|
+
"LST.Effect": 0, // 0 статичный, 1 радуга, 2 дыхание, 3 палитра, 4 комета, 5 мерцание, 6 огонь, 7 бегущие огни
|
|
23
|
+
"LST.Speed": 3, // 0..10, 0 — пауза
|
|
24
|
+
"LST.Count": 64, // 1..MaxCount
|
|
25
|
+
"LST.Model": 0, // 0 WS2812, 1 SK6812 (RGBW), 2 WS2811
|
|
26
|
+
"LST.Order": 0, // 0 GRB, 1 RGB, 2 BRG, 3 RBG, 4 GBR, 5 BGR
|
|
27
|
+
"LST.MaxCount": 64 // ёмкость из прошивки, только чтение
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Изменения — `SET ModCfg` с нужными ключами группы. Пин данных и способ передачи (RMT/SPI) задаются
|
|
31
|
+
в прошивке (`RGB_Strip_Init`) и через виджет не меняются.
|
|
32
|
+
|
|
33
|
+
## Пропсы
|
|
34
|
+
|
|
35
|
+
| Название | Тип | По умолчанию | Описание |
|
|
36
|
+
| ---------------- | -------------------------------------------------------- | -------------------------------- | ----------------------------------------------------------------------- |
|
|
37
|
+
| `id` | `string` | `undefined` | Префикс группы ленты (`"LST"`), используется вызывающим кодом |
|
|
38
|
+
| `wrapperClass` | `string` | `""` | CSS-классы обёртки |
|
|
39
|
+
| `componentClass` | `string` | `""` | «Мастер-цвет» виджета — роль вида `"bg-red"`, перекрашивает весь виджет |
|
|
40
|
+
| `label` | `{ name?: string }` | `{ name: "Светодиодная лента" }` | Заголовок карточки |
|
|
41
|
+
| `value` | `Partial<IWidgetRGBStripConfig>` | `undefined` | Поля группы `LST` |
|
|
42
|
+
| `keys` | `Partial<Record<keyof IWidgetRGBStripConfig, string>>` | `undefined` | Переопределение имён полей устройства |
|
|
43
|
+
| `collapsed` | `boolean` | `false` | Свёрнуто ли тело виджета (`$bindable`) |
|
|
44
|
+
| `persistKey` | `string` | `undefined` | Ключ для сохранения `collapsed` в `localStorage` |
|
|
45
|
+
| `onChange` | `(field: "Color" \| "Effect" \| "Speed", value) => void` | `undefined` | Изменение свечения — отправляется сразу; без обработчика только чтение |
|
|
46
|
+
| `onSave` | `(hardware: { Count, Model, Order }) => void` | `undefined` | Сохранение оборудования одним пакетом |
|
|
47
|
+
|
|
48
|
+
## Примеры
|
|
49
|
+
|
|
50
|
+
```svelte
|
|
51
|
+
<UI.WidgetRGBStrip
|
|
52
|
+
value={strip}
|
|
53
|
+
onChange={(field, value) => send({ [`LST.${field}`]: value })}
|
|
54
|
+
onSave={(hw) => send({ "LST.Count": hw.Count, "LST.Model": hw.Model, "LST.Order": hw.Order })}
|
|
55
|
+
/>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Конструктор свойств (WidgetRGBStripProps.svelte)
|
|
59
|
+
|
|
60
|
+
Группа «Общие»: заголовок, префикс группы ленты (ID), мастер-цвет.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
<!-- $lib/WidgetRGBStrip/WidgetRGBStrip.svelte — смарт-виджет адресной светодиодной ленты.
|
|
2
|
+
Поля группы LST (RGB_Strip, ProdFactory-ESP): Color/Effect/Speed отправляются сразу (onChange),
|
|
3
|
+
Count/Model/Order — одним пакетом по «Сохранить» (onSave): прошивка пересоздаёт драйвер ленты.
|
|
4
|
+
Ничего не знает про DeviceStore/WebSocket — только value и колбэки. -->
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import { untrack } from "svelte"
|
|
7
|
+
import { slide } from "svelte/transition"
|
|
8
|
+
import { twMerge } from "tailwind-merge"
|
|
9
|
+
import * as UI from ".."
|
|
10
|
+
import type { IOption, IWidgetRGBStripConfig, IWidgetRGBStripProps } from "../types"
|
|
11
|
+
import LightIcon from "./LightIcon.svelte"
|
|
12
|
+
import WidgetHeader from "../WidgetHeader.svelte"
|
|
13
|
+
import ArrowIcon from "../libIcons/ArrowIcon.svelte"
|
|
14
|
+
import { widgetAccentStyle } from "../widgetAccent"
|
|
15
|
+
import { readPersistedCollapsed, writePersistedCollapsed } from "../widgetCollapse"
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
wrapperClass = "",
|
|
19
|
+
componentClass = "",
|
|
20
|
+
label = { name: "Светодиодная лента" },
|
|
21
|
+
value,
|
|
22
|
+
collapsed = $bindable(false),
|
|
23
|
+
persistKey,
|
|
24
|
+
onChange,
|
|
25
|
+
onSave,
|
|
26
|
+
}: IWidgetRGBStripProps = $props()
|
|
27
|
+
|
|
28
|
+
let accentStyle = $derived(widgetAccentStyle(componentClass ?? ""))
|
|
29
|
+
|
|
30
|
+
/* Сворачивание тела по клику на шапку — как у остальных смарт-виджетов (см. WidgetWiFi.svelte) */
|
|
31
|
+
let hasMergedPersisted = false
|
|
32
|
+
$effect(() => {
|
|
33
|
+
if (hasMergedPersisted) return
|
|
34
|
+
hasMergedPersisted = true
|
|
35
|
+
const persisted = readPersistedCollapsed(persistKey, collapsed)
|
|
36
|
+
if (persisted !== collapsed) collapsed = persisted
|
|
37
|
+
})
|
|
38
|
+
$effect(() => writePersistedCollapsed(persistKey, collapsed))
|
|
39
|
+
|
|
40
|
+
const EFFECTS: IOption<number>[] = [
|
|
41
|
+
{ id: "static", value: 0, name: "Статичный" },
|
|
42
|
+
{ id: "rainbow", value: 1, name: "Радуга" },
|
|
43
|
+
{ id: "breath", value: 2, name: "Дыхание" },
|
|
44
|
+
{ id: "palette", value: 3, name: "Палитра" },
|
|
45
|
+
{ id: "chase", value: 4, name: "Комета" },
|
|
46
|
+
{ id: "twinkle", value: 5, name: "Мерцание" },
|
|
47
|
+
{ id: "fire", value: 6, name: "Огонь" },
|
|
48
|
+
{ id: "theater", value: 7, name: "Бегущие огни" },
|
|
49
|
+
]
|
|
50
|
+
const MODELS: IOption<number>[] = [
|
|
51
|
+
{ id: "ws2812", value: 0, name: "WS2812" },
|
|
52
|
+
{ id: "sk6812", value: 1, name: "SK6812" },
|
|
53
|
+
{ id: "ws2811", value: 2, name: "WS2811" },
|
|
54
|
+
]
|
|
55
|
+
const ORDERS: IOption<number>[] = ["GRB", "RGB", "BRG", "RBG", "GBR", "BGR"].map((name, index) => ({ id: name, value: index, name }))
|
|
56
|
+
|
|
57
|
+
/* Живые значения с устройства */
|
|
58
|
+
let color = $derived([0, 1, 2, 3].map((i) => Number(value?.Color?.[i] ?? 0)))
|
|
59
|
+
let effectIndex = $derived(Number(value?.Effect ?? 0))
|
|
60
|
+
let speed = $derived(Number(value?.Speed ?? 0))
|
|
61
|
+
let maxCount = $derived(Number(value?.MaxCount ?? 0) || 1024)
|
|
62
|
+
|
|
63
|
+
/* Оборудование редактируется локально и уходит одним пакетом; свежие значения с устройства подхватываются, пока нет правок */
|
|
64
|
+
type Hardware = Pick<IWidgetRGBStripConfig, "Count" | "Model" | "Order">
|
|
65
|
+
const fromValue = (): Hardware => ({ Count: Number(value?.Count ?? 1), Model: Number(value?.Model ?? 0), Order: Number(value?.Order ?? 0) })
|
|
66
|
+
let hardware: Hardware = $state(untrack(fromValue))
|
|
67
|
+
let savedSnapshot = $state(untrack(() => JSON.stringify(fromValue())))
|
|
68
|
+
let isDirty = $derived(JSON.stringify(hardware) !== savedSnapshot)
|
|
69
|
+
$effect(() => {
|
|
70
|
+
const incoming = fromValue()
|
|
71
|
+
if (!untrack(() => isDirty)) {
|
|
72
|
+
hardware = incoming
|
|
73
|
+
savedSnapshot = JSON.stringify(incoming)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
/* Оборудование — редкие настройки, свёрнуты по умолчанию; несохранённые правки держат блок раскрытым */
|
|
77
|
+
let hardwareOpen = $state(false)
|
|
78
|
+
let countValid = $derived(Number.isInteger(hardware.Count) && hardware.Count >= 1 && hardware.Count <= maxCount)
|
|
79
|
+
let hasWhite = $derived(hardware.Model === 1)
|
|
80
|
+
|
|
81
|
+
const save = () => {
|
|
82
|
+
if (!countValid) return
|
|
83
|
+
onSave?.($state.snapshot(hardware))
|
|
84
|
+
savedSnapshot = JSON.stringify(hardware)
|
|
85
|
+
}
|
|
86
|
+
const sendColor = (rgbw: number[]) => onChange?.("Color", rgbw)
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<div
|
|
90
|
+
class={twMerge("relative flex w-full flex-col gap-4 rounded-2xl border border-(--hairline-color) bg-(--container-color) p-4", wrapperClass)}
|
|
91
|
+
style={accentStyle}
|
|
92
|
+
>
|
|
93
|
+
<!-- Заголовок: образец текущего цвета -->
|
|
94
|
+
<WidgetHeader icon={LightIcon} label={label?.name ?? "Светодиодная лента"} bind:collapsed>
|
|
95
|
+
{#snippet right()}
|
|
96
|
+
<span class="size-6 rounded-md border border-(--hairline-color)" style="background: rgb({color[0]}, {color[1]}, {color[2]})" title="Текущий цвет"></span>
|
|
97
|
+
{/snippet}
|
|
98
|
+
</WidgetHeader>
|
|
99
|
+
|
|
100
|
+
{#if !collapsed}
|
|
101
|
+
<div class="flex flex-col gap-4" transition:slide={{ duration: 150 }}>
|
|
102
|
+
<!-- Свечение: применяется сразу -->
|
|
103
|
+
<div class="flex flex-col gap-3 rounded-[10px] border border-(--hairline-color) bg-(--back-color) p-3">
|
|
104
|
+
<UI.ColorPicker value={color.slice(0, 3)} readonly={!onChange} onChange={(rgb) => sendColor([...rgb, color[3]])} />
|
|
105
|
+
{#if hasWhite}
|
|
106
|
+
<UI.Slider
|
|
107
|
+
label={{ name: "Белый канал" }}
|
|
108
|
+
value={color[3]}
|
|
109
|
+
number={{ minNum: 0, maxNum: 255, step: 1 }}
|
|
110
|
+
disabled={!onChange}
|
|
111
|
+
onUpdate={(w) => sendColor([color[0], color[1], color[2], w as number])}
|
|
112
|
+
/>
|
|
113
|
+
{/if}
|
|
114
|
+
<UI.Select
|
|
115
|
+
label={{ name: "Эффект" }}
|
|
116
|
+
options={EFFECTS}
|
|
117
|
+
value={EFFECTS.find((o) => o.value === effectIndex) ?? EFFECTS[0]}
|
|
118
|
+
disabled={!onChange}
|
|
119
|
+
onUpdate={(option) => !Array.isArray(option) && onChange?.("Effect", option.value ?? 0)}
|
|
120
|
+
/>
|
|
121
|
+
<UI.Slider
|
|
122
|
+
label={{ name: "Скорость эффекта (0 — пауза)" }}
|
|
123
|
+
value={speed}
|
|
124
|
+
number={{ minNum: 0, maxNum: 10, step: 1 }}
|
|
125
|
+
disabled={!onChange || effectIndex === 0}
|
|
126
|
+
onUpdate={(v) => onChange?.("Speed", v as number)}
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<!-- Оборудование: свёрнуто по умолчанию, сохраняется одним пакетом, лента перезапускается -->
|
|
131
|
+
<div class="flex flex-col gap-3 rounded-[10px] border border-(--hairline-color) bg-(--back-color) p-3">
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
class="flex w-full cursor-pointer items-center justify-between gap-2 border-none bg-transparent p-0 text-left"
|
|
135
|
+
aria-expanded={hardwareOpen || isDirty}
|
|
136
|
+
onclick={() => (hardwareOpen = !hardwareOpen)}
|
|
137
|
+
>
|
|
138
|
+
<span class="text-[12px] font-semibold text-(--muted-color)">
|
|
139
|
+
Оборудование
|
|
140
|
+
<span class="font-normal text-(--faint-color)">
|
|
141
|
+
· {hardware.Count} LED · {MODELS[hardware.Model]?.name ?? "—"} · {ORDERS[hardware.Order]?.name ?? "—"}
|
|
142
|
+
</span>
|
|
143
|
+
</span>
|
|
144
|
+
<span
|
|
145
|
+
class="flex size-3.5 shrink-0 text-(--faint-color) transition-transform duration-150 [&_svg]:h-full [&_svg]:w-full"
|
|
146
|
+
style="transform: rotate({hardwareOpen || isDirty ? 90 : 0}deg)"
|
|
147
|
+
>
|
|
148
|
+
<ArrowIcon />
|
|
149
|
+
</span>
|
|
150
|
+
</button>
|
|
151
|
+
{#if hardwareOpen || isDirty}
|
|
152
|
+
<div class="flex flex-col gap-3" transition:slide={{ duration: 150 }}>
|
|
153
|
+
<UI.Input
|
|
154
|
+
label={{ name: `Количество светодиодов (1–${maxCount})` }}
|
|
155
|
+
type="number"
|
|
156
|
+
number={{ minNum: 1, maxNum: maxCount, step: 1 }}
|
|
157
|
+
value={hardware.Count}
|
|
158
|
+
onUpdate={(v) => (hardware.Count = Number(v))}
|
|
159
|
+
/>
|
|
160
|
+
<UI.Select
|
|
161
|
+
label={{ name: "Модель чипа" }}
|
|
162
|
+
type="buttons"
|
|
163
|
+
options={MODELS}
|
|
164
|
+
value={MODELS.find((o) => o.value === hardware.Model) ?? MODELS[0]}
|
|
165
|
+
onUpdate={(option) => !Array.isArray(option) && (hardware.Model = option.value ?? 0)}
|
|
166
|
+
/>
|
|
167
|
+
<UI.Select
|
|
168
|
+
label={{ name: "Порядок цветов" }}
|
|
169
|
+
type="buttons"
|
|
170
|
+
options={ORDERS}
|
|
171
|
+
value={ORDERS.find((o) => o.value === hardware.Order) ?? ORDERS[0]}
|
|
172
|
+
onUpdate={(option) => !Array.isArray(option) && (hardware.Order = option.value ?? 0)}
|
|
173
|
+
/>
|
|
174
|
+
{#if isDirty}
|
|
175
|
+
<span class="text-[12px] text-(--muted-color)" transition:slide={{ duration: 100 }}>
|
|
176
|
+
{countValid ? "При сохранении лента перезапустится с новыми настройками" : `Количество — целое от 1 до ${maxCount}`}
|
|
177
|
+
</span>
|
|
178
|
+
{/if}
|
|
179
|
+
<UI.Button componentClass="bg-accent" content={{ name: "Сохранить" }} disabled={!onSave || !isDirty || !countValid} onClick={save} />
|
|
180
|
+
</div>
|
|
181
|
+
{/if}
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
{/if}
|
|
185
|
+
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<!-- $lib/WidgetRGBStrip/WidgetRGBStripProps.svelte — панель пропсов конструктора для WidgetRGBStrip:
|
|
2
|
+
заголовок, префикс группы (по умолчанию LST), мастер-цвет. -->
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import { T } from "../locales/i18n"
|
|
5
|
+
import * as UI from ".."
|
|
6
|
+
import { updateProperty, type UIComponent, type IWidgetRGBStripProps } from "../types"
|
|
7
|
+
import WidgetAccentPicker from "../WidgetAccentPicker.svelte"
|
|
8
|
+
import PropsGroup from "../PropsGroup.svelte"
|
|
9
|
+
|
|
10
|
+
const { component, onPropertyChange } = $props<{
|
|
11
|
+
component: UIComponent & { properties: Partial<IWidgetRGBStripProps> }
|
|
12
|
+
onPropertyChange: (updates: Partial<{ properties?: string | object; name?: string; access?: string }>) => void
|
|
13
|
+
}>()
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div class="flex flex-col gap-2">
|
|
17
|
+
<PropsGroup label={$T("constructor.props.group.general")}>
|
|
18
|
+
<UI.Input
|
|
19
|
+
label={{ name: "Заголовок" }}
|
|
20
|
+
value={component.properties.label?.name}
|
|
21
|
+
onUpdate={(value) => updateProperty("label.name", value as string, component, onPropertyChange)}
|
|
22
|
+
/>
|
|
23
|
+
<UI.Input
|
|
24
|
+
label={{ name: "Группа ленты (ID)" }}
|
|
25
|
+
value={component.properties.id ?? "LST"}
|
|
26
|
+
onUpdate={(value) => updateProperty("id", value as string, component, onPropertyChange)}
|
|
27
|
+
/>
|
|
28
|
+
<WidgetAccentPicker
|
|
29
|
+
value={component.properties.componentClass ?? ""}
|
|
30
|
+
onUpdate={(value) => updateProperty("componentClass", value, component, onPropertyChange)}
|
|
31
|
+
/>
|
|
32
|
+
</PropsGroup>
|
|
33
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type UIComponent, type IWidgetRGBStripProps } from "../types";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
component: UIComponent & {
|
|
4
|
+
properties: Partial<IWidgetRGBStripProps>;
|
|
5
|
+
};
|
|
6
|
+
onPropertyChange: (updates: Partial<{
|
|
7
|
+
properties?: string | object;
|
|
8
|
+
name?: string;
|
|
9
|
+
access?: string;
|
|
10
|
+
}>) => void;
|
|
11
|
+
};
|
|
12
|
+
declare const WidgetRGBStripProps: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
13
|
+
type WidgetRGBStripProps = ReturnType<typeof WidgetRGBStripProps>;
|
|
14
|
+
export default WidgetRGBStripProps;
|
package/dist/index.d.ts
CHANGED
|
@@ -47,7 +47,9 @@ export { default as WidgetFileSystem } from "./WidgetFileSystem/WidgetFileSystem
|
|
|
47
47
|
export { default as WidgetFileSystemProps } from "./WidgetFileSystem/WidgetFileSystemProps.svelte";
|
|
48
48
|
export { default as WidgetStackInfo } from "./WidgetStackInfo/WidgetStackInfo.svelte";
|
|
49
49
|
export { default as WidgetStackInfoProps } from "./WidgetStackInfo/WidgetStackInfoProps.svelte";
|
|
50
|
+
export { default as WidgetRGBStrip } from "./WidgetRGBStrip/WidgetRGBStrip.svelte";
|
|
51
|
+
export { default as WidgetRGBStripProps } from "./WidgetRGBStrip/WidgetRGBStripProps.svelte";
|
|
50
52
|
export * from "./locales/i18n";
|
|
51
53
|
export * from "./locales/translations";
|
|
52
54
|
export * from "./ModalStackStore";
|
|
53
|
-
export { type UIComponent, type Position, type IUIComponentHandler, type IButtonProps, type IAccordionProps, type IInputProps, type ISelectProps, type IOption, type ISwitchProps, type IColorPickerProps, type IDividerProps, type ISliderProps, type ITextFieldProps, type IMapProps, type IProgressBarProps, type IWidgetProps, type IWidgetWiFiProps, type IWidgetWiFiConfig, type IWidgetDeviceInfoProps, type IWidgetDeviceInfoConfig, type IWidgetFileSystemProps, type IWidgetFileSystemInfo, type IWidgetFileSystemFile, type IWidgetFileSystemUpload, type IWidgetStackInfoProps, type IWidgetStackInfo, type IWidgetStackInfoHeap, type IWidgetStackInfoTask, type IGraphProps, type IGraphDataObject, type IGraphHistoryPoint, type IGraphHistorySeries, type ITableHeader, type ITableText, type ITableSelect, type ITableButton, type ITableProgressBar, type ITableImage, type ITableContent, type ITableProps, type ITabsProps, type IJoystickProps, type IFileAttachProps, type IDeviceGNSS, type ICarouselProps, type IReceivingDataObject, type IVideoViewerProps, type IPeriodChartProps, type IPeriodChartLevel, } from "./types";
|
|
55
|
+
export { type UIComponent, type Position, type IUIComponentHandler, type IButtonProps, type IAccordionProps, type IInputProps, type ISelectProps, type IOption, type ISwitchProps, type IColorPickerProps, type IDividerProps, type ISliderProps, type ITextFieldProps, type IMapProps, type IProgressBarProps, type IWidgetProps, type IWidgetWiFiProps, type IWidgetWiFiConfig, type IWidgetDeviceInfoProps, type IWidgetDeviceInfoConfig, type IWidgetFileSystemProps, type IWidgetFileSystemInfo, type IWidgetFileSystemFile, type IWidgetFileSystemUpload, type IWidgetStackInfoProps, type IWidgetStackInfo, type IWidgetStackInfoHeap, type IWidgetStackInfoTask, type IWidgetRGBStripProps, type IWidgetRGBStripConfig, type IGraphProps, type IGraphDataObject, type IGraphHistoryPoint, type IGraphHistorySeries, type ITableHeader, type ITableText, type ITableSelect, type ITableButton, type ITableProgressBar, type ITableImage, type ITableContent, type ITableProps, type ITabsProps, type IJoystickProps, type IFileAttachProps, type IDeviceGNSS, type ICarouselProps, type IReceivingDataObject, type IVideoViewerProps, type IPeriodChartProps, type IPeriodChartLevel, } from "./types";
|
package/dist/index.js
CHANGED
|
@@ -48,6 +48,8 @@ export { default as WidgetFileSystem } from "./WidgetFileSystem/WidgetFileSystem
|
|
|
48
48
|
export { default as WidgetFileSystemProps } from "./WidgetFileSystem/WidgetFileSystemProps.svelte";
|
|
49
49
|
export { default as WidgetStackInfo } from "./WidgetStackInfo/WidgetStackInfo.svelte";
|
|
50
50
|
export { default as WidgetStackInfoProps } from "./WidgetStackInfo/WidgetStackInfoProps.svelte";
|
|
51
|
+
export { default as WidgetRGBStrip } from "./WidgetRGBStrip/WidgetRGBStrip.svelte";
|
|
52
|
+
export { default as WidgetRGBStripProps } from "./WidgetRGBStrip/WidgetRGBStripProps.svelte";
|
|
51
53
|
export * from "./locales/i18n";
|
|
52
54
|
export * from "./locales/translations";
|
|
53
55
|
export * from "./ModalStackStore";
|
|
@@ -123,6 +123,11 @@ const translations = {
|
|
|
123
123
|
'🎯 **File system widget** — device storage, file list, deletion and upload.\n\n**🔗 Device link** — a single `CFG.FS` object, no variable binding needed\n- 📊 Usage: `Total`, `Used`, `Free` — bar and percent (orange from 75%, red from 90%)\n- 📄 Files: `List` — path and size; "Delete" — `SET FS` with `Op: Delete` (after confirmation)\n- ⬆️ Upload: `SET FS` with `Op` `UploadStart` → `UploadChunk` → `UploadDone`, progress — `Upload.Progress`. An upload from another client is visible too\n\n**⚠️ Limits**\n- File name — up to 31 characters\n- "Allowed files" — filter in the file dialog\n\n🔄 "Refresh" — `GET FS`.',
|
|
124
124
|
"🎯 **文件系统组件** —— 设备存储、文件列表、删除和上传。\n\n**🔗 与设备的关联** —— 单个 `CFG.FS` 对象,无需绑定变量\n- 📊 占用:`Total`、`Used`、`Free` —— 进度条和百分比(75% 起橙色,90% 起红色)\n- 📄 文件:`List` —— 路径和大小;「删除」—— 带 `Op: Delete` 的 `SET FS`(确认后)\n- ⬆️ 上传:带 `Op` `UploadStart` → `UploadChunk` → `UploadDone` 的 `SET FS`,进度 —— `Upload.Progress`。其他客户端的上传也可见\n\n**⚠️ 限制**\n- 文件名 —— 不超过 31 个字符\n- 「允许的文件」—— 文件对话框中的过滤器\n\n🔄 「刷新」—— `GET FS`。",
|
|
125
125
|
],
|
|
126
|
+
"component.info.WidgetRGBStrip": [
|
|
127
|
+
"🎯 **Виджет «Светодиодная лента»** — управление адресной лентой: цвет, эффект, скорость и параметры самой ленты.\n\n**🔗 Связь с устройством** — поля группы `LST` (драйвер `RGB_Strip`), привязывать переменные не нужно\n- 🎨 `Color` — `[R, G, B, W]`; белый канал виден только для модели SK6812\n- ✨ `Effect` — статичный, радуга, дыхание, палитра, комета, мерцание, огонь, бегущие огни; `Speed` — 0…10 по нелинейной шкале, 0 — пауза; радуга, палитра и огонь цвет не используют\n- 🔧 `Count`, `Model`, `Order` — количество светодиодов, модель чипа, порядок цветов; `MaxCount` — ёмкость из прошивки (только чтение)\n\n**📤 Отправка**\n- Цвет, эффект и скорость применяются сразу\n- Блок «Оборудование» редактируется локально и отправляется одним пакетом по «Сохранить» — лента перезапускается с новыми параметрами\n\n**💡 Подсказки**\n- Лента светит не тем цветом (красный вместо зелёного) — поменяйте порядок цветов: у WS2812 обычно GRB, у части WS2811 — RGB или BRG\n- Пин данных и способ передачи (RMT/SPI) задаются в прошивке и здесь не меняются\n\n⚙️ Другая группа вместо `LST` — поменяйте префикс (ID) в настройках виджета.",
|
|
128
|
+
'🎯 **LED strip widget** — controls an addressable strip: color, effect, speed and the strip itself.\n\n**🔗 Device link** — fields of the `LST` group (`RGB_Strip` driver), no variable binding needed\n- 🎨 `Color` — `[R, G, B, W]`; the white channel is shown only for the SK6812 model\n- ✨ `Effect` — static, rainbow, breath, palette, chase, twinkle, fire, theater chase; `Speed` — 0…10 on a non-linear scale, 0 is pause; rainbow, palette and fire ignore the color\n- 🔧 `Count`, `Model`, `Order` — number of LEDs, chip model, color order; `MaxCount` — firmware capacity (read only)\n\n**📤 Sending**\n- Color, effect and speed apply immediately\n- The "Hardware" block is edited locally and sent in one packet on "Save" — the strip restarts with the new settings\n\n**💡 Tips**\n- Wrong colors (red instead of green) — change the color order: WS2812 is usually GRB, some WS2811 are RGB or BRG\n- The data pin and transport (RMT/SPI) are set in firmware and can\'t be changed here\n\n⚙️ A group other than `LST` — change the prefix (ID) in widget settings.',
|
|
129
|
+
"🎯 **LED 灯带组件** —— 控制可寻址灯带:颜色、效果、速度以及灯带本身的参数。\n\n**🔗 与设备的关联** —— `LST` 分组的字段(`RGB_Strip` 驱动),无需绑定变量\n- 🎨 `Color` —— `[R, G, B, W]`;白色通道仅在 SK6812 型号时显示\n- ✨ `Effect` —— 静态、彩虹、呼吸、调色板、彗星、闪烁、火焰、跑马灯;`Speed` —— 0…10(非线性),0 为暂停;彩虹、调色板和火焰不使用颜色\n- 🔧 `Count`、`Model`、`Order` —— LED 数量、芯片型号、颜色顺序;`MaxCount` —— 固件中的容量(只读)\n\n**📤 发送**\n- 颜色、效果和速度立即生效\n- 「硬件」区块在本地编辑,点击「保存」一次性发送 —— 灯带将以新参数重启\n\n**💡 提示**\n- 颜色不对(红色变绿色)—— 更改颜色顺序:WS2812 通常为 GRB,部分 WS2811 为 RGB 或 BRG\n- 数据引脚和传输方式(RMT/SPI)在固件中设定,此处无法修改\n\n⚙️ 若分组不是 `LST` —— 在组件设置中修改前缀(ID)。",
|
|
130
|
+
],
|
|
126
131
|
"component.info.WidgetStackInfo": [
|
|
127
132
|
"🎯 **Виджет «Память и задачи»** — отладка: свободная память и запас стека задач прошивки.\n\n**🔗 Связь с устройством** — один объект `StackInfo` из ответа на `GET StackInfo`\n- 📊 **Кучи** `Heap[]`: занято / всего, свободно и минимум свободного с момента старта; светлая часть полосы — пик занятости\n- 🧵 **Задачи** `Tasks[]`: имя, состояние, приоритет, ядро (ESP32), минимальный запас стека в байтах\n\n**⚙️ Настройки**\n- Порог запаса стека — меньше порога красным, меньше удвоенного — оранжевым\n- Автоопрос: выкл / 1 / 5 / 10 с; свёрнутый виджет не опрашивает\n\n💡 Задачи всегда отсортированы по запасу стека — самые рискованные сверху.",
|
|
128
133
|
"🎯 **Memory and tasks widget** — debugging: free memory and firmware task stack headroom.\n\n**🔗 Device link** — a single `StackInfo` object from the `GET StackInfo` response\n- 📊 **Heaps** `Heap[]`: used / total, free and minimum free since boot; the light part of the bar is the usage peak\n- 🧵 **Tasks** `Tasks[]`: name, state, priority, core (ESP32), minimum stack headroom in bytes\n\n**⚙️ Settings**\n- Stack headroom threshold — below it red, below double — orange\n- Auto-polling: off / 1 / 5 / 10 s; a collapsed widget doesn't poll\n\n💡 Tasks are always sorted by stack headroom — the riskiest on top.",
|
package/dist/types.d.ts
CHANGED
|
@@ -15,10 +15,10 @@ export declare const updateComponent: (component: UIComponent, updates: Partial<
|
|
|
15
15
|
}>) => {
|
|
16
16
|
access: "full" | "viewOnly" | "hidden" | undefined;
|
|
17
17
|
name: string | undefined;
|
|
18
|
-
properties: IAccordionProps | IButtonProps | IColorPickerProps | IDividerProps | IFileAttachProps | IGraphProps | IInputProps | IJoystickProps | IMapProps | IPeriodChartProps | IProgressBarProps | ISelectProps<unknown> | ISliderProps | ISwitchProps | ITableProps<object> | ITabsProps | ITextFieldProps | IWidgetProps | IWidgetWiFiProps | IWidgetDeviceInfoProps | IWidgetFileSystemProps | IWidgetStackInfoProps;
|
|
18
|
+
properties: IAccordionProps | IButtonProps | IColorPickerProps | IDividerProps | IFileAttachProps | IGraphProps | IInputProps | IJoystickProps | IMapProps | IPeriodChartProps | IProgressBarProps | ISelectProps<unknown> | ISliderProps | ISwitchProps | ITableProps<object> | ITabsProps | ITextFieldProps | IWidgetProps | IWidgetWiFiProps | IWidgetDeviceInfoProps | IWidgetFileSystemProps | IWidgetStackInfoProps | IWidgetRGBStripProps;
|
|
19
19
|
eventHandler: IUIComponentHandler | undefined;
|
|
20
20
|
id: string;
|
|
21
|
-
type: "Accordion" | "Button" | "ColorPicker" | "Divider" | "FileAttach" | "Graph" | "Input" | "Joystick" | "Map" | "PeriodChart" | "ProgressBar" | "Select" | "Slider" | "Switch" | "Table" | "Tabs" | "TextField" | "VideoViewer" | "Widget" | "WidgetWiFi" | "WidgetDeviceInfo" | "WidgetFileSystem" | "WidgetStackInfo";
|
|
21
|
+
type: "Accordion" | "Button" | "ColorPicker" | "Divider" | "FileAttach" | "Graph" | "Input" | "Joystick" | "Map" | "PeriodChart" | "ProgressBar" | "Select" | "Slider" | "Switch" | "Table" | "Tabs" | "TextField" | "VideoViewer" | "Widget" | "WidgetWiFi" | "WidgetDeviceInfo" | "WidgetFileSystem" | "WidgetStackInfo" | "WidgetRGBStrip";
|
|
22
22
|
position: Required<Position>;
|
|
23
23
|
parentId: string;
|
|
24
24
|
};
|
|
@@ -26,8 +26,8 @@ export interface UIComponent {
|
|
|
26
26
|
id: string;
|
|
27
27
|
name?: string;
|
|
28
28
|
access?: "full" | "viewOnly" | "hidden";
|
|
29
|
-
type: "Accordion" | "Button" | "ColorPicker" | "Divider" | "FileAttach" | "Graph" | "Input" | "Joystick" | "Map" | "PeriodChart" | "ProgressBar" | "Select" | "Slider" | "Switch" | "Table" | "Tabs" | "TextField" | "VideoViewer" | "Widget" | "WidgetWiFi" | "WidgetDeviceInfo" | "WidgetFileSystem" | "WidgetStackInfo";
|
|
30
|
-
properties: IAccordionProps | IButtonProps | IColorPickerProps | IDividerProps | IFileAttachProps | IGraphProps | IInputProps | IJoystickProps | IMapProps | IPeriodChartProps | IProgressBarProps | ISelectProps | ISliderProps | ISwitchProps | ITableProps<object> | ITabsProps | ITextFieldProps | IWidgetProps | IWidgetWiFiProps | IWidgetDeviceInfoProps | IWidgetFileSystemProps | IWidgetStackInfoProps;
|
|
29
|
+
type: "Accordion" | "Button" | "ColorPicker" | "Divider" | "FileAttach" | "Graph" | "Input" | "Joystick" | "Map" | "PeriodChart" | "ProgressBar" | "Select" | "Slider" | "Switch" | "Table" | "Tabs" | "TextField" | "VideoViewer" | "Widget" | "WidgetWiFi" | "WidgetDeviceInfo" | "WidgetFileSystem" | "WidgetStackInfo" | "WidgetRGBStrip";
|
|
30
|
+
properties: IAccordionProps | IButtonProps | IColorPickerProps | IDividerProps | IFileAttachProps | IGraphProps | IInputProps | IJoystickProps | IMapProps | IPeriodChartProps | IProgressBarProps | ISelectProps | ISliderProps | ISwitchProps | ITableProps<object> | ITabsProps | ITextFieldProps | IWidgetProps | IWidgetWiFiProps | IWidgetDeviceInfoProps | IWidgetFileSystemProps | IWidgetStackInfoProps | IWidgetRGBStripProps;
|
|
31
31
|
position: Required<Position>;
|
|
32
32
|
parentId: string;
|
|
33
33
|
eventHandler?: IUIComponentHandler;
|
|
@@ -632,3 +632,26 @@ export interface IWidgetStackInfoProps {
|
|
|
632
632
|
persistKey?: string;
|
|
633
633
|
onRefresh?: () => void;
|
|
634
634
|
}
|
|
635
|
+
export interface IWidgetRGBStripConfig {
|
|
636
|
+
Color: number[];
|
|
637
|
+
Effect: number;
|
|
638
|
+
Speed: number;
|
|
639
|
+
Count: number;
|
|
640
|
+
Model: number;
|
|
641
|
+
Order: number;
|
|
642
|
+
MaxCount: number;
|
|
643
|
+
}
|
|
644
|
+
export interface IWidgetRGBStripProps {
|
|
645
|
+
id?: string;
|
|
646
|
+
wrapperClass?: string;
|
|
647
|
+
componentClass?: string;
|
|
648
|
+
label?: {
|
|
649
|
+
name?: string;
|
|
650
|
+
};
|
|
651
|
+
value?: Partial<IWidgetRGBStripConfig>;
|
|
652
|
+
keys?: Partial<Record<keyof IWidgetRGBStripConfig, string>>;
|
|
653
|
+
collapsed?: boolean;
|
|
654
|
+
persistKey?: string;
|
|
655
|
+
onChange?: (field: "Color" | "Effect" | "Speed", value: number | number[]) => void;
|
|
656
|
+
onSave?: (hardware: Pick<IWidgetRGBStripConfig, "Count" | "Model" | "Order">) => void;
|
|
657
|
+
}
|