poe-svelte-ui-lib 1.44.1 → 1.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -110,6 +110,8 @@ npm install poe-svelte-ui-lib
110
110
 
111
111
  **README компонента** (`src/lib/<Компонент>/README.md`, вкладка «Инфо» витрины) — развёрнутая документация для разработчика. Первые разделы повторяют описание (i) и не должны ему противоречить, дальше идут разработческие: `## Назначение` (плюс заметка о размерах) → `## Пакет данных` (плюс `### Протокол устройства` / пример кода прошивки) → `## Ключи` (таблица «Ключ / Описание», те же формулировки, что в (i)) → `## Особенности` (поведение, ограничения) → `## Пропсы` (таблица «Название / Тип / По умолчанию / Описание», колбэки — `### Колбэки`) → `## Примеры` (Svelte) → `## Конструктор свойств (<Компонент>Props.svelte)` → `## Внутреннее устройство`.
112
112
 
113
+ **Языки — единственный источник в библиотеке.** Список языков — `LANGUAGES` в `locales/languages.ts` (код и название, без Svelte — годится и для серверного кода); из него выводятся `LanguageCode`, `LANGUAGE_CODES`, `isLanguageCode` и тип строки словаря `Translation` (по переводу на каждый язык в порядке `LANGUAGES`, TypeScript не даст пропустить язык). `locales/i18n.ts` — текущий язык `Language`/`setLanguage` (сохраняется в `localStorage` `AppLanguage`), `LOCALES` для переключателей (с флагами) и механизм перевода `createTranslator(словарь)`. Приложения (DevCloud, WebApp, терминал) свой список языков, хранилище и функцию перевода не держат: берут `Language`, `setLanguage`, `LOCALES` из библиотеки, а свой `$T` строят из своего словаря — `export const T = createTranslator(translations)`, где `translations: Translations`. Одно `setLanguage` переключает и компоненты библиотеки, и приложение. Новый язык — строка в `LANGUAGES` и флаг в `i18n.ts`, затем TypeScript покажет все словари, где не хватает перевода.
114
+
113
115
  **Языки (ru/en/zh) — два слоя:**
114
116
 
115
117
  - **Встроенные тексты компонентов** (подписи полей виджетов, кнопки, подсказки, `aria-label`) — только через словарь `src/lib/locales/translations.ts` и `$T("ключ")`; строка с числами — параметрами: `$T("widget.fs.files", { n: files.length })` на ключ `"Файлов: {n}"`. Русских литералов в разметке и скриптах компонентов быть не должно. Списки вариантов с подписями — `$derived`, иначе не переключатся при смене языка. Исключение — шкалы прибора `WidgetPFD`: там общепринятые авиационные сокращения (`SPD`, `ALT`, `AS`, `VS`, `NO DATA`) на всех языках, переводятся только подсказки и строки состояния.
@@ -3,7 +3,7 @@
3
3
  import { getContext } from "svelte"
4
4
  import { T } from "../locales/i18n"
5
5
  import translations from "../locales/translations"
6
- import { LANGUAGE_CODES } from "../locales/localize"
6
+ import { LANGUAGE_CODES } from "../locales/languages"
7
7
  import type { IInputProps, UIComponent, IOption, IUIComponentHandler } from "../types"
8
8
  import * as UI from ".."
9
9
  import { optionsStore, findColorOption } from "../options"
@@ -3,8 +3,8 @@
3
3
  поэтому окно одно на все компоненты; окна свойств сами тексты не показывают и не правят. -->
4
4
  <script lang="ts">
5
5
  import { T } from "./locales/i18n"
6
- import { LANGUAGE_CODES, collectTexts, packLocalized, setAtPath, type TextPath } from "./locales/localize"
7
- import type { LanguageCode } from "./locales/translations"
6
+ import { collectTexts, packLocalized, setAtPath, type TextPath } from "./locales/localize"
7
+ import { LANGUAGE_CODES, type LanguageCode } from "./locales/languages"
8
8
  import Button from "./Button/Button.svelte"
9
9
  import Input from "./Input/Input.svelte"
10
10
  import Modal from "./Modal.svelte"
@@ -11,6 +11,7 @@
11
11
  - Поля группы `CFG` (меняется префиксом в настройках), привязывать переменные не нужно
12
12
  - «Сохранить» отправляет изменённые поля одним пакетом `SET ModCfg`
13
13
  - 🔌 «Перезагрузить» — команда `SET Restart`
14
+ - ♻️ «Сброс настроек» — после подтверждения команда `SET RstNVS`: прошивка очищает NVS и перезагружается, все настройки возвращаются к заводским
14
15
 
15
16
  ## Ключи
16
17
 
