poe-svelte-ui-lib 1.20.6 → 1.22.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/WidgetAccentPicker.svelte +27 -0
- package/dist/WidgetAccentPicker.svelte.d.ts +8 -0
- package/dist/WidgetDeviceInfo/CpuIcon.svelte +9 -0
- package/dist/WidgetDeviceInfo/CpuIcon.svelte.d.ts +18 -0
- package/dist/WidgetDeviceInfo/README.md +82 -0
- package/dist/WidgetDeviceInfo/RestartIcon.svelte +12 -0
- package/dist/WidgetDeviceInfo/RestartIcon.svelte.d.ts +18 -0
- package/dist/WidgetDeviceInfo/WidgetDeviceInfo.svelte +96 -0
- package/dist/WidgetDeviceInfo/WidgetDeviceInfo.svelte.d.ts +4 -0
- package/dist/WidgetDeviceInfo/WidgetDeviceInfoProps.svelte +26 -0
- package/dist/WidgetDeviceInfo/WidgetDeviceInfoProps.svelte.d.ts +14 -0
- package/dist/WidgetHeader.svelte +45 -0
- package/dist/WidgetHeader.svelte.d.ts +10 -0
- package/dist/WidgetWiFi/LoaderIcon.svelte +5 -0
- package/dist/WidgetWiFi/LoaderIcon.svelte.d.ts +18 -0
- package/dist/WidgetWiFi/README.md +84 -0
- package/dist/WidgetWiFi/SearchIcon.svelte +12 -0
- package/dist/WidgetWiFi/SearchIcon.svelte.d.ts +18 -0
- package/dist/WidgetWiFi/WarningIcon.svelte +6 -0
- package/dist/WidgetWiFi/WarningIcon.svelte.d.ts +18 -0
- package/dist/WidgetWiFi/WidgetWiFi.svelte +215 -0
- package/dist/WidgetWiFi/WidgetWiFi.svelte.d.ts +4 -0
- package/dist/WidgetWiFi/WidgetWiFiProps.svelte +65 -0
- package/dist/WidgetWiFi/WidgetWiFiProps.svelte.d.ts +14 -0
- package/dist/WidgetWiFi/WifiIcon.svelte +6 -0
- package/dist/WidgetWiFi/WifiIcon.svelte.d.ts +18 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -0
- package/dist/options.js +9 -3
- package/dist/types.d.ts +56 -4
- package/dist/widgetAccent.d.ts +1 -0
- package/dist/widgetAccent.js +16 -0
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!-- $lib/WidgetAccentPicker.svelte — переиспользуемый пункт панели пропсов "Мастер-цвет" для
|
|
2
|
+
смарт-виджетов конструктора (WidgetWiFi, WidgetDeviceInfo и далее). Один UI на все виджеты,
|
|
3
|
+
та же палитра, что и у примитивов (optionsStore.COLOR_OPTIONS) — см. widgetAccent.ts. -->
|
|
4
|
+
<script lang="ts">
|
|
5
|
+
import { optionsStore } from "./options"
|
|
6
|
+
import Select from "./Select/Select.svelte"
|
|
7
|
+
import type { IOption } from "./types"
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
value = "",
|
|
11
|
+
onUpdate,
|
|
12
|
+
wrapperClass = "h-14",
|
|
13
|
+
}: {
|
|
14
|
+
value?: string
|
|
15
|
+
onUpdate: (value: string) => void
|
|
16
|
+
wrapperClass?: string
|
|
17
|
+
} = $props()
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<Select
|
|
21
|
+
{wrapperClass}
|
|
22
|
+
label={{ name: "Мастер-цвет" }}
|
|
23
|
+
type="buttons"
|
|
24
|
+
options={$optionsStore.COLOR_OPTIONS}
|
|
25
|
+
value={$optionsStore.COLOR_OPTIONS.find((o) => o.value === value) ?? $optionsStore.COLOR_OPTIONS[0]}
|
|
26
|
+
onUpdate={(option) => onUpdate((option as IOption<string>).value ?? "")}
|
|
27
|
+
/>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
value?: string;
|
|
3
|
+
onUpdate: (value: string) => void;
|
|
4
|
+
wrapperClass?: string;
|
|
5
|
+
};
|
|
6
|
+
declare const WidgetAccentPicker: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type WidgetAccentPicker = ReturnType<typeof WidgetAccentPicker>;
|
|
8
|
+
export default WidgetAccentPicker;
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
5
|
+
><rect x="4" y="4" width="16" height="16" rx="2" /><rect x="9" y="9" width="6" height="6" /><path
|
|
6
|
+
d="M9 1v3M15 1v3M9 20v3M15 20v3M20 9h3M20 14h3M1 9h3M1 14h3"
|
|
7
|
+
/></g
|
|
8
|
+
></svg
|
|
9
|
+
>
|
|
@@ -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 CpuIcon: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type CpuIcon = InstanceType<typeof CpuIcon>;
|
|
18
|
+
export default CpuIcon;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# WidgetDeviceInfo Component
|
|
2
|
+
|
|
3
|
+
## Описание
|
|
4
|
+
|
|
5
|
+
Смарт-виджет информации об устройстве. Как и `WidgetWiFi`, не привязывается к одной произвольной
|
|
6
|
+
переменной устройства — работает с фиксированным набором полей, соответствующих реальной
|
|
7
|
+
прошивочной конвенции (`main_config.c`, группа `CFG`, ProdFactory-ESP): `DevSN`, `DevID`, `DevFW`,
|
|
8
|
+
`RunCnt` — только чтение; `DevName`, `HostName`, `WebUser`, `WebPsw` — редактируются локально и
|
|
9
|
+
сохраняются одним пакетом по кнопке "Сохранить". Отдельная кнопка "Перезагрузить" рядом с
|
|
10
|
+
заголовком вызывает `onRestart` немедленно (не входит в пакет сохранения полей). Компонент
|
|
11
|
+
ничего не знает про DeviceStore/WebSocket — только `value`/`onSave`/`onRestart`.
|
|
12
|
+
|
|
13
|
+
## Пропсы
|
|
14
|
+
|
|
15
|
+
| Название | Тип | По умолчанию | Описание |
|
|
16
|
+
| ---------------- | ------------------------------------------ | --------------------------------------- | ----------------------------------------------------------------------- |
|
|
17
|
+
| `wrapperClass` | `string` | `""` | CSS-классы для обёртки компонента |
|
|
18
|
+
| `componentClass` | `string` | `""` | "Мастер-цвет" виджета — роль вида `"bg-red"` (см. `optionsStore.COLOR_OPTIONS`) |
|
|
19
|
+
| `label` | `{ name?: string }` | `{ name: "Информация об устройстве" }` | Заголовок карточки |
|
|
20
|
+
| `value` | `IWidgetDeviceInfoConfig` | внутренний дефолт | Текущая информация об устройстве |
|
|
21
|
+
| `onSave` | `(info: IWidgetDeviceInfoConfig) => void` | `undefined` | Срабатывает при нажатии "Сохранить" — редактируемые поля одним пакетом |
|
|
22
|
+
| `onRestart` | `() => void` | `undefined` | Срабатывает при нажатии "Перезагрузить" |
|
|
23
|
+
|
|
24
|
+
### IWidgetDeviceInfoConfig
|
|
25
|
+
|
|
26
|
+
| Поле | Тип | Только чтение | Описание |
|
|
27
|
+
| ----------- | -------- | :-----------: | ------------------------------------ |
|
|
28
|
+
| `DevSN` | `string` | да | Серийный номер устройства |
|
|
29
|
+
| `DevID` | `string` | да | ID устройства |
|
|
30
|
+
| `DevFW` | `number` | да | Версия ПО |
|
|
31
|
+
| `RunCnt` | `number` | да | Счётчик запусков |
|
|
32
|
+
| `DevName` | `string` | нет | Имя устройства |
|
|
33
|
+
| `HostName` | `string` | нет | Имя хоста |
|
|
34
|
+
| `WebUser` | `string` | нет | Имя пользователя Web-панели |
|
|
35
|
+
| `WebPsw` | `string` | нет | Пароль Web-панели |
|
|
36
|
+
|
|
37
|
+
## Примеры
|
|
38
|
+
|
|
39
|
+
```svelte
|
|
40
|
+
<script>
|
|
41
|
+
import * as UI from "poe-svelte-ui-lib"
|
|
42
|
+
|
|
43
|
+
const handleSave = (info) => {
|
|
44
|
+
console.log("Отправка настроек одним пакетом:", info)
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<UI.WidgetDeviceInfo
|
|
49
|
+
value={{
|
|
50
|
+
DevSN: "00.00.000-00_0000AC670000B22B00007428:DBC5",
|
|
51
|
+
DevID: "20.00.000-01",
|
|
52
|
+
DevFW: 1.4,
|
|
53
|
+
RunCnt: 128,
|
|
54
|
+
DevName: "SYP-Device-01",
|
|
55
|
+
HostName: "syp-device-01",
|
|
56
|
+
WebUser: "admin",
|
|
57
|
+
WebPsw: "",
|
|
58
|
+
}}
|
|
59
|
+
onSave={handleSave}
|
|
60
|
+
onRestart={() => console.log("Перезагрузка устройства")}
|
|
61
|
+
/>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Конструктор свойств (WidgetDeviceInfoProps.svelte)
|
|
65
|
+
|
|
66
|
+
### Пропсы конструктора
|
|
67
|
+
|
|
68
|
+
| Название | Тип | Описание |
|
|
69
|
+
| ------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------- |
|
|
70
|
+
| `component` | `UIComponent & { properties: Partial<IWidgetDeviceInfoProps> }` | Объект компонента с его свойствами |
|
|
71
|
+
| `onPropertyChange` | `(updates: Partial<{ properties?: string \| object; name?: string; access?: string }>) => void` | Коллбэк для обновления свойств компонента |
|
|
72
|
+
|
|
73
|
+
Панель минимальна: заголовок и мастер-цвет (через общий `WidgetAccentPicker.svelte`) — полей
|
|
74
|
+
для привязки нет, они фиксированы прошивочной конвенцией.
|
|
75
|
+
|
|
76
|
+
## Заметки
|
|
77
|
+
|
|
78
|
+
- Серийный номер устройства обычно длинный (например
|
|
79
|
+
`00.00.000-00_0000AC670000B22B00007428:DBC5`) — в разметке занимает отдельную строку, чтобы не
|
|
80
|
+
обрезаться в узкой сетке.
|
|
81
|
+
- `WebPsw` в реальном устройстве исторически передаётся обычным текстом; в виджете сделан
|
|
82
|
+
`type="password"` для консистентности с остальным обликом конструктора.
|
|
@@ -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="M21 12a9 9 0 1 1-2.64-6.36M21 3v6h-6"
|
|
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 RestartIcon: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type RestartIcon = InstanceType<typeof RestartIcon>;
|
|
18
|
+
export default RestartIcon;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<!-- $lib/WidgetDeviceInfo/WidgetDeviceInfo.svelte — смарт-виджет информации об устройстве.
|
|
2
|
+
Фиксированный набор полей (см. IWidgetDeviceInfoConfig в types.ts), соответствует реальной
|
|
3
|
+
прошивочной конвенции (main_config.c, группа "CFG", ProdFactory-ESP):
|
|
4
|
+
DevSN/DevID/DevFW/RunCnt — только чтение; DevName/HostName/WebUser/WebPsw — сохраняются
|
|
5
|
+
одним пакетом. Ничего не знает про DeviceStore/WebSocket — value/onSave/onRestart. -->
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import { slide } from "svelte/transition"
|
|
8
|
+
import { twMerge } from "tailwind-merge"
|
|
9
|
+
import * as UI from ".."
|
|
10
|
+
import type { IWidgetDeviceInfoConfig, IWidgetDeviceInfoProps } from "../types"
|
|
11
|
+
import CpuIcon from "./CpuIcon.svelte"
|
|
12
|
+
import RestartIcon from "./RestartIcon.svelte"
|
|
13
|
+
import WidgetHeader from "../WidgetHeader.svelte"
|
|
14
|
+
import { widgetAccentStyle } from "../widgetAccent"
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
wrapperClass = "",
|
|
18
|
+
componentClass = "",
|
|
19
|
+
label = { name: "Информация об устройстве" },
|
|
20
|
+
value = $bindable(),
|
|
21
|
+
onSave = () => {},
|
|
22
|
+
onRestart = () => {},
|
|
23
|
+
}: IWidgetDeviceInfoProps = $props()
|
|
24
|
+
|
|
25
|
+
let accentStyle = $derived(widgetAccentStyle(componentClass ?? ""))
|
|
26
|
+
|
|
27
|
+
let info: IWidgetDeviceInfoConfig = $state(
|
|
28
|
+
value ?? {
|
|
29
|
+
DevSN: "",
|
|
30
|
+
DevID: "",
|
|
31
|
+
DevFW: 0,
|
|
32
|
+
RunCnt: 0,
|
|
33
|
+
DevName: "",
|
|
34
|
+
HostName: "",
|
|
35
|
+
WebUser: "",
|
|
36
|
+
WebPsw: "",
|
|
37
|
+
},
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
/* Сворачивание тела виджета по клику на значок/заголовок (как аккордеон) */
|
|
41
|
+
let collapsed = $state(false)
|
|
42
|
+
|
|
43
|
+
/* Поля редактируются локально (как Argument: NoSend в реальной GUI) — отправка одним пакетом по кнопке */
|
|
44
|
+
let savedSnapshot = $state(JSON.stringify(info))
|
|
45
|
+
let isDirty = $derived(JSON.stringify(info) !== savedSnapshot)
|
|
46
|
+
const save = () => {
|
|
47
|
+
onSave?.($state.snapshot(info))
|
|
48
|
+
savedSnapshot = JSON.stringify(info)
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<div
|
|
53
|
+
class={twMerge("flex w-full max-w-3xl flex-col gap-4 rounded-2xl border border-(--hairline-color) bg-(--container-color) p-4", wrapperClass)}
|
|
54
|
+
style={accentStyle}
|
|
55
|
+
>
|
|
56
|
+
<!-- Заголовок -->
|
|
57
|
+
<WidgetHeader icon={CpuIcon} label={label?.name ?? "Информация об устройстве"} bind:collapsed>
|
|
58
|
+
{#snippet right()}
|
|
59
|
+
<UI.Button wrapperClass="w-auto" componentClass="bg-transparent px-3" content={{ icon: RestartIcon, name: "Перезагрузить" }} onClick={onRestart} />
|
|
60
|
+
{/snippet}
|
|
61
|
+
</WidgetHeader>
|
|
62
|
+
|
|
63
|
+
{#if !collapsed}
|
|
64
|
+
<div class="flex flex-col gap-4" transition:slide={{ duration: 150 }}>
|
|
65
|
+
<!-- Только чтение -->
|
|
66
|
+
<div class="flex flex-col gap-2 rounded-[10px] border border-(--hairline-color) bg-(--back-color) p-3">
|
|
67
|
+
<!-- Серийник бывает длинным (напр. 00.00.000-00_0000AC670000B22B00007428:DBC5) — своя строка, чтобы не резался -->
|
|
68
|
+
<UI.Input label={{ name: "Серийный номер" }} value={info.DevSN} readonly help={{ copyButton: true }} />
|
|
69
|
+
<div class="grid grid-cols-1 gap-2 sm:grid-cols-3">
|
|
70
|
+
<UI.Input label={{ name: "ID устройства" }} value={info.DevID} readonly />
|
|
71
|
+
<UI.Input label={{ name: "Версия ПО" }} value={info.DevFW} readonly />
|
|
72
|
+
<UI.Input label={{ name: "Запусков" }} value={info.RunCnt} readonly />
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<!-- Редактируемые настройки -->
|
|
77
|
+
<div class="flex flex-col gap-3 rounded-[10px] border border-(--hairline-color) bg-(--back-color) p-3">
|
|
78
|
+
<span class="text-[12px] font-semibold text-(--muted-color)">Настройки</span>
|
|
79
|
+
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
80
|
+
<UI.Input label={{ name: "Имя устройства" }} bind:value={info.DevName} maxlength={26} />
|
|
81
|
+
<UI.Input label={{ name: "Имя хоста" }} bind:value={info.HostName} maxlength={32} />
|
|
82
|
+
<UI.Input label={{ name: "Пользователь Web" }} bind:value={info.WebUser} maxlength={16} />
|
|
83
|
+
<UI.Input type="password" label={{ name: "Пароль Web" }} bind:value={info.WebPsw} maxlength={16} help={{ autocomplete: "off" }} />
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<!-- Сохранение -->
|
|
88
|
+
<div class="flex flex-col items-center gap-2">
|
|
89
|
+
{#if isDirty}
|
|
90
|
+
<span class="text-[12px] text-(--muted-color)" transition:slide={{ duration: 100 }}>Есть несохранённые изменения</span>
|
|
91
|
+
{/if}
|
|
92
|
+
<UI.Button wrapperClass="w-40" componentClass="bg-green" content={{ name: "Сохранить" }} disabled={!isDirty} onClick={save} />
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
{/if}
|
|
96
|
+
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!-- $lib/WidgetDeviceInfo/WidgetDeviceInfoProps.svelte — панель пропсов конструктора
|
|
2
|
+
для WidgetDeviceInfo. Виджет простой, полей мало — панель тоже простая: заголовок + мастер-цвет. -->
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import * as UI from ".."
|
|
5
|
+
import { updateProperty, type UIComponent, type IWidgetDeviceInfoProps } from "../types"
|
|
6
|
+
import WidgetAccentPicker from "../WidgetAccentPicker.svelte"
|
|
7
|
+
|
|
8
|
+
const { component, onPropertyChange } = $props<{
|
|
9
|
+
component: UIComponent & { properties: Partial<IWidgetDeviceInfoProps> }
|
|
10
|
+
onPropertyChange: (updates: Partial<{ properties?: string | object; name?: string; access?: string }>) => void
|
|
11
|
+
}>()
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<div class="flex w-full flex-col gap-3">
|
|
15
|
+
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
16
|
+
<UI.Input
|
|
17
|
+
label={{ name: "Заголовок" }}
|
|
18
|
+
value={component.properties.label?.name}
|
|
19
|
+
onUpdate={(value) => updateProperty("label.name", value as string, component, onPropertyChange)}
|
|
20
|
+
/>
|
|
21
|
+
<WidgetAccentPicker
|
|
22
|
+
value={component.properties.componentClass ?? ""}
|
|
23
|
+
onUpdate={(value) => updateProperty("componentClass", value, component, onPropertyChange)}
|
|
24
|
+
/>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type UIComponent, type IWidgetDeviceInfoProps } from "../types";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
component: UIComponent & {
|
|
4
|
+
properties: Partial<IWidgetDeviceInfoProps>;
|
|
5
|
+
};
|
|
6
|
+
onPropertyChange: (updates: Partial<{
|
|
7
|
+
properties?: string | object;
|
|
8
|
+
name?: string;
|
|
9
|
+
access?: string;
|
|
10
|
+
}>) => void;
|
|
11
|
+
};
|
|
12
|
+
declare const WidgetDeviceInfoProps: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
13
|
+
type WidgetDeviceInfoProps = ReturnType<typeof WidgetDeviceInfoProps>;
|
|
14
|
+
export default WidgetDeviceInfoProps;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<!-- $lib/WidgetHeader.svelte — общая шапка смарт-виджетов конструктора: значок + заголовок
|
|
2
|
+
сворачивают/разворачивают тело виджета по клику (как аккордеон), справа — снипет с любым
|
|
3
|
+
содержимым шапки конкретного виджета (переключатель режима, кнопка "Перезагрузить" и т.п.),
|
|
4
|
+
клик по нему сворачивание не триггерит. -->
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import type { Component, Snippet } from "svelte"
|
|
7
|
+
import ArrowIcon from "./libIcons/ArrowIcon.svelte"
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
icon,
|
|
11
|
+
label,
|
|
12
|
+
collapsed = $bindable(false),
|
|
13
|
+
right,
|
|
14
|
+
}: {
|
|
15
|
+
icon: Component
|
|
16
|
+
label: string
|
|
17
|
+
collapsed?: boolean
|
|
18
|
+
right?: Snippet
|
|
19
|
+
} = $props()
|
|
20
|
+
|
|
21
|
+
const Icon = $derived(icon)
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<div class="flex flex-wrap items-center justify-between gap-3">
|
|
25
|
+
<button
|
|
26
|
+
type="button"
|
|
27
|
+
class="flex min-w-0 cursor-pointer items-center gap-2 border-none bg-transparent p-0 text-left"
|
|
28
|
+
onclick={() => (collapsed = !collapsed)}
|
|
29
|
+
aria-expanded={!collapsed}
|
|
30
|
+
>
|
|
31
|
+
<span class="flex size-8 shrink-0 items-center justify-center rounded-xl bg-(--accent-soft) text-(--accent-color)">
|
|
32
|
+
<Icon />
|
|
33
|
+
</span>
|
|
34
|
+
<h3 class="min-w-0 truncate">{label}</h3>
|
|
35
|
+
<span
|
|
36
|
+
class="flex size-4 shrink-0 items-center justify-center text-(--faint-color) transition-transform duration-150 [&_svg]:h-full [&_svg]:w-full"
|
|
37
|
+
style="transform: rotate({collapsed ? 0 : 90}deg)"
|
|
38
|
+
>
|
|
39
|
+
<ArrowIcon />
|
|
40
|
+
</span>
|
|
41
|
+
</button>
|
|
42
|
+
{#if right}
|
|
43
|
+
{@render right()}
|
|
44
|
+
{/if}
|
|
45
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Component, Snippet } from "svelte";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
icon: Component;
|
|
4
|
+
label: string;
|
|
5
|
+
collapsed?: boolean;
|
|
6
|
+
right?: Snippet;
|
|
7
|
+
};
|
|
8
|
+
declare const WidgetHeader: Component<$$ComponentProps, {}, "collapsed">;
|
|
9
|
+
type WidgetHeader = ReturnType<typeof WidgetHeader>;
|
|
10
|
+
export default WidgetHeader;
|
|
@@ -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 LoaderIcon: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type LoaderIcon = InstanceType<typeof LoaderIcon>;
|
|
18
|
+
export default LoaderIcon;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# WidgetWiFi Component
|
|
2
|
+
|
|
3
|
+
## Описание
|
|
4
|
+
|
|
5
|
+
Смарт-виджет настройки WiFi (режимы STA / AP / STA+AP). В отличие от примитивов конструктора,
|
|
6
|
+
не привязывается к одной произвольной переменной устройства через Select — работает с
|
|
7
|
+
фиксированным набором полей, соответствующих реальной прошивочной конвенции (`main_config.c`,
|
|
8
|
+
группа `CFG`, ProdFactory-ESP): `WiFiMode`, `StaSSID`, `StaPSK`, `StaticIP`, `StaIP`, `StaMS`,
|
|
9
|
+
`StaGW`, `ApSSID`, `ApPSK`, `ApIP`, `ApMS`, `ApGW`. Компонент ничего не знает про
|
|
10
|
+
DeviceStore/WebSocket — только `value`/`onSave`, вся отправка на стороне вызывающего кода.
|
|
11
|
+
|
|
12
|
+
Поля редактируются локально и отправляются одним пакетом по кнопке "Сохранить". Переход в
|
|
13
|
+
режим AP (отключает STA) подтверждается отдельно — предупреждение появляется в момент нажатия
|
|
14
|
+
"Сохранить", а не при простом переключении сегмент-контрола, чтобы можно было спокойно открыть
|
|
15
|
+
секцию AP и заполнить SSID/пароль до реальной отправки.
|
|
16
|
+
|
|
17
|
+
## Пропсы
|
|
18
|
+
|
|
19
|
+
| Название | Тип | По умолчанию | Описание |
|
|
20
|
+
| ----------------- | ------------------------------------------------------------- | ---------------------- | --------------------------------------------------------------------- |
|
|
21
|
+
| `wrapperClass` | `string` | `""` | CSS-классы для обёртки компонента |
|
|
22
|
+
| `componentClass` | `string` | `""` | "Мастер-цвет" виджета — роль вида `"bg-red"` (см. `optionsStore.COLOR_OPTIONS`); перекрашивает значок, сегмент-контролы и фокус-кольцо у полей |
|
|
23
|
+
| `label` | `{ name?: string }` | `{ name: "WiFi" }` | Заголовок карточки |
|
|
24
|
+
| `value` | `IWidgetWiFiConfig` | внутренний дефолт | Текущая конфигурация WiFi |
|
|
25
|
+
| `allowedModes` | `number[]` | `[1, 2, 3]` | Какие режимы показывать в сегмент-контроле (1=STA, 2=AP, 3=STA+AP) |
|
|
26
|
+
| `confirmOnAP` | `boolean` | `true` | Показывать предупреждение при сохранении чистого режима AP |
|
|
27
|
+
| `onScan` | `() => Promise<{ id: string; name: string; value: string }[]>` | `undefined` | Обработчик сканирования сетей (в проде — `Header: GET, Argument: APsList`) |
|
|
28
|
+
| `onSave` | `(config: IWidgetWiFiConfig) => void` | `undefined` | Срабатывает при нажатии "Сохранить" — конфиг уходит одним пакетом |
|
|
29
|
+
|
|
30
|
+
### IWidgetWiFiConfig
|
|
31
|
+
|
|
32
|
+
| Поле | Тип | Описание |
|
|
33
|
+
| ----------- | -------- | ---------------------------------------------------- |
|
|
34
|
+
| `WiFiMode` | `number` | 0=NULL, 1=STA, 2=AP, 3=STA+AP (как `esp_wifi wifi_mode_t`) |
|
|
35
|
+
| `StaSSID` | `string` | SSID сети в режиме клиента |
|
|
36
|
+
| `StaPSK` | `string` | Пароль сети в режиме клиента |
|
|
37
|
+
| `StaticIP` | `number` | 0=DHCP, 1=Static |
|
|
38
|
+
| `StaIP` | `string` | Статический IP клиента |
|
|
39
|
+
| `StaMS` | `string` | Маска подсети клиента |
|
|
40
|
+
| `StaGW` | `string` | Шлюз клиента |
|
|
41
|
+
| `ApSSID` | `string` | SSID точки доступа |
|
|
42
|
+
| `ApPSK` | `string` | Пароль точки доступа |
|
|
43
|
+
| `ApIP` | `string` | IP адрес точки доступа |
|
|
44
|
+
| `ApMS` | `string` | Маска подсети точки доступа |
|
|
45
|
+
| `ApGW` | `string` | Шлюз точки доступа |
|
|
46
|
+
|
|
47
|
+
## Примеры
|
|
48
|
+
|
|
49
|
+
```svelte
|
|
50
|
+
<script>
|
|
51
|
+
import * as UI from "poe-svelte-ui-lib"
|
|
52
|
+
|
|
53
|
+
const handleScan = async () => {
|
|
54
|
+
/* Header: GET, Argument: APsList */
|
|
55
|
+
return [{ id: "1", name: "MyNetwork", value: "MyNetwork" }]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const handleSave = (config) => {
|
|
59
|
+
console.log("Отправка конфига одним пакетом:", config)
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<UI.WidgetWiFi label={{ name: "WiFi" }} onScan={handleScan} onSave={handleSave} />
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Конструктор свойств (WidgetWiFiProps.svelte)
|
|
67
|
+
|
|
68
|
+
### Пропсы конструктора
|
|
69
|
+
|
|
70
|
+
| Название | Тип | Описание |
|
|
71
|
+
| ------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------- |
|
|
72
|
+
| `component` | `UIComponent & { properties: Partial<IWidgetWiFiProps> }` | Объект компонента с его свойствами |
|
|
73
|
+
| `onPropertyChange` | `(updates: Partial<{ properties?: string \| object; name?: string; access?: string }>) => void` | Коллбэк для обновления свойств компонента |
|
|
74
|
+
|
|
75
|
+
Панель проще, чем у примитивов: не "к какой переменной привязать" (полей несколько, и они
|
|
76
|
+
фиксированы прошивочной конвенцией), а "как виджет себя ведёт" — заголовок, разрешённые режимы,
|
|
77
|
+
подтверждение при AP, мастер-цвет (через общий `WidgetAccentPicker.svelte`).
|
|
78
|
+
|
|
79
|
+
## Заметки
|
|
80
|
+
|
|
81
|
+
- Первый по-настоящему составной (multi-field) тип компонента конструктора — примитивы всегда
|
|
82
|
+
привязаны к одной переменной устройства через `properties.id`.
|
|
83
|
+
- Чтение/батч-запись нескольких ключей `DeviceStore` реализуется на стороне приложения
|
|
84
|
+
(`GUIPreview.svelte`), а не внутри библиотеки.
|
|
@@ -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="1.5"
|
|
10
|
+
d="m14 14l2.5 2.5m-.067 2.025a1.48 1.48 0 1 1 2.092-2.092l3.042 3.042a1.48 1.48 0 1 1-2.092 2.092zM16 9A7 7 0 1 0 2 9a7 7 0 0 0 14 0"
|
|
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 SearchIcon: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type SearchIcon = InstanceType<typeof SearchIcon>;
|
|
18
|
+
export default SearchIcon;
|
|
@@ -0,0 +1,6 @@
|
|
|
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 fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
5
|
+
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0M12 9v4m0 4h.01" /></svg
|
|
6
|
+
>
|
|
@@ -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 WarningIcon: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type WarningIcon = InstanceType<typeof WarningIcon>;
|
|
18
|
+
export default WarningIcon;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
<!-- $lib/WidgetWiFi/WidgetWiFi.svelte — смарт-виджет настройки WiFi (STA/AP) для конструктора.
|
|
2
|
+
В отличие от примитивов не привязывается к одной произвольной переменной устройства —
|
|
3
|
+
работает с фиксированным набором полей (см. IWidgetWiFiConfig в types.ts), которые
|
|
4
|
+
соответствуют реальной прошивочной конвенции (main_config.c, группа "CFG", ProdFactory-ESP):
|
|
5
|
+
WiFiMode / StaSSID/StaPSK/StaticIP/StaIP/StaMS/StaGW / ApSSID/ApPSK/ApIP/ApMS/ApGW.
|
|
6
|
+
Ничего не знает про DeviceStore/WebSocket — value/onSave, вся отправка на стороне вызывающего. -->
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import { slide, fade, scale } from "svelte/transition"
|
|
9
|
+
import { twMerge } from "tailwind-merge"
|
|
10
|
+
import * as UI from ".."
|
|
11
|
+
import type { IWidgetWiFiConfig, IWidgetWiFiProps, IOption } from "../types"
|
|
12
|
+
import WifiIcon from "./WifiIcon.svelte"
|
|
13
|
+
import SearchIcon from "./SearchIcon.svelte"
|
|
14
|
+
import LoaderIcon from "./LoaderIcon.svelte"
|
|
15
|
+
import WarningIcon from "./WarningIcon.svelte"
|
|
16
|
+
import WidgetHeader from "../WidgetHeader.svelte"
|
|
17
|
+
import { widgetAccentStyle } from "../widgetAccent"
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
wrapperClass = "",
|
|
21
|
+
componentClass = "",
|
|
22
|
+
label = { name: "WiFi" },
|
|
23
|
+
value = $bindable(),
|
|
24
|
+
allowedModes = [1, 2, 3],
|
|
25
|
+
confirmOnAP = true,
|
|
26
|
+
onScan,
|
|
27
|
+
onSave = () => {},
|
|
28
|
+
}: IWidgetWiFiProps = $props()
|
|
29
|
+
|
|
30
|
+
/* "Мастер-цвет" виджета — переопределяет --accent-color/--accent-soft на корне, дальше это
|
|
31
|
+
штатный каскад переменных, на котором и так построены компоненты библиотеки: значок, режимы
|
|
32
|
+
STA/AP, фокус-кольцо у полей перекрашиваются вместе, одним источником истины. Кнопка
|
|
33
|
+
"Сохранить" остаётся зелёной осознанно (componentClass="bg-green" сама выставляет --bg-color). */
|
|
34
|
+
let accentStyle = $derived(widgetAccentStyle(componentClass ?? ""))
|
|
35
|
+
|
|
36
|
+
let cfg: IWidgetWiFiConfig = $state(
|
|
37
|
+
value ?? {
|
|
38
|
+
WiFiMode: 1,
|
|
39
|
+
StaSSID: "",
|
|
40
|
+
StaPSK: "",
|
|
41
|
+
StaticIP: 0,
|
|
42
|
+
StaIP: "",
|
|
43
|
+
StaMS: "",
|
|
44
|
+
StaGW: "",
|
|
45
|
+
ApSSID: "",
|
|
46
|
+
ApPSK: "",
|
|
47
|
+
ApIP: "192.168.108.1",
|
|
48
|
+
ApMS: "255.255.255.0",
|
|
49
|
+
ApGW: "192.168.108.1",
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const ALL_MODE_OPTIONS: IOption<number>[] = [
|
|
54
|
+
{ id: "sta", name: "STA", value: 1 },
|
|
55
|
+
{ id: "ap", name: "AP", value: 2 },
|
|
56
|
+
{ id: "apsta", name: "STA + AP", value: 3 },
|
|
57
|
+
]
|
|
58
|
+
let MODE_OPTIONS = $derived(ALL_MODE_OPTIONS.filter((o) => (allowedModes ?? [1, 2, 3]).includes(o.value as number)))
|
|
59
|
+
let selectedMode = $derived(MODE_OPTIONS.find((o) => o.value === cfg.WiFiMode) ?? MODE_OPTIONS[0])
|
|
60
|
+
|
|
61
|
+
/* Режим переключается сразу и только локально — иначе нельзя спокойно открыть секцию AP и
|
|
62
|
+
заполнить SSID/пароль до реальной отправки. Предупреждение о разрыве связи с облаком
|
|
63
|
+
показывается не здесь, а перед save() — в момент, когда конфиг действительно уходит на
|
|
64
|
+
устройство и режим на нём меняется. STA+AP не предупреждаем: STA остаётся активным. */
|
|
65
|
+
let showAPWarning = $state(false)
|
|
66
|
+
|
|
67
|
+
const IP_MODE_OPTIONS: IOption<number>[] = [
|
|
68
|
+
{ id: "dhcp", name: "DHCP", value: 0 },
|
|
69
|
+
{ id: "static", name: "Static", value: 1 },
|
|
70
|
+
]
|
|
71
|
+
let selectedIPMode = $derived(IP_MODE_OPTIONS.find((o) => o.value === cfg.StaticIP) ?? IP_MODE_OPTIONS[0])
|
|
72
|
+
|
|
73
|
+
const IP_REGEXP = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
|
|
74
|
+
/* WPA2: пусто (открытая сеть) либо 8-63 символа — так же ограничивает сам чип */
|
|
75
|
+
const PSK_REGEXP = /^$|^.{8,63}$/
|
|
76
|
+
|
|
77
|
+
/* Сканирование сетей — на стороне вызывающего (в проде вызывает Header: GET, Argument: APsList) */
|
|
78
|
+
let scanning = $state(false)
|
|
79
|
+
let foundNetworks: IOption<string>[] = $state([])
|
|
80
|
+
const scanNetworks = async () => {
|
|
81
|
+
if (!onScan) return
|
|
82
|
+
scanning = true
|
|
83
|
+
foundNetworks = await onScan()
|
|
84
|
+
scanning = false
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Сворачивание тела виджета по клику на значок/заголовок (как аккордеон) — переключатель режима
|
|
88
|
+
в шапке кликом не триггерит, только сама кнопка значок+заголовок (см. WidgetHeader.svelte) */
|
|
89
|
+
let collapsed = $state(false)
|
|
90
|
+
|
|
91
|
+
/* Поля редактируются локально (как Argument: NoSend в реальной GUI) — отправка одним пакетом по кнопке */
|
|
92
|
+
let savedSnapshot = $state(JSON.stringify(cfg))
|
|
93
|
+
let isDirty = $derived(JSON.stringify(cfg) !== savedSnapshot)
|
|
94
|
+
const commitSave = () => {
|
|
95
|
+
onSave?.($state.snapshot(cfg))
|
|
96
|
+
savedSnapshot = JSON.stringify(cfg)
|
|
97
|
+
}
|
|
98
|
+
const save = () => {
|
|
99
|
+
if (cfg.WiFiMode === 2 && confirmOnAP) showAPWarning = true
|
|
100
|
+
else commitSave()
|
|
101
|
+
}
|
|
102
|
+
const confirmAndSave = () => {
|
|
103
|
+
showAPWarning = false
|
|
104
|
+
commitSave()
|
|
105
|
+
}
|
|
106
|
+
</script>
|
|
107
|
+
|
|
108
|
+
<div class={twMerge("relative flex w-full max-w-3xl flex-col gap-4 rounded-2xl border border-(--hairline-color) bg-(--container-color) p-4", wrapperClass)} style={accentStyle}>
|
|
109
|
+
<!-- Заголовок и переключатель режима -->
|
|
110
|
+
<WidgetHeader icon={WifiIcon} label={label?.name ?? "WiFi"} bind:collapsed>
|
|
111
|
+
{#snippet right()}
|
|
112
|
+
<UI.Select
|
|
113
|
+
wrapperClass="w-full sm:w-72"
|
|
114
|
+
type="buttons"
|
|
115
|
+
value={selectedMode}
|
|
116
|
+
options={MODE_OPTIONS}
|
|
117
|
+
onUpdate={(option) => (cfg.WiFiMode = (option as IOption<number>).value as number)}
|
|
118
|
+
/>
|
|
119
|
+
{/snippet}
|
|
120
|
+
</WidgetHeader>
|
|
121
|
+
|
|
122
|
+
{#if !collapsed}
|
|
123
|
+
<div class="flex flex-col gap-4" transition:slide={{ duration: 150 }}>
|
|
124
|
+
{#if cfg.WiFiMode === 1 || cfg.WiFiMode === 3}
|
|
125
|
+
<!-- Клиентский режим (STA) -->
|
|
126
|
+
<div class="flex flex-col gap-3 rounded-[10px] border border-(--hairline-color) bg-(--back-color) p-3" transition:slide={{ duration: 150 }}>
|
|
127
|
+
<span class="text-[12px] font-semibold text-(--muted-color)">Режим клиента (STA)</span>
|
|
128
|
+
|
|
129
|
+
<div class="flex items-end gap-2">
|
|
130
|
+
<UI.Select
|
|
131
|
+
wrapperClass="flex-1"
|
|
132
|
+
type="input"
|
|
133
|
+
label={{ name: "SSID сети" }}
|
|
134
|
+
value={{ id: "current", name: cfg.StaSSID, value: cfg.StaSSID }}
|
|
135
|
+
options={foundNetworks}
|
|
136
|
+
onUpdate={(option) => (cfg.StaSSID = (option as IOption<string>).name ?? "")}
|
|
137
|
+
/>
|
|
138
|
+
<UI.Button
|
|
139
|
+
wrapperClass="w-9"
|
|
140
|
+
componentClass="p-1"
|
|
141
|
+
content={{ icon: scanning ? LoaderIcon : SearchIcon, info: { text: "Сканировать сети", side: "top" } }}
|
|
142
|
+
disabled={scanning || !onScan}
|
|
143
|
+
onClick={scanNetworks}
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<UI.Input type="password" label={{ name: "Пароль" }} bind:value={cfg.StaPSK} maxlength={64} help={{ autocomplete: "off", regExp: PSK_REGEXP }} />
|
|
148
|
+
|
|
149
|
+
<UI.Select wrapperClass="w-48" type="buttons" label={{ name: "Получение IP" }} value={selectedIPMode} options={IP_MODE_OPTIONS}
|
|
150
|
+
onUpdate={(option) => (cfg.StaticIP = (option as IOption<number>).value as number)} />
|
|
151
|
+
|
|
152
|
+
{#if cfg.StaticIP === 1}
|
|
153
|
+
<div class="grid grid-cols-1 gap-2 sm:grid-cols-3" transition:slide={{ duration: 150 }}>
|
|
154
|
+
<UI.Input label={{ name: "IP адрес" }} bind:value={cfg.StaIP} maxlength={15} placeholder="192.168.1.100" help={{ regExp: IP_REGEXP }} />
|
|
155
|
+
<UI.Input label={{ name: "Маска подсети" }} bind:value={cfg.StaMS} maxlength={15} placeholder="255.255.255.0" help={{ regExp: IP_REGEXP }} />
|
|
156
|
+
<UI.Input label={{ name: "Шлюз" }} bind:value={cfg.StaGW} maxlength={15} placeholder="192.168.1.1" help={{ regExp: IP_REGEXP }} />
|
|
157
|
+
</div>
|
|
158
|
+
{/if}
|
|
159
|
+
</div>
|
|
160
|
+
{/if}
|
|
161
|
+
|
|
162
|
+
{#if cfg.WiFiMode === 2 || cfg.WiFiMode === 3}
|
|
163
|
+
<!-- Точка доступа (AP) -->
|
|
164
|
+
<div class="flex flex-col gap-3 rounded-[10px] border border-(--hairline-color) bg-(--back-color) p-3" transition:slide={{ duration: 150 }}>
|
|
165
|
+
<span class="text-[12px] font-semibold text-(--muted-color)">Точка доступа (AP)</span>
|
|
166
|
+
|
|
167
|
+
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
168
|
+
<UI.Input label={{ name: "SSID" }} bind:value={cfg.ApSSID} maxlength={32} />
|
|
169
|
+
<UI.Input type="password" label={{ name: "Пароль" }} bind:value={cfg.ApPSK} maxlength={64} help={{ autocomplete: "off", regExp: PSK_REGEXP }} />
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<div class="grid grid-cols-1 gap-2 sm:grid-cols-3">
|
|
173
|
+
<UI.Input label={{ name: "IP адрес" }} bind:value={cfg.ApIP} maxlength={15} help={{ regExp: IP_REGEXP }} />
|
|
174
|
+
<UI.Input label={{ name: "Маска подсети" }} bind:value={cfg.ApMS} maxlength={15} help={{ regExp: IP_REGEXP }} />
|
|
175
|
+
<UI.Input label={{ name: "Шлюз" }} bind:value={cfg.ApGW} maxlength={15} help={{ regExp: IP_REGEXP }} />
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
{/if}
|
|
179
|
+
|
|
180
|
+
<!-- Сохранение -->
|
|
181
|
+
<div class="flex flex-col items-center gap-2">
|
|
182
|
+
{#if isDirty}
|
|
183
|
+
<span class="text-[12px] text-(--muted-color)" transition:slide={{ duration: 100 }}>Есть несохранённые изменения</span>
|
|
184
|
+
{/if}
|
|
185
|
+
<UI.Button wrapperClass="w-40" componentClass="bg-green" content={{ name: "Сохранить" }} disabled={!isDirty} onClick={save} />
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
{/if}
|
|
189
|
+
|
|
190
|
+
{#if showAPWarning}
|
|
191
|
+
<!-- Подтверждение локально к виджету (не на весь экран) — предупреждение относится
|
|
192
|
+
именно к этой карточке, а не к странице целиком -->
|
|
193
|
+
<div
|
|
194
|
+
class="absolute inset-0 z-10 flex items-center justify-center rounded-2xl bg-black/40 backdrop-blur-[2px]"
|
|
195
|
+
transition:fade={{ duration: 150 }}
|
|
196
|
+
>
|
|
197
|
+
<div
|
|
198
|
+
class="flex w-full max-w-80 flex-col gap-3 rounded-[16px] border border-(--hairline-color) bg-(--back-color) p-4 shadow-(--elevation-3)"
|
|
199
|
+
transition:scale={{ duration: 150, start: 0.96 }}
|
|
200
|
+
>
|
|
201
|
+
<div class="flex items-center gap-2 text-(--orange-color)">
|
|
202
|
+
<WarningIcon />
|
|
203
|
+
<h4 class="text-[15px] font-semibold text-(--font-color)">Сохранить режим AP?</h4>
|
|
204
|
+
</div>
|
|
205
|
+
<p class="text-[13px] text-(--muted-color)">
|
|
206
|
+
Wi-Fi клиент (STA) будет выключен, устройство отключится от облака и станет доступно только по своей точке доступа. Продолжить?
|
|
207
|
+
</p>
|
|
208
|
+
<div class="flex gap-2">
|
|
209
|
+
<UI.Button wrapperClass="flex-1" componentClass="bg-transparent" content={{ name: "Отмена" }} onClick={() => (showAPWarning = false)} />
|
|
210
|
+
<UI.Button wrapperClass="flex-1" componentClass="bg-red" content={{ name: "Сохранить" }} onClick={confirmAndSave} />
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
{/if}
|
|
215
|
+
</div>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<!-- $lib/WidgetWiFi/WidgetWiFiProps.svelte — панель пропсов конструктора для WidgetWiFi.
|
|
2
|
+
Виджет — самостоятельный тип компонента (не набор примитивов с привязкой полей),
|
|
3
|
+
поэтому и панель проще: не "к какой переменной привязать", а "как виджет себя ведёт". -->
|
|
4
|
+
<script lang="ts">
|
|
5
|
+
import * as UI from ".."
|
|
6
|
+
import { updateProperty, type UIComponent, type IWidgetWiFiProps, type IOption } from "../types"
|
|
7
|
+
import WidgetAccentPicker from "../WidgetAccentPicker.svelte"
|
|
8
|
+
|
|
9
|
+
const { component, onPropertyChange } = $props<{
|
|
10
|
+
component: UIComponent & { properties: Partial<IWidgetWiFiProps> }
|
|
11
|
+
onPropertyChange: (updates: Partial<{ properties?: string | object; name?: string; access?: string }>) => void
|
|
12
|
+
}>()
|
|
13
|
+
|
|
14
|
+
const MODE_OPTIONS: IOption<number>[] = [
|
|
15
|
+
{ id: "sta", name: "STA", value: 1 },
|
|
16
|
+
{ id: "ap", name: "AP", value: 2 },
|
|
17
|
+
{ id: "apsta", name: "STA + AP", value: 3 },
|
|
18
|
+
]
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
{#snippet WidgetLabel()}
|
|
22
|
+
<UI.Input
|
|
23
|
+
label={{ name: "Заголовок" }}
|
|
24
|
+
value={component.properties.label?.name}
|
|
25
|
+
onUpdate={(value) => updateProperty("label.name", value as string, component, onPropertyChange)}
|
|
26
|
+
/>
|
|
27
|
+
{/snippet}
|
|
28
|
+
|
|
29
|
+
{#snippet WidgetAllowedModes()}
|
|
30
|
+
<UI.Select
|
|
31
|
+
label={{ name: "Разрешённые режимы" }}
|
|
32
|
+
type="buttons"
|
|
33
|
+
multiSelect={true}
|
|
34
|
+
options={MODE_OPTIONS}
|
|
35
|
+
value={MODE_OPTIONS.filter((o) => (component.properties.allowedModes ?? [1, 2, 3]).includes(o.value as number))}
|
|
36
|
+
onUpdate={(value) => {
|
|
37
|
+
if (Array.isArray(value)) updateProperty("allowedModes", value.map((o) => o.value) as number[], component, onPropertyChange)
|
|
38
|
+
}}
|
|
39
|
+
/>
|
|
40
|
+
{/snippet}
|
|
41
|
+
|
|
42
|
+
{#snippet WidgetConfirmAP()}
|
|
43
|
+
<UI.Switch
|
|
44
|
+
label={{ name: "Подтверждение при включении AP" }}
|
|
45
|
+
value={component.properties.confirmOnAP === false ? 0 : 1}
|
|
46
|
+
options={[{ id: crypto.randomUUID(), value: 0, class: "" }]}
|
|
47
|
+
onChange={(value) => updateProperty("confirmOnAP", value === 1, component, onPropertyChange)}
|
|
48
|
+
/>
|
|
49
|
+
{/snippet}
|
|
50
|
+
|
|
51
|
+
{#snippet WidgetColor()}
|
|
52
|
+
<WidgetAccentPicker
|
|
53
|
+
value={component.properties.componentClass ?? ""}
|
|
54
|
+
onUpdate={(value) => updateProperty("componentClass", value, component, onPropertyChange)}
|
|
55
|
+
/>
|
|
56
|
+
{/snippet}
|
|
57
|
+
|
|
58
|
+
<div class="flex w-full flex-col gap-3">
|
|
59
|
+
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
60
|
+
{@render WidgetLabel()}
|
|
61
|
+
{@render WidgetConfirmAP()}
|
|
62
|
+
</div>
|
|
63
|
+
{@render WidgetAllowedModes()}
|
|
64
|
+
{@render WidgetColor()}
|
|
65
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type UIComponent, type IWidgetWiFiProps } from "../types";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
component: UIComponent & {
|
|
4
|
+
properties: Partial<IWidgetWiFiProps>;
|
|
5
|
+
};
|
|
6
|
+
onPropertyChange: (updates: Partial<{
|
|
7
|
+
properties?: string | object;
|
|
8
|
+
name?: string;
|
|
9
|
+
access?: string;
|
|
10
|
+
}>) => void;
|
|
11
|
+
};
|
|
12
|
+
declare const WidgetWiFiProps: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
13
|
+
type WidgetWiFiProps = ReturnType<typeof WidgetWiFiProps>;
|
|
14
|
+
export default WidgetWiFiProps;
|
|
@@ -0,0 +1,6 @@
|
|
|
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 fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
5
|
+
d="M5 12.55a11 11 0 0 1 14.08 0M1.42 9a16 16 0 0 1 21.16 0M8.53 16.11a6 6 0 0 1 6.95 0M12 20h.01" /></svg
|
|
6
|
+
>
|
|
@@ -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 WifiIcon: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type WifiIcon = InstanceType<typeof WifiIcon>;
|
|
18
|
+
export default WifiIcon;
|
package/dist/index.d.ts
CHANGED
|
@@ -39,7 +39,11 @@ export { default as VideoViewer } from "./VideoViewer/VideoViewer.svelte";
|
|
|
39
39
|
export { default as VideoViewerProps } from "./VideoViewer/VideoViewerProps.svelte";
|
|
40
40
|
export { default as Widget } from "./Widget/Widget.svelte";
|
|
41
41
|
export { default as WidgetProps } from "./Widget/WidgetProps.svelte";
|
|
42
|
+
export { default as WidgetWiFi } from "./WidgetWiFi/WidgetWiFi.svelte";
|
|
43
|
+
export { default as WidgetWiFiProps } from "./WidgetWiFi/WidgetWiFiProps.svelte";
|
|
44
|
+
export { default as WidgetDeviceInfo } from "./WidgetDeviceInfo/WidgetDeviceInfo.svelte";
|
|
45
|
+
export { default as WidgetDeviceInfoProps } from "./WidgetDeviceInfo/WidgetDeviceInfoProps.svelte";
|
|
42
46
|
export * from "./locales/i18n";
|
|
43
47
|
export * from "./locales/translations";
|
|
44
48
|
export * from "./ModalStackStore";
|
|
45
|
-
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 IGraphProps, type IGraphDataObject, type ITableHeader, type ITableProps, type ITabsProps, type IJoystickProps, type IFileAttachProps, type IDeviceGNSS, type ICarouselProps, type IReceivingDataObject, type IVideoViewerProps, type IPeriodChartProps, type IPeriodChartLevel, } from "./types";
|
|
49
|
+
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 IGraphProps, type IGraphDataObject, type ITableHeader, 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
|
@@ -40,6 +40,10 @@ export { default as VideoViewer } from "./VideoViewer/VideoViewer.svelte";
|
|
|
40
40
|
export { default as VideoViewerProps } from "./VideoViewer/VideoViewerProps.svelte";
|
|
41
41
|
export { default as Widget } from "./Widget/Widget.svelte";
|
|
42
42
|
export { default as WidgetProps } from "./Widget/WidgetProps.svelte";
|
|
43
|
+
export { default as WidgetWiFi } from "./WidgetWiFi/WidgetWiFi.svelte";
|
|
44
|
+
export { default as WidgetWiFiProps } from "./WidgetWiFi/WidgetWiFiProps.svelte";
|
|
45
|
+
export { default as WidgetDeviceInfo } from "./WidgetDeviceInfo/WidgetDeviceInfo.svelte";
|
|
46
|
+
export { default as WidgetDeviceInfoProps } from "./WidgetDeviceInfo/WidgetDeviceInfoProps.svelte";
|
|
43
47
|
export * from "./locales/i18n";
|
|
44
48
|
export * from "./locales/translations";
|
|
45
49
|
export * from "./ModalStackStore";
|
package/dist/options.js
CHANGED
|
@@ -54,14 +54,20 @@ export const optionsStore = derived(T, ($T) => {
|
|
|
54
54
|
{ id: id(), value: "underline", name: $T("constructor.props.underline") },
|
|
55
55
|
{ id: id(), value: "background", name: $T("constructor.props.background") },
|
|
56
56
|
],
|
|
57
|
+
/* Единая семантическая палитра (app.css: --X-color/--X-soft, светлая и тёмная тема уже учтены) —
|
|
58
|
+
тот же набор ролей, что и в "мастер-цвете" виджетов конструктора (widgetAccent.ts на стороне
|
|
59
|
+
приложения). bg-max — нейтральный вариант (= --back-color), с ним свотч отмечается рамкой
|
|
60
|
+
(Select.svelte: hasOwnColor), а не акцентной синей заливкой, как обычный сегмент-контрол. */
|
|
57
61
|
COLOR_OPTIONS: [
|
|
58
|
-
{ id: id(), name: "", value: "bg-max", class: "bg-max" },
|
|
62
|
+
{ id: id(), name: "-", value: "bg-max", class: "bg-max text-transparent" },
|
|
59
63
|
{ id: id(), name: "", value: "bg-red", class: "bg-red" },
|
|
64
|
+
{ id: id(), name: "", value: "bg-orange", class: "bg-orange" },
|
|
60
65
|
{ id: id(), name: "", value: "bg-yellow", class: "bg-yellow" },
|
|
61
66
|
{ id: id(), name: "", value: "bg-green", class: "bg-green" },
|
|
62
|
-
{ id: id(), name: "
|
|
67
|
+
{ id: id(), name: "", value: "bg-cyan", class: "bg-cyan" },
|
|
68
|
+
{ id: id(), name: "", value: "bg-blue", class: "bg-blue" },
|
|
63
69
|
{ id: id(), name: "", value: "bg-purple", class: "bg-purple" },
|
|
64
|
-
{ id: id(), name: "", value: "bg-
|
|
70
|
+
{ id: id(), name: "", value: "bg-pink", class: "bg-pink" },
|
|
65
71
|
],
|
|
66
72
|
TEXT_COLOR_OPTIONS: [
|
|
67
73
|
{
|
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;
|
|
18
|
+
properties: IAccordionProps | IButtonProps | IColorPickerProps | IDividerProps | IFileAttachProps | IGraphProps | IInputProps | IJoystickProps | IMapProps | IPeriodChartProps | IProgressBarProps | ISelectProps<unknown> | ISliderProps | ISwitchProps | ITableProps<object> | ITabsProps | ITextFieldProps | IWidgetProps | IWidgetWiFiProps | IWidgetDeviceInfoProps;
|
|
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";
|
|
21
|
+
type: "Accordion" | "Button" | "ColorPicker" | "Divider" | "FileAttach" | "Graph" | "Input" | "Joystick" | "Map" | "PeriodChart" | "ProgressBar" | "Select" | "Slider" | "Switch" | "Table" | "Tabs" | "TextField" | "VideoViewer" | "Widget" | "WidgetWiFi" | "WidgetDeviceInfo";
|
|
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";
|
|
30
|
-
properties: IAccordionProps | IButtonProps | IColorPickerProps | IDividerProps | IFileAttachProps | IGraphProps | IInputProps | IJoystickProps | IMapProps | IPeriodChartProps | IProgressBarProps | ISelectProps | ISliderProps | ISwitchProps | ITableProps<object> | ITabsProps | ITextFieldProps | IWidgetProps;
|
|
29
|
+
type: "Accordion" | "Button" | "ColorPicker" | "Divider" | "FileAttach" | "Graph" | "Input" | "Joystick" | "Map" | "PeriodChart" | "ProgressBar" | "Select" | "Slider" | "Switch" | "Table" | "Tabs" | "TextField" | "VideoViewer" | "Widget" | "WidgetWiFi" | "WidgetDeviceInfo";
|
|
30
|
+
properties: IAccordionProps | IButtonProps | IColorPickerProps | IDividerProps | IFileAttachProps | IGraphProps | IInputProps | IJoystickProps | IMapProps | IPeriodChartProps | IProgressBarProps | ISelectProps | ISliderProps | ISwitchProps | ITableProps<object> | ITabsProps | ITextFieldProps | IWidgetProps | IWidgetWiFiProps | IWidgetDeviceInfoProps;
|
|
31
31
|
position: Required<Position>;
|
|
32
32
|
parentId: string;
|
|
33
33
|
eventHandler?: IUIComponentHandler;
|
|
@@ -492,3 +492,55 @@ export interface IWidgetProps {
|
|
|
492
492
|
};
|
|
493
493
|
onUpdate?: (value: number) => void;
|
|
494
494
|
}
|
|
495
|
+
export interface IWidgetWiFiConfig {
|
|
496
|
+
WiFiMode: number;
|
|
497
|
+
StaSSID: string;
|
|
498
|
+
StaPSK: string;
|
|
499
|
+
StaticIP: number;
|
|
500
|
+
StaIP: string;
|
|
501
|
+
StaMS: string;
|
|
502
|
+
StaGW: string;
|
|
503
|
+
ApSSID: string;
|
|
504
|
+
ApPSK: string;
|
|
505
|
+
ApIP: string;
|
|
506
|
+
ApMS: string;
|
|
507
|
+
ApGW: string;
|
|
508
|
+
}
|
|
509
|
+
export interface IWidgetWiFiProps {
|
|
510
|
+
id?: string;
|
|
511
|
+
wrapperClass?: string;
|
|
512
|
+
componentClass?: string;
|
|
513
|
+
label?: {
|
|
514
|
+
name?: string;
|
|
515
|
+
};
|
|
516
|
+
value?: IWidgetWiFiConfig;
|
|
517
|
+
allowedModes?: number[];
|
|
518
|
+
confirmOnAP?: boolean;
|
|
519
|
+
onScan?: () => Promise<{
|
|
520
|
+
id: string;
|
|
521
|
+
name: string;
|
|
522
|
+
value: string;
|
|
523
|
+
}[]>;
|
|
524
|
+
onSave?: (config: IWidgetWiFiConfig) => void;
|
|
525
|
+
}
|
|
526
|
+
export interface IWidgetDeviceInfoConfig {
|
|
527
|
+
DevSN: string;
|
|
528
|
+
DevID: string;
|
|
529
|
+
DevFW: number;
|
|
530
|
+
RunCnt: number;
|
|
531
|
+
DevName: string;
|
|
532
|
+
HostName: string;
|
|
533
|
+
WebUser: string;
|
|
534
|
+
WebPsw: string;
|
|
535
|
+
}
|
|
536
|
+
export interface IWidgetDeviceInfoProps {
|
|
537
|
+
id?: string;
|
|
538
|
+
wrapperClass?: string;
|
|
539
|
+
componentClass?: string;
|
|
540
|
+
label?: {
|
|
541
|
+
name?: string;
|
|
542
|
+
};
|
|
543
|
+
value?: IWidgetDeviceInfoConfig;
|
|
544
|
+
onSave?: (info: IWidgetDeviceInfoConfig) => void;
|
|
545
|
+
onRestart?: () => void;
|
|
546
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const widgetAccentStyle: (componentClass: string) => string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* $lib/widgetAccent.ts — общий "мастер-цвет" для смарт-виджетов конструктора (WidgetWiFi,
|
|
2
|
+
WidgetDeviceInfo и далее). Роль вида "bg-red" (см. optionsStore.COLOR_OPTIONS — та же палитра,
|
|
3
|
+
что и во всех примитивах) разворачивается в переопределение --accent-color/--accent-soft на
|
|
4
|
+
корне виджета: весь внутренний UI (сегмент-контролы, фокус у полей и т.п.) уже читает именно
|
|
5
|
+
эти переменные, поэтому виджет перекрашивается целиком одним источником истины. */
|
|
6
|
+
/* componentClass — значение вида "bg-red" из optionsStore.COLOR_OPTIONS (или "" — без роли,
|
|
7
|
+
обычный акцент темы). Возвращает inline-style для корня виджета; пустая строка ничего не переопределяет. */
|
|
8
|
+
export const widgetAccentStyle = (componentClass) => {
|
|
9
|
+
if (!componentClass)
|
|
10
|
+
return "";
|
|
11
|
+
const role = componentClass.replace(/^bg-/, "");
|
|
12
|
+
if (role === "max")
|
|
13
|
+
return "";
|
|
14
|
+
return (`--accent-color: var(--${role}-color); --accent-soft: var(--${role}-soft); --accent-hover: var(--${role}-color);` +
|
|
15
|
+
`--focus-shadow-color: 0 0 0 3px color-mix(in srgb, var(--${role}-color) 18%, transparent);`);
|
|
16
|
+
};
|