poe-svelte-ui-lib 1.24.2 → 1.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Modal.svelte
CHANGED
|
@@ -52,7 +52,11 @@
|
|
|
52
52
|
|
|
53
53
|
const handleClickOutside = (e: MouseEvent) => {
|
|
54
54
|
const target = e.target as HTMLElement
|
|
55
|
-
|
|
55
|
+
/* [data-ui-portal] — элементы вроде выпадающего списка Select физически вынесены в
|
|
56
|
+
document.body (портал, чтобы их не обрезал overflow родителя), т.е. DOM-предок у них —
|
|
57
|
+
не [data-modal], а <body>. Без этой проверки клик по такому элементу внутри модалки
|
|
58
|
+
считался бы кликом "снаружи" и сразу закрывал бы её. */
|
|
59
|
+
if (isTopmost && !target.closest("[data-modal]") && !target.closest("[data-ui-portal]")) {
|
|
56
60
|
isOpen = false
|
|
57
61
|
onCancel()
|
|
58
62
|
}
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
/* Список вариантов выносится в document.body (портал), чтобы его не обрезал
|
|
17
17
|
overflow-hidden/overflow-auto ближайшего скроллящегося предка (например,
|
|
18
18
|
панель свойств конструктора). Позиция считается от dropdownElement и
|
|
19
|
-
обновляется, пока список открыт, при скролле/резайзе где угодно на странице.
|
|
19
|
+
обновляется, пока список открыт, при скролле/резайзе где угодно на странице.
|
|
20
|
+
Атрибут data-ui-portal на контейнере — чтобы Modal.svelte не принимал клик по
|
|
21
|
+
опции (физически лежащей вне [data-modal]) за клик "снаружи" и не закрывался. */
|
|
20
22
|
function portal(node: HTMLElement) {
|
|
21
23
|
document.body.appendChild(node)
|
|
22
24
|
return {
|
|
@@ -174,6 +176,7 @@
|
|
|
174
176
|
<div
|
|
175
177
|
use:portal
|
|
176
178
|
bind:this={portalElement}
|
|
179
|
+
data-ui-portal
|
|
177
180
|
class="fixed z-[9999] mt-1"
|
|
178
181
|
style="top: {anchorRect?.bottom ?? 0}px; left: {anchorRect?.left ?? 0}px; width: {anchorRect?.width ?? 0}px;"
|
|
179
182
|
transition:slide={{ duration: 150 }}
|
|
@@ -262,6 +265,7 @@
|
|
|
262
265
|
<div
|
|
263
266
|
use:portal
|
|
264
267
|
bind:this={portalElement}
|
|
268
|
+
data-ui-portal
|
|
265
269
|
class="fixed z-[9999] mt-1 select-none"
|
|
266
270
|
style="top: {anchorRect?.bottom ?? 0}px; left: {anchorRect?.left ?? 0}px; width: {anchorRect?.width ?? 0}px;"
|
|
267
271
|
transition:slide={{ duration: 150 }}
|
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
<script lang="ts">
|
|
4
4
|
import { T } from "../locales/i18n"
|
|
5
5
|
import * as UI from ".."
|
|
6
|
-
import { updateProperty, type UIComponent, type IWidgetDeviceInfoProps } from "../types"
|
|
6
|
+
import { updateProperty, type UIComponent, type IWidgetDeviceInfoProps, type IWidgetDeviceInfoConfig, type IOption } from "../types"
|
|
7
7
|
import WidgetAccentPicker from "../WidgetAccentPicker.svelte"
|
|
8
8
|
import PropsGroup from "../PropsGroup.svelte"
|
|
9
|
+
import { optionsStore } from "../options"
|
|
10
|
+
|
|
11
|
+
const DEVICE_INFO_KEYS: (keyof IWidgetDeviceInfoConfig)[] = ["DevSN", "DevID", "DevFW", "RunCnt", "DevName", "HostName", "WebUser", "WebPsw"]
|
|
9
12
|
|
|
10
13
|
const { component, onPropertyChange } = $props<{
|
|
11
14
|
component: UIComponent & { properties: Partial<IWidgetDeviceInfoProps> }
|
|
@@ -13,14 +16,52 @@
|
|
|
13
16
|
}>()
|
|
14
17
|
</script>
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
{#snippet WidgetKeys()}
|
|
20
|
+
{#each DEVICE_INFO_KEYS as key (key)}
|
|
21
|
+
<UI.Input
|
|
22
|
+
label={{ name: key }}
|
|
23
|
+
value={component.properties.keys?.[key] ?? key}
|
|
24
|
+
onUpdate={(value) => updateProperty(`keys.${key}`, value as string, component, onPropertyChange)}
|
|
25
|
+
/>
|
|
26
|
+
{/each}
|
|
27
|
+
{/snippet}
|
|
28
|
+
|
|
29
|
+
{#snippet WidgetRestartCommand()}
|
|
30
|
+
<UI.Select
|
|
31
|
+
label={{ name: "Заголовок пакета" }}
|
|
32
|
+
type="buttons"
|
|
33
|
+
options={$optionsStore.HEADER_OPTIONS}
|
|
34
|
+
value={$optionsStore.HEADER_OPTIONS.find((o) => o.value === (component.properties.restartCommand?.header || "SET"))}
|
|
35
|
+
onUpdate={(option) => updateProperty("restartCommand.header", (option as IOption<string>).value as string, component, onPropertyChange)}
|
|
21
36
|
/>
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
<UI.Input
|
|
38
|
+
label={{ name: "Аргумент" }}
|
|
39
|
+
value={component.properties.restartCommand?.argument ?? "Restart"}
|
|
40
|
+
onUpdate={(value) => updateProperty("restartCommand.argument", value as string, component, onPropertyChange)}
|
|
25
41
|
/>
|
|
26
|
-
|
|
42
|
+
{/snippet}
|
|
43
|
+
|
|
44
|
+
<div class="flex flex-col gap-2">
|
|
45
|
+
<PropsGroup label={$T("constructor.props.group.general")}>
|
|
46
|
+
<UI.Input
|
|
47
|
+
label={{ name: "Заголовок" }}
|
|
48
|
+
value={component.properties.label?.name}
|
|
49
|
+
onUpdate={(value) => updateProperty("label.name", value as string, component, onPropertyChange)}
|
|
50
|
+
/>
|
|
51
|
+
<UI.Input
|
|
52
|
+
label={{ name: "Префикс группы (ID)" }}
|
|
53
|
+
value={component.properties.id}
|
|
54
|
+
onUpdate={(value) => updateProperty("id", value as string, component, onPropertyChange)}
|
|
55
|
+
/>
|
|
56
|
+
<WidgetAccentPicker
|
|
57
|
+
value={component.properties.componentClass ?? ""}
|
|
58
|
+
onUpdate={(value) => updateProperty("componentClass", value, component, onPropertyChange)}
|
|
59
|
+
/>
|
|
60
|
+
</PropsGroup>
|
|
61
|
+
<PropsGroup label="Ключи устройства">
|
|
62
|
+
{@render WidgetKeys()}
|
|
63
|
+
</PropsGroup>
|
|
64
|
+
<PropsGroup label="Команда перезагрузки">
|
|
65
|
+
{@render WidgetRestartCommand()}
|
|
66
|
+
</PropsGroup>
|
|
67
|
+
</div>
|
|
@@ -4,9 +4,25 @@
|
|
|
4
4
|
<script lang="ts">
|
|
5
5
|
import { T } from "../locales/i18n"
|
|
6
6
|
import * as UI from ".."
|
|
7
|
-
import { updateProperty, type UIComponent, type IWidgetWiFiProps, type IOption } from "../types"
|
|
7
|
+
import { updateProperty, type UIComponent, type IWidgetWiFiProps, type IWidgetWiFiConfig, type IOption } from "../types"
|
|
8
8
|
import WidgetAccentPicker from "../WidgetAccentPicker.svelte"
|
|
9
9
|
import PropsGroup from "../PropsGroup.svelte"
|
|
10
|
+
import { optionsStore } from "../options"
|
|
11
|
+
|
|
12
|
+
const WIFI_KEYS: (keyof IWidgetWiFiConfig)[] = [
|
|
13
|
+
"WiFiMode",
|
|
14
|
+
"StaSSID",
|
|
15
|
+
"StaPSK",
|
|
16
|
+
"StaticIP",
|
|
17
|
+
"StaIP",
|
|
18
|
+
"StaMS",
|
|
19
|
+
"StaGW",
|
|
20
|
+
"ApSSID",
|
|
21
|
+
"ApPSK",
|
|
22
|
+
"ApIP",
|
|
23
|
+
"ApMS",
|
|
24
|
+
"ApGW",
|
|
25
|
+
]
|
|
10
26
|
|
|
11
27
|
const { component, onPropertyChange } = $props<{
|
|
12
28
|
component: UIComponent & { properties: Partial<IWidgetWiFiProps> }
|
|
@@ -28,6 +44,39 @@
|
|
|
28
44
|
/>
|
|
29
45
|
{/snippet}
|
|
30
46
|
|
|
47
|
+
{#snippet WidgetGroupPrefix()}
|
|
48
|
+
<UI.Input
|
|
49
|
+
label={{ name: "Префикс группы (ID)" }}
|
|
50
|
+
value={component.properties.id}
|
|
51
|
+
onUpdate={(value) => updateProperty("id", value as string, component, onPropertyChange)}
|
|
52
|
+
/>
|
|
53
|
+
{/snippet}
|
|
54
|
+
|
|
55
|
+
{#snippet WidgetKeys()}
|
|
56
|
+
{#each WIFI_KEYS as key (key)}
|
|
57
|
+
<UI.Input
|
|
58
|
+
label={{ name: key }}
|
|
59
|
+
value={component.properties.keys?.[key] ?? key}
|
|
60
|
+
onUpdate={(value) => updateProperty(`keys.${key}`, value as string, component, onPropertyChange)}
|
|
61
|
+
/>
|
|
62
|
+
{/each}
|
|
63
|
+
{/snippet}
|
|
64
|
+
|
|
65
|
+
{#snippet WidgetScanCommand()}
|
|
66
|
+
<UI.Select
|
|
67
|
+
label={{ name: "Заголовок пакета" }}
|
|
68
|
+
type="buttons"
|
|
69
|
+
options={$optionsStore.HEADER_OPTIONS}
|
|
70
|
+
value={$optionsStore.HEADER_OPTIONS.find((o) => o.value === (component.properties.scanCommand?.header || "GET"))}
|
|
71
|
+
onUpdate={(option) => updateProperty("scanCommand.header", (option as IOption<string>).value as string, component, onPropertyChange)}
|
|
72
|
+
/>
|
|
73
|
+
<UI.Input
|
|
74
|
+
label={{ name: "Аргумент" }}
|
|
75
|
+
value={component.properties.scanCommand?.argument ?? "APsList"}
|
|
76
|
+
onUpdate={(value) => updateProperty("scanCommand.argument", value as string, component, onPropertyChange)}
|
|
77
|
+
/>
|
|
78
|
+
{/snippet}
|
|
79
|
+
|
|
31
80
|
{#snippet WidgetAllowedModes()}
|
|
32
81
|
<UI.Select
|
|
33
82
|
label={{ name: "Разрешённые режимы" }}
|
|
@@ -60,10 +109,17 @@
|
|
|
60
109
|
<div class="flex flex-col gap-2">
|
|
61
110
|
<PropsGroup label={$T("constructor.props.group.general")}>
|
|
62
111
|
{@render WidgetLabel()}
|
|
112
|
+
{@render WidgetGroupPrefix()}
|
|
63
113
|
{@render WidgetAllowedModes()}
|
|
64
114
|
{@render WidgetColor()}
|
|
65
115
|
</PropsGroup>
|
|
66
116
|
<PropsGroup label={$T("constructor.props.group.behavior")}>
|
|
67
117
|
{@render WidgetConfirmAP()}
|
|
68
118
|
</PropsGroup>
|
|
119
|
+
<PropsGroup label="Ключи устройства">
|
|
120
|
+
{@render WidgetKeys()}
|
|
121
|
+
</PropsGroup>
|
|
122
|
+
<PropsGroup label="Команда сканирования">
|
|
123
|
+
{@render WidgetScanCommand()}
|
|
124
|
+
</PropsGroup>
|
|
69
125
|
</div>
|
|
@@ -9,6 +9,19 @@ const translations = {
|
|
|
9
9
|
"library.regexp": ["Регулярные выражения", "Regular expressions", "正则表达式"],
|
|
10
10
|
"library.info": ["Инфо", "Info", "信息"],
|
|
11
11
|
"library.retry": ["Повторить", "Retry", "重试"],
|
|
12
|
+
/* Документация компонентов для ⓘ в конструкторе (приложение читает через UI.T) — механизм общий
|
|
13
|
+
для всех типов компонентов, заполнено пока только там, где есть что объяснить (фиксированная
|
|
14
|
+
прошивочная конвенция у смарт-виджетов) */
|
|
15
|
+
"component.info.WidgetWiFi": [
|
|
16
|
+
"📶 Виджет работает с фиксированным набором полей группы `CFG` на устройстве — привязка переменных вручную не нужна.\n\n**📡 Режим клиента (STA)**\n- `WiFiMode`, `StaSSID`, `StaPSK`\n- `StaticIP`, `StaIP`, `StaMS`, `StaGW`\n\n**📡 Точка доступа (AP)**\n- `ApSSID`, `ApPSK`\n- `ApIP`, `ApMS`, `ApGW`\n\n🔍 Сканирование сетей отправляет `Header: GET`, `Argument: APsList` — ответ устройства ожидается в поле `CFG.StaSSID-S`.\n\n⚙️ Если на устройстве используется другое имя группы вместо `CFG`, поменяйте префикс (ID) в настройках виджета.",
|
|
17
|
+
"📶 The widget works with a fixed set of fields in the device's `CFG` group — no manual variable binding needed.\n\n**📡 Client mode (STA)**\n- `WiFiMode`, `StaSSID`, `StaPSK`\n- `StaticIP`, `StaIP`, `StaMS`, `StaGW`\n\n**📡 Access point (AP)**\n- `ApSSID`, `ApPSK`\n- `ApIP`, `ApMS`, `ApGW`\n\n🔍 Network scanning sends `Header: GET`, `Argument: APsList` — the device response is expected in the `CFG.StaSSID-S` field.\n\n⚙️ If the device uses a different group name instead of `CFG`, change the prefix (ID) in the widget settings.",
|
|
18
|
+
"📶 该组件使用设备 `CFG` 分组下的一组固定字段——无需手动绑定变量。\n\n**📡 客户端模式(STA)**\n- `WiFiMode`、`StaSSID`、`StaPSK`\n- `StaticIP`、`StaIP`、`StaMS`、`StaGW`\n\n**📡 接入点(AP)**\n- `ApSSID`、`ApPSK`\n- `ApIP`、`ApMS`、`ApGW`\n\n🔍 扫描网络会发送 `Header: GET`、`Argument: APsList`——设备应在 `CFG.StaSSID-S` 字段中返回结果。\n\n⚙️ 如果设备使用的分组名称不是 `CFG`,请在组件设置中修改前缀(ID)。",
|
|
19
|
+
],
|
|
20
|
+
"component.info.WidgetDeviceInfo": [
|
|
21
|
+
"ℹ️ Виджет работает с фиксированным набором полей группы `CFG` на устройстве — привязка переменных вручную не нужна.\n\n**🔒 Только чтение**\n- `DevSN`, `DevID`, `DevFW`, `RunCnt`\n\n**✏️ Редактируемые** (сохраняются одним пакетом по кнопке «Сохранить»)\n- `DevName`, `HostName`, `WebUser`, `WebPsw`\n\n🔌 Кнопка «Перезагрузить» отправляет `Argument: Restart`.\n\n⚙️ Если на устройстве используется другое имя группы вместо `CFG`, поменяйте префикс (ID) в настройках виджета.",
|
|
22
|
+
"ℹ️ The widget works with a fixed set of fields in the device's `CFG` group — no manual variable binding needed.\n\n**🔒 Read-only**\n- `DevSN`, `DevID`, `DevFW`, `RunCnt`\n\n**✏️ Editable** (saved as one packet via the \"Save\" button)\n- `DevName`, `HostName`, `WebUser`, `WebPsw`\n\n🔌 The \"Restart\" button sends `Argument: Restart`.\n\n⚙️ If the device uses a different group name instead of `CFG`, change the prefix (ID) in the widget settings.",
|
|
23
|
+
"ℹ️ 该组件使用设备 `CFG` 分组下的一组固定字段——无需手动绑定变量。\n\n**🔒 只读字段**\n- `DevSN`、`DevID`、`DevFW`、`RunCnt`\n\n**✏️ 可编辑字段**(通过「保存」按钮一次性发送)\n- `DevName`、`HostName`、`WebUser`、`WebPsw`\n\n🔌 「重启」按钮会发送 `Argument: Restart`。\n\n⚙️ 如果设备使用的分组名称不是 `CFG`,请在组件设置中修改前缀(ID)。",
|
|
24
|
+
],
|
|
12
25
|
/* Опции редактора свойств */
|
|
13
26
|
"constructor.props.action.update": ["Обновить", "Update", "更新"],
|
|
14
27
|
"constructor.props.action.save": ["Сохранить", "Save", "保存"],
|
package/dist/types.d.ts
CHANGED
|
@@ -514,6 +514,11 @@ export interface IWidgetWiFiProps {
|
|
|
514
514
|
name?: string;
|
|
515
515
|
};
|
|
516
516
|
value?: IWidgetWiFiConfig;
|
|
517
|
+
keys?: Partial<Record<keyof IWidgetWiFiConfig, string>>;
|
|
518
|
+
scanCommand?: {
|
|
519
|
+
header?: string;
|
|
520
|
+
argument?: string;
|
|
521
|
+
};
|
|
517
522
|
allowedModes?: number[];
|
|
518
523
|
confirmOnAP?: boolean;
|
|
519
524
|
collapsed?: boolean;
|
|
@@ -543,6 +548,11 @@ export interface IWidgetDeviceInfoProps {
|
|
|
543
548
|
name?: string;
|
|
544
549
|
};
|
|
545
550
|
value?: IWidgetDeviceInfoConfig;
|
|
551
|
+
keys?: Partial<Record<keyof IWidgetDeviceInfoConfig, string>>;
|
|
552
|
+
restartCommand?: {
|
|
553
|
+
header?: string;
|
|
554
|
+
argument?: string;
|
|
555
|
+
};
|
|
546
556
|
collapsed?: boolean;
|
|
547
557
|
persistKey?: string;
|
|
548
558
|
onSave?: (info: IWidgetDeviceInfoConfig) => void;
|