@@ -27,7 +28,7 @@
27
28
 
28
29
  ## Особенности
29
30
 
30
- Смарт-виджет информации об устройстве. Как и `WidgetWiFi`, не привязывается к одной произвольной переменной устройства — работает с фиксированным набором полей, соответствующих реальной прошивочной конвенции (`main_config.c`, группа `CFG`, ProdFactory-ESP): `DevSN`, `DevID`, `DevFW`, `RunCnt`, `StaIP`, `ApIP` — только чтение; `DevName`, `HostName`, `WebUser`, `WebPsw` — редактируются локально и сохраняются одним пакетом по кнопке "Сохранить". Отдельная кнопка "Перезагрузить" рядом с заголовком вызывает `onRestart` немедленно (не входит в пакет сохранения полей). Реальное имя поля устройства для каждого ключа можно переопределить через `keys` (по умолчанию совпадает с именем ключа). Компонент ничего не знает про DeviceStore/WebSocket — только `value`/`onSave`/`onRestart`.
31
+ Смарт-виджет информации об устройстве. Как и `WidgetWiFi`, не привязывается к одной произвольной переменной устройства — работает с фиксированным набором полей, соответствующих реальной прошивочной конвенции (`main_config.c`, группа `CFG`, ProdFactory-ESP): `DevSN`, `DevID`, `DevFW`, `RunCnt`, `StaIP`, `ApIP` — только чтение; `DevName`, `HostName`, `WebUser`, `WebPsw` — редактируются локально и сохраняются одним пакетом по кнопке "Сохранить". Отдельная кнопка "Перезагрузить" рядом с заголовком вызывает `onRestart` немедленно (не входит в пакет сохранения полей). Реальное имя поля устройства для каждого ключа можно переопределить через `keys` (по умолчанию совпадает с именем ключа). Внизу — «Сброс настроек» (`onFactoryReset`): все настройки к заводским, только после подтверждения в окне поверх виджета; без колбэка кнопка не показывается. Компонент ничего не знает про DeviceStore/WebSocket — только `value`/`onSave`/`onRestart`/`onFactoryReset`.
31
32
 
32
33
  - Серийный номер устройства обычно длинный (например `00.00.000-00_0000AC670000B22B00007428:DBC5`) — в разметке занимает отдельную строку, чтобы не обрезаться в узкой сетке.
33
34
  - `WebPsw` в реальном устройстве исторически передаётся обычным текстом; в виджете сделан `type="password"` для консистентности с остальным обликом конструктора.
@@ -44,6 +45,8 @@
44
45
  | `persistKey` | `string` | `undefined` | Ключ для сохранения состояния `collapsed` в `localStorage` — при заданном ключе состояние сворачивания переживает перезагрузку страницы |
45
46
  | `onSave` | `(info: IWidgetDeviceInfoConfig) => void` | `undefined` | Срабатывает при нажатии "Сохранить" — редактируемые поля одним пакетом |
46
47
  | `onRestart` | `() => void` | `undefined` | Срабатывает при нажатии "Перезагрузить" |
48
+ | `onFactoryReset` | `() => void` | `undefined` | Срабатывает после подтверждения «Сброс настроек»; без колбэка кнопки нет |
49
+ | `resetCommand` | `{ header?: string; argument?: string }` | `SET` / `RstNVS` | Команда сброса настроек — для приложения, сам компонент её не отправляет |
47
50
 
48
51
  ### IWidgetDeviceInfoConfig
49
52
 
@@ -98,7 +101,7 @@
98
101
  | `component` | `UIComponent & { properties: Partial<IWidgetDeviceInfoProps> }` | Объект компонента с его свойствами |
99
102
  | `onPropertyChange` | `(updates: Partial<{ properties?: string \| object; name?: string; access?: string }>) => void` | Коллбэк для обновления свойств компонента |
100
103
 
101
- Панель содержит заголовок, мастер-цвет (через общий `WidgetAccentPicker.svelte`), префикс прошивочной группы (ID), переопределение имени поля устройства для каждого ключа `IWidgetDeviceInfoConfig` (по умолчанию совпадает с именем ключа) и команду перезагрузки (Header/Argument, по умолчанию `SET`/`Restart`).
104
+ Панель содержит заголовок, мастер-цвет (через общий `WidgetAccentPicker.svelte`), префикс прошивочной группы (ID), переопределение имени поля устройства для каждого ключа `IWidgetDeviceInfoConfig` (по умолчанию совпадает с именем ключа) команду перезагрузки (Header/Argument, по умолчанию `SET`/`Restart`) и команду сброса настроек (по умолчанию `SET`/`RstNVS`).
102
105
 
103
106
  ### Что настраивается
104
107
 
@@ -2,11 +2,12 @@
2
2
  Фиксированный набор полей (см. IWidgetDeviceInfoConfig в types.ts), соответствует реальной
3
3
  прошивочной конвенции (main_config.c, группа "CFG", ProdFactory-ESP):
4
4
  DevSN/DevID/DevFW/RunCnt — только чтение; DevName/HostName/WebUser/WebPsw — сохраняются
5
- одним пакетом. Ничего не знает про DeviceStore/WebSocket — value/onSave/onRestart. -->
5
+ одним пакетом; «Сброс настроек» — с подтверждением, все настройки к заводским. Ничего не знает про DeviceStore/WebSocket —
6
+ value/onSave/onRestart/onFactoryReset. -->
6
7
  <script lang="ts">
7
8
  import { T } from "../locales/i18n"
8
9
  import { untrack } from "svelte"
9
- import { slide } from "svelte/transition"
10
+ import { fade, scale, slide } from "svelte/transition"
10
11
  import { twMerge } from "tailwind-merge"
11
12
  import * as UI from ".."
12
13
  import type { IWidgetDeviceInfoConfig, IWidgetDeviceInfoProps } from "../types"
@@ -25,8 +26,15 @@
25
26
  persistKey,
26
27
  onSave = () => {},
27
28
  onRestart = () => {},
29
+ onFactoryReset,
28
30
  }: IWidgetDeviceInfoProps = $props()
29
31
 
32
+ let confirmReset = $state(false)
33
+ const factoryReset = () => {
34
+ confirmReset = false
35
+ onFactoryReset?.()
36
+ }
37
+
30
38
  let accentStyle = $derived(widgetAccentStyle(componentClass ?? ""))
31
39
 
32
40
  let info: IWidgetDeviceInfoConfig = $state(
@@ -80,7 +88,10 @@
80
88
  }
81
89
  </script>
82
90
 
83
- <div class={twMerge("flex w-full flex-col gap-4 rounded-2xl border border-(--hairline-color) bg-(--container-color) p-4", wrapperClass)} style={accentStyle}>
91
+ <div
92
+ class={twMerge("relative flex w-full flex-col gap-4 rounded-2xl border border-(--hairline-color) bg-(--container-color) p-4", wrapperClass)}
93
+ style={accentStyle}
94
+ >
84
95
  <!-- Заголовок -->
85
96
  <WidgetHeader icon={CpuIcon} label={label?.name || $T("widget.info.title")} bind:collapsed>
86
97
  {#snippet right()}
@@ -128,6 +139,41 @@
128
139
  {/if}
129
140
  <UI.Button wrapperClass="w-40" componentClass="bg-accent" content={{ name: $T("widget.common.save") }} disabled={!isDirty} onClick={save} />
130
141
  </div>
142
+
143
+ <!-- Сброс всех настроек к заводским — отдельно от сохранения, только с подтверждением -->
144
+ {#if onFactoryReset}
145
+ <div class="flex items-center justify-between gap-3 border-t border-(--hairline-color) pt-3">
146
+ <span class="text-[12px] text-(--muted-color)">{$T("widget.info.factory_reset_hint")}</span>
147
+ <UI.Button
148
+ wrapperClass="w-auto shrink-0"
149
+ componentClass="bg-red px-3"
150
+ content={{ name: $T("widget.info.factory_reset") }}
151
+ onClick={() => (confirmReset = true)}
152
+ />
153
+ </div>
154
+ {/if}
155
+ </div>
156
+ {/if}
157
+
158
+ {#if confirmReset}
159
+ <!-- Подтверждение локально к виджету, как удаление файла у WidgetFileSystem -->
160
+ <div class="absolute inset-0 z-10 flex items-center justify-center rounded-2xl bg-black/40 backdrop-blur-[2px]" transition:fade={{ duration: 150 }}>
161
+ <div
162
+ class="flex w-full max-w-96 flex-col gap-3 rounded-[16px] border border-(--hairline-color) bg-(--back-color) p-4 shadow-(--elevation-3)"
163
+ transition:scale={{ duration: 150, start: 0.96 }}
164
+ >
165
+ <h4 class="text-[15px] font-semibold">{$T("widget.info.factory_reset_title")}</h4>
166
+ <p class="text-[13px] text-(--muted-color)">{$T("widget.info.factory_reset_text")}</p>
167
+ <div class="flex gap-2">
168
+ <UI.Button
169
+ wrapperClass="flex-1"
170
+ componentClass="bg-transparent"
171
+ content={{ name: $T("widget.common.cancel") }}
172
+ onClick={() => (confirmReset = false)}
173
+ />
174
+ <UI.Button wrapperClass="flex-1" componentClass="bg-red" content={{ name: $T("widget.info.factory_reset") }} onClick={factoryReset} />
175
+ </div>
176
+ </div>
131
177
  </div>
132
178
  {/if}
133
179
  </div>
@@ -29,18 +29,18 @@
29
29
  {/each}
30
30
  {/snippet}
31
31
 
32
- {#snippet WidgetRestartCommand()}
32
+ {#snippet WidgetCommand(path: "restartCommand" | "resetCommand", defaultArgument: string)}
33
33
  <UI.Select
34
34
  label={{ name: $T("widget.common.packet_header") }}
35
35
  type="buttons"
36
36
  options={$optionsStore.HEADER_OPTIONS}
37
- value={$optionsStore.HEADER_OPTIONS.find((o) => o.value === (component.properties.restartCommand?.header || "SET"))}
38
- onUpdate={(option) => updateProperty("restartCommand.header", (option as IOption<string>).value as string, component, onPropertyChange)}
37
+ value={$optionsStore.HEADER_OPTIONS.find((o) => o.value === (component.properties[path]?.header || "SET"))}
38
+ onUpdate={(option) => updateProperty(`${path}.header`, (option as IOption<string>).value as string, component, onPropertyChange)}
39
39
  />
40
40
  <UI.Input
41
41
  label={{ name: $T("widget.common.argument") }}
42
- value={component.properties.restartCommand?.argument ?? "Restart"}
43
- onUpdate={(value) => updateProperty("restartCommand.argument", value as string, component, onPropertyChange)}
42
+ value={component.properties[path]?.argument ?? defaultArgument}
43
+ onUpdate={(value) => updateProperty(`${path}.argument`, value as string, component, onPropertyChange)}
44
44
  />
45
45
  {/snippet}
46
46
 
@@ -61,6 +61,9 @@
61
61
  {@render WidgetKeys()}
62
62
  </PropsGroup>
63
63
  <PropsGroup label={$T("widget.info.restart_command")}>
64
- {@render WidgetRestartCommand()}
64
+ {@render WidgetCommand("restartCommand", "Restart")}
65
+ </PropsGroup>
66
+ <PropsGroup label={$T("widget.info.reset_command")}>
67
+ {@render WidgetCommand("resetCommand", "RstNVS")}
65
68
  </PropsGroup>
66
69
  </div>
package/dist/index.d.ts CHANGED
@@ -51,7 +51,7 @@ export { default as WidgetPFDProps } from "./WidgetPFD/WidgetPFDProps.svelte";
51
51
  export { default as WidgetMap } from "./WidgetMap/WidgetMap.svelte";
52
52
  export { default as WidgetMapProps } from "./WidgetMap/WidgetMapProps.svelte";
53
53
  export * from "./locales/i18n";
54
- export * from "./locales/translations";
54
+ export * from "./locales/languages";
55
55
  export * from "./locales/localize";
56
56
  export { widgetAccentStyle } from "./widgetAccent";
57
57
  export * from "./ModalStackStore";
package/dist/index.js CHANGED
@@ -52,7 +52,7 @@ export { default as WidgetPFDProps } from "./WidgetPFD/WidgetPFDProps.svelte";
52
52
  export { default as WidgetMap } from "./WidgetMap/WidgetMap.svelte";
53
53
  export { default as WidgetMapProps } from "./WidgetMap/WidgetMapProps.svelte";
54
54
  export * from "./locales/i18n";
55
- export * from "./locales/translations";
55
+ export * from "./locales/languages";
56
56
  export * from "./locales/localize";
57
57
  export { widgetAccentStyle } from "./widgetAccent";
58
58
  export * from "./ModalStackStore";
@@ -1,11 +1,14 @@
1
1
  import { type Readable } from "svelte/store";
2
+ import { type LanguageCode, type Translations } from "./languages";
2
3
  export declare const Language: import("svelte/store").Writable<string>;
3
4
  export declare function setLanguage(newLang: string): void;
4
5
  export declare const LOCALES: {
5
6
  id: string;
6
7
  name: string;
7
- value: string;
8
+ value: LanguageCode;
8
9
  component: ConstructorOfATypedSvelteComponent;
9
10
  }[];
10
11
  export type TranslateParams = Record<string, string | number>;
11
- export declare const T: Readable<(key: string, params?: TranslateParams) => string>;
12
+ export type Translator = (key: string, params?: TranslateParams) => string;
13
+ export declare const createTranslator: (dictionary: Translations) => Readable<Translator>;
14
+ export declare const T: Readable<Translator>;
@@ -1,39 +1,58 @@
1
- import { derived } from "svelte/store";
2
- import { writable } from "svelte/store";
1
+ import { derived, writable } from "svelte/store";
3
2
  import translations from "./translations";
4
3
  import FlagZh from "./FlagZh.svelte";
5
4
  import FlagRu from "./FlagRu.svelte";
6
5
  import FlagEn from "./FlagEn.svelte";
7
- /* Язык по умолчанию */
8
- let initialLanguage = "ru";
9
- /* Проверяем, доступен ли localStorage */
6
+ import { DEFAULT_LANGUAGE, LANGUAGES, isLanguageCode } from "./languages";
7
+ /* Язык и механизм перевода — общие для библиотеки и приложений: приложение берёт отсюда Language/setLanguage/LOCALES и строит свой $T
8
+ из своего словаря через createTranslator (см. README «Языки»); переключение языка одним setLanguage меняет и компоненты, и приложение */
9
+ /* Стартовый язык: сохранённый → язык браузера → по умолчанию */
10
+ let initialLanguage = DEFAULT_LANGUAGE;
10
11
  if (typeof window !== "undefined") {
11
- initialLanguage = localStorage.getItem("AppLanguage") || navigator.language?.split("-")[0]?.toLowerCase() || "ru";
12
- const supported = ["ru", "en", "zh"];
13
- if (!supported.includes(initialLanguage)) {
14
- initialLanguage = "ru";
12
+ let saved = null;
13
+ try {
14
+ saved = localStorage.getItem("AppLanguage");
15
15
  }
16
+ catch {
17
+ /* хранилище недоступно */
18
+ }
19
+ const candidate = saved || navigator.language?.split("-")[0]?.toLowerCase();
20
+ if (isLanguageCode(candidate))
21
+ initialLanguage = candidate;
16
22
  }
17
- /* Создаем хранилище с начальным значением языка */
23
+ /* Текущий язык */
18
24
  export const Language = writable(initialLanguage);
19
- /* Функция для изменения языка */
25
+ /* Смена языка с сохранением выбора */
20
26
  export function setLanguage(newLang) {
27
+ if (!isLanguageCode(newLang))
28
+ return;
21
29
  if (typeof window !== "undefined") {
22
- localStorage.setItem("AppLanguage", newLang);
30
+ try {
31
+ localStorage.setItem("AppLanguage", newLang);
32
+ }
33
+ catch {
34
+ /* хранилище недоступно — выбор действует до перезагрузки */
35
+ }
23
36
  }
24
37
  Language.set(newLang);
25
38
  }
26
- /* Поддерживаемые языки (код, название языка, флаг) */
27
- export const LOCALES = [
28
- { id: "app-language-ru", name: "Русский", value: "ru", component: FlagRu },
29
- { id: "app-language-en", name: "English", value: "en", component: FlagEn },
30
- { id: "app-language-zh", name: "中国人", value: "zh", component: FlagZh },
31
- ];
32
- /* Индекс языка в кортеже перевода [ru, en, zh] — жёстко привязан к порядку LOCALES */
33
- const LANGUAGE_INDEX = Object.fromEntries(LOCALES.map((locale, index) => [locale.value, index]));
34
- /* Функция для перевода для текущего языка */
35
- function translate(locale, key, params) {
36
- const text = translations[key]?.[LANGUAGE_INDEX[locale] ?? 0] || key;
39
+ /* Языки для переключателей: список из languages.ts + флаг */
40
+ const FLAGS = { ru: FlagRu, en: FlagEn, zh: FlagZh };
41
+ export const LOCALES = LANGUAGES.map((language) => ({
42
+ id: `app-language-${language.value}`,
43
+ name: language.name,
44
+ value: language.value,
45
+ component: FLAGS[language.value],
46
+ }));
47
+ /* Индекс языка в строке словаря — порядок LANGUAGES */
48
+ const LANGUAGE_INDEX = Object.fromEntries(LANGUAGES.map((language, index) => [language.value, index]));
49
+ /* Перевод по словарю: текущий язык → русский → сам ключ */
50
+ const translate = (dictionary, locale, key, params) => {
51
+ const row = dictionary[key];
52
+ const text = row?.[LANGUAGE_INDEX[locale] ?? 0] || row?.[0] || key;
37
53
  return params ? text.replace(/\{(\w+)\}/g, (match, name) => (name in params ? String(params[name]) : match)) : text;
38
- }
39
- export const T = derived(Language, ($lang) => (key, params) => translate($lang, key, params));
54
+ };
55
+ /* $T приложения из его собственного словаря — поверх общего языка */
56
+ export const createTranslator = (dictionary) => derived(Language, ($lang) => (key, params) => translate(dictionary, $lang, key, params));
57
+ /* $T самой библиотеки */
58
+ export const T = createTranslator(translations);
@@ -0,0 +1,20 @@
1
+ export declare const LANGUAGES: readonly [{
2
+ readonly value: "ru";
3
+ readonly name: "Русский";
4
+ }, {
5
+ readonly value: "en";
6
+ readonly name: "English";
7
+ }, {
8
+ readonly value: "zh";
9
+ readonly name: "中文";
10
+ }];
11
+ export type LanguageCode = (typeof LANGUAGES)[number]["value"];
12
+ export declare const LANGUAGE_CODES: LanguageCode[];
13
+ type StringPerItem<T extends readonly unknown[]> = {
14
+ -readonly [K in keyof T]: string;
15
+ };
16
+ export type Translation = StringPerItem<typeof LANGUAGES>;
17
+ export type Translations = Record<string, Translation>;
18
+ export declare const DEFAULT_LANGUAGE: LanguageCode;
19
+ export declare const isLanguageCode: (value: unknown) => value is LanguageCode;
20
+ export {};
@@ -0,0 +1,11 @@
1
+ /* $lib/locales/languages.ts */
2
+ /* Единственный источник списка языков для библиотеки и всех приложений на ней (DevCloud, WebApp, терминал); без Svelte-зависимостей — годится и для серверного кода */
3
+ export const LANGUAGES = [
4
+ { value: "ru", name: "Русский" },
5
+ { value: "en", name: "English" },
6
+ { value: "zh", name: "中文" },
7
+ ];
8
+ /* Коды в порядке LANGUAGES — он же порядок переводов в строке словаря */
9
+ export const LANGUAGE_CODES = LANGUAGES.map((language) => language.value);
10
+ export const DEFAULT_LANGUAGE = "ru";
11
+ export const isLanguageCode = (value) => LANGUAGE_CODES.includes(value);
@@ -1,6 +1,5 @@
1
- import type { LanguageCode } from "./translations";
1
+ import { type LanguageCode } from "./languages";
2
2
  export type LocalizedText = string | Partial<Record<LanguageCode, string>>;
3
- export declare const LANGUAGE_CODES: LanguageCode[];
4
3
  export declare const isLocalizedText: (value: unknown) => value is Partial<Record<LanguageCode, string>>;
5
4
  export declare const localizeText: (value: LocalizedText | undefined, lang: string) => string;
6
5
  export declare function localizeDeep<T>(value: T, lang: string): T;
@@ -1,4 +1,6 @@
1
- export const LANGUAGE_CODES = ["ru", "en", "zh"];
1
+ /* $lib/locales/localize.ts */
2
+ /* Многоязычные тексты, введённые в конструкторе: строка (один язык) или объект { ru, en, zh }; ru — основной и запасной */
3
+ import { LANGUAGE_CODES } from "./languages";
2
4
  /* Поля свойств, которые считаются текстом для пользователя — их показывает и правит окно «Тексты и переводы» (TextsEditor) */
3
5
  const TEXT_KEYS = new Set([
4
6
  "name",
@@ -1,3 +1,3 @@
1
- export type LanguageCode = "ru" | "en" | "zh";
2
- declare const translations: Record<string, [string, string, string]>;
1
+ import type { Translations } from "./languages";
2
+ declare const translations: Translations;
3
3
  export default translations;
@@ -103,9 +103,9 @@ const translations = {
103
103
  "🎯 **用途**\n设备网络的完整配置:模式、连接路由器、自身接入点。\n\n📦 **数据包**\n- `CFG` 分组的字段(可通过设置中的前缀更改),无需绑定变量\n- 字段在本地编辑,「保存」以一个 `SET ModCfg` 数据包发送已修改的字段\n- 🔍 扫描网络 —— `GET APsList`,找到的网络出现在 SSID 列表中\n\n🔑 **键**\n- `WiFiMode` —— 数字:`0` 关闭、`1` 客户端、`2` 接入点、`3` 两者\n- `StaSSID`、`StaPSK` —— 字符串:路由器网络和密码\n- `StaticIP` —— 数字:`0` DHCP、`1` 静态地址\n- `StaIP`、`StaMS`、`StaGW` —— 字符串:客户端地址、掩码、网关\n- `ApSSID`、`ApPSK` —— 字符串:自身接入点名称和密码\n- `ApIP`、`ApMS`、`ApGW` —— 字符串:接入点地址、掩码、网关\n\n✨ **特点**\n- 仅启用接入点时组件会提醒:设备可能从局域网中消失\n\n⚙️ **设置**\n- 分组前缀(ID)、字段名覆盖、主色",
104
104
  ],
105
105
  "component.info.WidgetDeviceInfo": [
106
- "🎯 **Назначение**\nПаспорт устройства и его основные настройки.\n\n📦 **Пакет данных**\n- Поля группы `CFG` (меняется префиксом в настройках), привязывать переменные не нужно\n- «Сохранить» отправляет изменённые поля одним пакетом `SET ModCfg`\n- 🔌 «Перезагрузить» — команда `SET Restart`\n\n🔑 **Ключи**\n- `DevSN` — строка: серийный номер, только чтение\n- `DevID` — строка: код изделия, только чтение\n- `DevFW` — число: версия ПО, только чтение\n- `RunCnt` — число: счётчик запусков, только чтение\n- `StaIP`, `ApIP` — строки: текущие адреса клиента и точки доступа, только чтение\n- `DevName` — строка: имя устройства\n- `HostName` — строка: сетевое имя\n- `WebUser`, `WebPsw` — строки: логин и пароль веб-интерфейса\n\n⚙️ **Настройки**\n- Префикс группы (ID), мастер-цвет",
107
- '🎯 **Purpose**\nDevice passport and its main settings.\n\n📦 **Data packet**\n- Fields of the `CFG` group (changed by the prefix in settings), no variable binding needed\n- "Save" sends the changed fields in one `SET ModCfg` packet\n- 🔌 "Restart" — the `SET Restart` command\n\n🔑 **Keys**\n- `DevSN` — string: serial number, read only\n- `DevID` — string: product code, read only\n- `DevFW` — number: firmware version, read only\n- `RunCnt` — number: boot counter, read only\n- `StaIP`, `ApIP` — strings: current client and access point addresses, read only\n- `DevName` — string: device name\n- `HostName` — string: network host name\n- `WebUser`, `WebPsw` — strings: web interface login and password\n\n⚙️ **Settings**\n- Group prefix (ID), master color',
108
- "🎯 **用途**\n设备档案及其主要设置。\n\n📦 **数据包**\n- `CFG` 分组的字段(可通过设置中的前缀更改),无需绑定变量\n- 「保存」以一个 `SET ModCfg` 数据包发送已修改的字段\n- 🔌「重启」—— `SET Restart` 命令\n\n🔑 **键**\n- `DevSN` —— 字符串:序列号,只读\n- `DevID` —— 字符串:产品代码,只读\n- `DevFW` —— 数字:固件版本,只读\n- `RunCnt` —— 数字:启动计数,只读\n- `StaIP`、`ApIP` —— 字符串:客户端和接入点的当前地址,只读\n- `DevName` —— 字符串:设备名称\n- `HostName` —— 字符串:网络主机名\n- `WebUser`、`WebPsw` —— 字符串:Web 界面登录名和密码\n\n⚙️ **设置**\n- 分组前缀(ID)、主色",
106
+ "🎯 **Назначение**\nПаспорт устройства и его основные настройки.\n\n📦 **Пакет данных**\n- Поля группы `CFG` (меняется префиксом в настройках), привязывать переменные не нужно\n- «Сохранить» отправляет изменённые поля одним пакетом `SET ModCfg`\n- ♻️ «Сброс настроек» — после подтверждения команда `SET RstNVS`, все настройки к заводским, прибор перезагружается\n- 🔌 «Перезагрузить» — команда `SET Restart`\n\n🔑 **Ключи**\n- `DevSN` — строка: серийный номер, только чтение\n- `DevID` — строка: код изделия, только чтение\n- `DevFW` — число: версия ПО, только чтение\n- `RunCnt` — число: счётчик запусков, только чтение\n- `StaIP`, `ApIP` — строки: текущие адреса клиента и точки доступа, только чтение\n- `DevName` — строка: имя устройства\n- `HostName` — строка: сетевое имя\n- `WebUser`, `WebPsw` — строки: логин и пароль веб-интерфейса\n\n⚙️ **Настройки**\n- Префикс группы (ID), мастер-цвет",
107
+ '🎯 **Purpose**\nDevice passport and its main settings.\n\n📦 **Data packet**\n- Fields of the `CFG` group (changed by the prefix in settings), no variable binding needed\n- "Save" sends the changed fields in one `SET ModCfg` packet\n- ♻️ "Factory reset" — after confirmation the `SET RstNVS` command, all settings return to factory defaults and the device restarts\n- 🔌 "Restart" — the `SET Restart` command\n\n🔑 **Keys**\n- `DevSN` — string: serial number, read only\n- `DevID` — string: product code, read only\n- `DevFW` — number: firmware version, read only\n- `RunCnt` — number: boot counter, read only\n- `StaIP`, `ApIP` — strings: current client and access point addresses, read only\n- `DevName` — string: device name\n- `HostName` — string: network host name\n- `WebUser`, `WebPsw` — strings: web interface login and password\n\n⚙️ **Settings**\n- Group prefix (ID), master color',
108
+ "🎯 **用途**\n设备档案及其主要设置。\n\n📦 **数据包**\n- `CFG` 分组的字段(可通过设置中的前缀更改),无需绑定变量\n- 「保存」以一个 `SET ModCfg` 数据包发送已修改的字段\n- ♻️「恢复出厂设置」—— 确认后发送 `SET RstNVS` 命令,所有设置恢复为出厂默认值,设备重启\n- 🔌「重启」—— `SET Restart` 命令\n\n🔑 **键**\n- `DevSN` —— 字符串:序列号,只读\n- `DevID` —— 字符串:产品代码,只读\n- `DevFW` —— 数字:固件版本,只读\n- `RunCnt` —— 数字:启动计数,只读\n- `StaIP`、`ApIP` —— 字符串:客户端和接入点的当前地址,只读\n- `DevName` —— 字符串:设备名称\n- `HostName` —— 字符串:网络主机名\n- `WebUser`、`WebPsw` —— 字符串:Web 界面登录名和密码\n\n⚙️ **设置**\n- 分组前缀(ID)、主色",
109
109
  ],
110
110
  "component.info.WidgetFileSystem": [
111
111
  '🎯 **Назначение**\nПамять устройства, список файлов, удаление и закачка.\n\n📦 **Пакет данных**\n- Один объект `CFG.FS` (префикс и имя поля меняются в настройках), привязывать переменные не нужно\n- 🔄 «Обновить» — `GET FS`\n- «Удалить» — `SET FS` с `Op: Delete` после подтверждения\n- Закачка — `SET FS` с `Op` `UploadStart` → `UploadChunk` → `UploadDone`\n\n🔑 **Ключи**\n- `Total`, `Used`, `Free` — числа, байт: объём, занято, свободно\n- `List` — массив `[{ "Name", "Size" }]`: полный путь файла и размер в байтах\n- `Upload` — объект `{ "Name", "Size", "Progress" }`, есть только во время закачки; `Progress` — 0–100\n\n✨ **Особенности**\n- Полоса занятости оранжевая от 75%, красная от 90%\n- Закачка с другого клиента тоже видна\n- Имя файла — не длиннее 31 символа (ограничение прошивки, задаётся в настройках)\n\n⚙️ **Настройки**\n- Префикс группы (ID), поле состояния ФС, аргумент команд, мастер-цвет\n- Допустимые файлы (фильтр в окне выбора), максимальная длина имени',
@@ -754,5 +754,14 @@ const translations = {
754
754
  "constructor.texts.buttons": ["Кнопка", "Button", "按钮"],
755
755
  "ui.graph.values": ["Значения", "Values", "数值"],
756
756
  "ui.graph.refresh": ["Развёртка", "Refresh rate", "刷新率"],
757
+ "widget.info.factory_reset": ["Сброс настроек", "Factory reset", "恢复出厂设置"],
758
+ "widget.info.factory_reset_hint": ["Все настройки к заводским", "All settings to factory defaults", "所有设置恢复为出厂默认值"],
759
+ "widget.info.factory_reset_title": ["Сбросить все настройки?", "Reset all settings?", "重置所有设置?"],
760
+ "widget.info.factory_reset_text": [
761
+ "Все настройки прибора — Wi-Fi, имя, пароль Web, настройки модулей — вернутся к заводским, прибор перезагрузится. Связь по Wi-Fi может пропасть.",
762
+ "All device settings — Wi-Fi, name, Web password, module settings — return to factory defaults and the device restarts. The Wi-Fi connection may be lost.",
763
+ "设备的所有设置(Wi-Fi、名称、Web 密码、模块设置)将恢复为出厂默认值,设备将重启。Wi-Fi 连接可能会断开。",
764
+ ],
765
+ "widget.info.reset_command": ["Команда сброса настроек", "Factory reset command", "恢复出厂命令"],
757
766
  };
758
767
  export default translations;
package/dist/types.d.ts CHANGED
@@ -525,10 +525,15 @@ export interface IWidgetDeviceInfoProps {
525
525
  header?: string;
526
526
  argument?: string;
527
527
  };
528
+ resetCommand?: {
529
+ header?: string;
530
+ argument?: string;
531
+ };
528
532
  collapsed?: boolean;
529
533
  persistKey?: string;
530
534
  onSave?: (info: IWidgetDeviceInfoConfig) => void;
531
535
  onRestart?: () => void;
536
+ onFactoryReset?: () => void;
532
537
  }
533
538
  export interface IWidgetFileSystemFile {
534
539
  Name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.44.1",
3
+ "version": "1.46.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Oleg-Perevyshin/ElementsUI.git"