feat(editors): три диалога настроек собраны в карточки-группы

«Общие настройки», «Пространство» и «Устройство на плане» были плоскими
столбцами. В «Общих» девять подписей-заголовков шли вперемешку с рядами и
абзацами; в «Устройстве» на 760 строк формы не было ни одного деления, и
«Название» лежало в одном списке с «Дополнительными действиями».

Теперь это карточки общего набора: семь в «Общих», четыре в «Пространстве»,
пять в «Устройстве». Набор взят как есть — он поднят в #594 и доказан на диалоге
комнаты и редакторе панели (#597).

Шесть пояснений переехали под «?» к своему заголовку, подзаголовку или строке;
текст не переписан. Два сообщения о СОСТОЯНИИ остались на виду — решение
владельца: `gs.sun_missing` и `marker.run_target_gone` говорят, что настройка
сломана прямо сейчас, а второе ещё и называет пропавшую цель подстановкой,
которой в статической подсказке взяться неоткуда.

`gs.hint` при этом переехал из ленивого support-словаря в основной: «?» читает
текст через `_t`, а не `supportT`. Тест мёртвых ключей сливает оба словаря, так
что копия в support удалена во всех четырёх локалях.

Классы-опоры сохранены намеренно: `.srcrow`, `.dispsection`, `.gsrow`,
`.colorrow`, `.namein`, `.areasel`, `.tempin` остались на своих элементах, а
карточки добавились вокруг. За эти имена держатся одиннадцать смоков, и потеря
любого превратила бы ревью в чтение переписанных свидетелей.

Изменён ровно один смок — `smoke_general_settings`, и изменён в сторону
строгости: вместо одного списка подписей он снимает теперь два (заголовки
карточек и подзаголовки внутри), требует непустоты каждой карточки, проверяет
что радиус свечения лежит ВНУТРИ карточки свечения (подпись такой границей не
была), что четыре переехавших пояснения доступны кнопкой и что их абзацев в
форме не осталось.

`src/houseplan-onboarding-runtime.ts` затронут двумя строками: он второй
потребитель переименованных ключей `space.hide_*_tip` → `space.hide_*.help`.
Один текст — один ключ; дублировать его ради неприкосновенности файла хуже.

Потолок стартового графа поднят 291 400 → 292 500. Выросли СТРОКИ, а не код:
12 новых ключей в четырёх локалях плюс переехавший `gs.hint`. Разметка диалогов
как была в ленивом редакторском графе, так там и осталась.

Свидетели: `state-callout-hidden-under-help` прячет сообщение о состоянии под
«?» и краснит новый блок `smoke_tap_run`; `dialog-card-loses-its-heading`
обнуляет заголовок карточки и краснит `smoke_general_settings` — карточка без
заголовка это снова плоский список, только с рамкой.

Issue: #598
User-Visible: yes
This commit is contained in:
Claude
2026-09-19 17:52:49 +03:00
parent 2eaeb6f885
commit c22a1f43db
68 changed files with 1361 additions and 1094 deletions
+171 -140
View File
@@ -1,12 +1,17 @@
/**
* Диалог «Общие настройки» (#592: вынесен из houseplan-editor-runtime.ts как есть).
* Диалог «Общие настройки».
*
* Тело перенесено побайтово: тип `this` объявлен параметром, поэтому ни одна
* строка разметки, ни один обработчик и ни один якорь мутанта не переписаны.
* Состояние остаётся на хосте — модуль только рисует (#592, К2).
* #592 вынес его сюда побайтово; #598 собрал девять подписей-заголовков в семь
* карточек общего набора. Состояние по-прежнему живёт на хосте — модуль только
* рисует, и каждый контрол пишет в тот же ключ черновика, что и раньше (#598 К1).
*
* Классы `.srcrow`, `.dispsection`, `.gsrow`, `.colorrow`, `.namein`, `.areasel`,
* `.tempin` сохранены намеренно: за них держатся одиннадцать смоков, и потеря
* любого превратила бы ревью в чтение переписанных свидетелей (#598 К6).
*/
import { html, nothing, type TemplateResult } from 'lit';
import { formCard, segmented } from './form-kit';
import { langOf } from '../i18n';
import { supportT } from '../i18n/support';
import { DEFAULT_FILL_COLORS } from '../logic';
@@ -20,147 +25,173 @@ import type { HouseplanEditorRuntime } from '../houseplan-editor-runtime';
export function renderGeneralSettingsDialog(this: HouseplanEditorRuntime): TemplateResult {
return html`<hp-dialog .hass=${this.host.hass} data-kind="settings" .title=${this.host._t('gs.title')} icon="mdi:cog-outline" wide
@hp-close=${() => (this.host._settingsDialog = null)}>
<div class="body">
<div class="rhint">${supportT(
langOf(this.host.hass, this.host._config?.language), 'gs.hint',
)}</div>
<label class="srcrow">
${this._boolInput(this.host._settingsDialog!.showRoomTooltip, (v) =>
(this.host._settingsDialog = { ...this.host._settingsDialog!, showRoomTooltip: v }))}
<span>${supportT(
langOf(this.host.hass, this.host._config?.language), 'gs.show_room_tooltip',
)}</span>
</label>
<label class="srcrow">
${this._boolInput(this.host._settingsDialog!.radarShowLive, (v) =>
(this.host._settingsDialog = { ...this.host._settingsDialog!, radarShowLive: v }))}
<span>${this.host._t('gs.radar_show_live')}</span>
</label>
<div class="rhint">${this.host._t('gs.radar_show_live_hint')}</div>
<hp-zigbee-topology-settings .hass=${this.host.hass} .value=${this.host._settingsDialog!.zigbeeTopology} .savedEnabled=${zigbeeTopologySettingsOf(this.host._settings).enabled} .devices=${this.host._devices} .registry=${this.host._haRegistry} @hp-topology-settings-change=${(event: CustomEvent<ZigbeeTopologySettings>) => (this.host._settingsDialog = { ...this.host._settingsDialog!, zigbeeTopology: event.detail })}></hp-zigbee-topology-settings>
<label class="dispsection">${this.host._t('gs.light_group')}</label>
${this._renderColorRow('light_on', 'gs.light_on')}
${this._renderColorRow('light_off', 'gs.light_off')}
${this._renderColorRow('light_none', 'gs.light_none')}
<label class="dispsection">${this.host._t('gs.temp_group')}</label>
${this._renderColorRow('temp_cold', 'gs.temp_cold')}
${this._renderColorRow('temp_ok', 'gs.temp_ok')}
${this._renderColorRow('temp_hot', 'gs.temp_hot')}
<label class="dispsection">${this.host._t('gs.lqi_group')}</label>
${this._renderColorRow('lqi_low', 'gs.lqi_low')}
${this._renderColorRow('lqi_high', 'gs.lqi_high')}
<label class="dispsection">${this.host._t('gs.glow_group')}</label>
${this._renderColorRow('glow_base', 'gs.glow_base')}
${this._renderColorRow('glow_light', 'gs.glow_light')}
<div class="colorrow gsrow">
<span class="gsl help-inline-label"><label for="gs-glow-radius">${this.host._t('gs.glow_radius')}</label>
${this._help('gs.glow_radius.help')}</span>
<input id="gs-glow-radius" type="number" class="tempin" min="0.5" step="0.5"
.value=${String(this.host._settingsDialog!.glowRadius)}
@input=${(e: Event) => {
const v = strictNumber((e.target as HTMLInputElement).value);
if (v != null && v > 0)
this.host._settingsDialog = { ...this.host._settingsDialog!, glowRadius: v };
}} />
<span class="opl">${this.host._imperial ? this.host._t('gs.unit_ft') : this.host._t('gs.unit_m')}</span>
</div>
<label class="dispsection">${this.host._t('gs.wall_group')}</label>
${this._renderColorRow('wall_fill', 'gs.wall_fill')}
<label class="dispsection">${this.host._t('gs.bg_group')}</label>
<div class="colorrow gsrow">
<span class="gsl help-inline-label"><label for="gs-bg-mode">${this.host._t('gs.bg_mode')}</label>
${this._help('gs.bg_mode.help')}</span>
<select id="gs-bg-mode" class="areasel"
@change=${(e: Event) =>
(this.host._settingsDialog = { ...this.host._settingsDialog!, bgMode: (e.target as HTMLSelectElement).value === 'daynight' ? 'daynight' : 'static' })}>
<option value="static" ?selected=${this.host._settingsDialog!.bgMode === 'static'}>${this.host._t('gs.bg_static')}</option>
<option value="daynight" ?selected=${this.host._settingsDialog!.bgMode === 'daynight'}>${this.host._t('gs.bg_daynight')}</option>
</select>
</div>
${this.host._settingsDialog!.bgMode === 'static'
? html`<div class="colorrow gsrow">
<hp-color-opacity .label=${this.host._t('gs.bg_color')}
.pickerLabels=${this.host._colorPickerLabels}
.color=${this.host._settingsDialog!.bgColor || this.host._stageBgHex()}
.opacity=${1} .showOpacity=${false}
@hp-color-opacity-change=${(e: CustomEvent<{ color: string }>) => {
this.host._settingsDialog = { ...this.host._settingsDialog!, bgColor: e.detail.color };
}}></hp-color-opacity>
${this.host._settingsDialog!.bgColor
? html`<button class="btn ghost" @click=${() =>
(this.host._settingsDialog = { ...this.host._settingsDialog!, bgColor: null })}>${this.host._t('gs.bg_default')}</button>`
: html`<span class="opl">${this.host._t('gs.bg_theme')}</span>`}
</div>`
: nothing}
<label class="dispsection">${this.host._t('gs.sun_group')}</label>
${!sunStateOf(this.host.hass)
? html`<div class="rhint">${this.host._t('gs.sun_missing')}</div>`
: nothing}
<div class="sunrow">
${this.host._renderCompass()}
<div class="suncol">
<div class="helpfieldlabel compact">
<label for="gs-north">${this.host._t('gs.north')}</label>
${this._help('gs.north.help')}
</div>
<div class="colorrow">
<input id="gs-north" class="namein tempin" type="number" min="0" max="359" step="1"
placeholder=${this.host._t('gs.north_ph')}
.value=${this.host._settingsDialog!.northDeg === null ? '' : String(this.host._settingsDialog!.northDeg)}
<div class="body hpf-form">
${formCard({
title: this.host._t('gs.card_display'),
body: html`
<label class="srcrow">
${this._boolInput(this.host._settingsDialog!.showRoomTooltip, (v) =>
(this.host._settingsDialog = { ...this.host._settingsDialog!, showRoomTooltip: v }))}
<span>${supportT(
langOf(this.host.hass, this.host._config?.language), 'gs.show_room_tooltip',
)}</span>
</label>
<label class="srcrow">
${this._boolInput(this.host._settingsDialog!.radarShowLive, (v) =>
(this.host._settingsDialog = { ...this.host._settingsDialog!, radarShowLive: v }))}
<span>${this.host._t('gs.radar_show_live')}</span>
${this._help('gs.radar_show_live.help')}
</label>`,
})}
${formCard({
title: this.host._t('gs.card_zigbee'),
body: html`<hp-zigbee-topology-settings .hass=${this.host.hass} .value=${this.host._settingsDialog!.zigbeeTopology} .savedEnabled=${zigbeeTopologySettingsOf(this.host._settings).enabled} .devices=${this.host._devices} .registry=${this.host._haRegistry} @hp-topology-settings-change=${(event: CustomEvent<ZigbeeTopologySettings>) => (this.host._settingsDialog = { ...this.host._settingsDialog!, zigbeeTopology: event.detail })}></hp-zigbee-topology-settings>`,
})}
${formCard({
title: this.host._t('gs.card_fills'),
help: this._help('gs.card_fills.help'),
body: html`
<label class="dispsection">${this.host._t('gs.light_group')}</label>
${this._renderColorRow('light_on', 'gs.light_on')}
${this._renderColorRow('light_off', 'gs.light_off')}
${this._renderColorRow('light_none', 'gs.light_none')}
<label class="dispsection">${this.host._t('gs.temp_group')}</label>
${this._renderColorRow('temp_cold', 'gs.temp_cold')}
${this._renderColorRow('temp_ok', 'gs.temp_ok')}
${this._renderColorRow('temp_hot', 'gs.temp_hot')}
<label class="dispsection">${this.host._t('gs.lqi_group')}</label>
${this._renderColorRow('lqi_low', 'gs.lqi_low')}
${this._renderColorRow('lqi_high', 'gs.lqi_high')}`,
})}
${formCard({
title: this.host._t('gs.glow_group'),
body: html`
${this._renderColorRow('glow_base', 'gs.glow_base')}
${this._renderColorRow('glow_light', 'gs.glow_light')}
<div class="colorrow gsrow">
<span class="gsl help-inline-label"><label for="gs-glow-radius">${this.host._t('gs.glow_radius')}</label>
${this._help('gs.glow_radius.help')}</span>
<input id="gs-glow-radius" type="number" class="tempin" min="0.5" step="0.5"
.value=${String(this.host._settingsDialog!.glowRadius)}
@input=${(e: Event) => {
const raw = (e.target as HTMLInputElement).value.trim();
const n = raw === '' ? null : Math.round(Number(raw));
this.host._settingsDialog = {
...this.host._settingsDialog!,
northDeg: n !== null && Number.isFinite(n) ? Math.min(359, Math.max(0, n)) : null,
};
const v = strictNumber((e.target as HTMLInputElement).value);
if (v != null && v > 0)
this.host._settingsDialog = { ...this.host._settingsDialog!, glowRadius: v };
}} />
${this.host._settingsDialog!.northDeg !== null
? html`<button class="btn ghost" @click=${() =>
(this.host._settingsDialog = { ...this.host._settingsDialog!, northDeg: null })}>${this.host._t('gs.north_clear')}</button>`
: nothing}
<span class="opl">${this.host._imperial ? this.host._t('gs.unit_ft') : this.host._t('gs.unit_m')}</span>
</div>`,
})}
${formCard({
title: this.host._t('gs.card_plan'),
body: html`
<label class="dispsection">${this.host._t('gs.wall_group')}</label>
${this._renderColorRow('wall_fill', 'gs.wall_fill')}
<label class="dispsection">${this.host._t('gs.bg_group')}</label>
<div class="colorrow gsrow">
<span class="gsl help-inline-label"><label for="gs-bg-mode">${this.host._t('gs.bg_mode')}</label>
${this._help('gs.bg_mode.help')}</span>
${segmented({
name: 'gs-bg-mode',
value: this.host._settingsDialog!.bgMode === 'daynight' ? 'daynight' : 'static',
ariaLabel: this.host._t('gs.bg_mode'),
options: [
{ value: 'static', label: this.host._t('gs.bg_static') },
{ value: 'daynight', label: this.host._t('gs.bg_daynight') },
],
onChange: (value) =>
(this.host._settingsDialog = { ...this.host._settingsDialog!, bgMode: value === 'daynight' ? 'daynight' : 'static' }),
})}
</div>
</div>
</div>
<label class="srcrow">
${this._boolInput(this.host._settingsDialog!.sunRays, (v) =>
(this.host._settingsDialog = { ...this.host._settingsDialog!, sunRays: v }))}
<span>${this.host._t('gs.sun_rays')}</span>
</label>
${renderSunRayOriginSelect(this.host._settingsDialog!.sunRayOrigin, (key) => this.host._t(key), (sunRayOrigin) => (this.host._settingsDialog = { ...this.host._settingsDialog!, sunRayOrigin }))}
${this.host._canEdit ? html`
<label class="dispsection">${this.host._t('gs.backup_group')}</label>
<div class="rhint">${this.host._t('gs.backup_hint')}</div>
<div class="backupactions">
<button class="btn ghost" @click=${() => this._openBackupExport()}>
<ha-icon icon="mdi:download"></ha-icon>${this.host._t('backup.export_open')}
</button>
<span class="backupupload">
<button class="btn ghost" type="button" @click=${(e: Event) =>
((e.currentTarget as HTMLElement).nextElementSibling as HTMLInputElement | null)?.click()}>
<ha-icon icon="mdi:upload"></ha-icon>${this.host._t('backup.import_open')}
${this.host._settingsDialog!.bgMode === 'static'
? html`<div class="colorrow gsrow">
<hp-color-opacity .label=${this.host._t('gs.bg_color')}
.pickerLabels=${this.host._colorPickerLabels}
.color=${this.host._settingsDialog!.bgColor || this.host._stageBgHex()}
.opacity=${1} .showOpacity=${false}
@hp-color-opacity-change=${(e: CustomEvent<{ color: string }>) => {
this.host._settingsDialog = { ...this.host._settingsDialog!, bgColor: e.detail.color };
}}></hp-color-opacity>
${this.host._settingsDialog!.bgColor
? html`<button class="btn ghost" @click=${() =>
(this.host._settingsDialog = { ...this.host._settingsDialog!, bgColor: null })}>${this.host._t('gs.bg_default')}</button>`
: html`<span class="opl">${this.host._t('gs.bg_theme')}</span>`}
</div>`
: nothing}`,
})}
${formCard({
title: this.host._t('gs.sun_group'),
body: html`
${!sunStateOf(this.host.hass)
? html`<div class="rhint">${this.host._t('gs.sun_missing')}</div>`
: nothing}
<div class="sunrow">
${this.host._renderCompass()}
<div class="suncol">
<div class="helpfieldlabel compact">
<label for="gs-north">${this.host._t('gs.north')}</label>
${this._help('gs.north.help')}
</div>
<div class="colorrow">
<input id="gs-north" class="namein tempin" type="number" min="0" max="359" step="1"
placeholder=${this.host._t('gs.north_ph')}
.value=${this.host._settingsDialog!.northDeg === null ? '' : String(this.host._settingsDialog!.northDeg)}
@input=${(e: Event) => {
const raw = (e.target as HTMLInputElement).value.trim();
const n = raw === '' ? null : Math.round(Number(raw));
this.host._settingsDialog = {
...this.host._settingsDialog!,
northDeg: n !== null && Number.isFinite(n) ? Math.min(359, Math.max(0, n)) : null,
};
}} />
${this.host._settingsDialog!.northDeg !== null
? html`<button class="btn ghost" @click=${() =>
(this.host._settingsDialog = { ...this.host._settingsDialog!, northDeg: null })}>${this.host._t('gs.north_clear')}</button>`
: nothing}
</div>
</div>
</div>
<label class="srcrow">
${this._boolInput(this.host._settingsDialog!.sunRays, (v) =>
(this.host._settingsDialog = { ...this.host._settingsDialog!, sunRays: v }))}
<span>${this.host._t('gs.sun_rays')}</span>
</label>
${renderSunRayOriginSelect(this.host._settingsDialog!.sunRayOrigin, (key) => this.host._t(key), (sunRayOrigin) => (this.host._settingsDialog = { ...this.host._settingsDialog!, sunRayOrigin }))}`,
})}
${formCard({
title: this.host._t('gs.card_data'),
body: html`
${this.host._canEdit ? html`
<label class="dispsection">${this.host._t('gs.backup_group')}${this._help('gs.backup_group.help')}</label>
<div class="backupactions">
<button class="btn ghost" @click=${() => this._openBackupExport()}>
<ha-icon icon="mdi:download"></ha-icon>${this.host._t('backup.export_open')}
</button>
<span class="backupupload">
<button class="btn ghost" type="button" @click=${(e: Event) =>
((e.currentTarget as HTMLElement).nextElementSibling as HTMLInputElement | null)?.click()}>
<ha-icon icon="mdi:upload"></ha-icon>${this.host._t('backup.import_open')}
</button>
<input type="file" accept="application/json,.json" @change=${(event: Event) => this._pickBackupImport(event)} />
</span>
${this.host._canOptimizeUndo && this.host._undoKind === 'import' ? html`
<button class="btn ghost" @click=${() => this._undoPlanOptimization()}
?disabled=${this.host._optimizeUndoBusy}>
<ha-icon icon="mdi:undo-variant"></ha-icon>${this.host._t('backup.undo_import')}
</button>` : nothing}
</div>` : nothing}
<label class="dispsection">${this.host._t('gs.grid_group')}${this._help('gs.grid_group.help')}</label>
<div class="colorrow gsrow">
<button class="btn ghost alignall" @click=${() => this._openAlignDialog()}>
<ha-icon icon="mdi:broom"></ha-icon>${this.host._t('gs.align_all')}
</button>
<input type="file" accept="application/json,.json" @change=${(event: Event) => this._pickBackupImport(event)} />
</span>
${this.host._canOptimizeUndo && this.host._undoKind === 'import' ? html`
<button class="btn ghost" @click=${() => this._undoPlanOptimization()}
</div>
${this.host._canOptimizeUndo && this.host._undoKind !== 'import' ? html`<div class="colorrow gsrow">
<button class="btn ghost alignall" @click=${() => this._undoPlanOptimization()}
?disabled=${this.host._optimizeUndoBusy}>
<ha-icon icon="mdi:undo-variant"></ha-icon>${this.host._t('backup.undo_import')}
</button>` : nothing}
</div>` : nothing}
<label class="dispsection">${this.host._t('gs.grid_group')}</label>
<div class="rhint">${this.host._t('gs.grid_hint')}</div>
<div class="colorrow gsrow">
<button class="btn ghost alignall" @click=${() => this._openAlignDialog()}>
<ha-icon icon="mdi:broom"></ha-icon>${this.host._t('gs.align_all')}
</button>
</div>
${this.host._canOptimizeUndo && this.host._undoKind !== 'import' ? html`<div class="colorrow gsrow">
<button class="btn ghost alignall" @click=${() => this._undoPlanOptimization()}
?disabled=${this.host._optimizeUndoBusy}>
<ha-icon icon="mdi:undo-variant"></ha-icon>${this.host._t('gs.optimize_undo')}
</button>
</div>` : nothing}
<ha-icon icon="mdi:undo-variant"></ha-icon>${this.host._t('gs.optimize_undo')}
</button>
</div>` : nothing}`,
})}
</div>
<div class="row" slot="footer">
<button class="btn ghost" @click=${() =>
+28 -6
View File
@@ -7,6 +7,8 @@
*/
import { html, nothing, type TemplateResult } from 'lit';
import { formCard } from './form-kit';
import { resolveDevicePresentation } from '../device-presentation';
import { toggleEntityCandidates } from '../device-toggle';
import { recommendedValueBadgeSource, valueBadgeCandidates, valueBadgeSourceFromKey, valueBadgeSourceKey } from '../device-value-badge';
@@ -131,7 +133,10 @@ export function renderMarkerDialog(this: HouseplanEditorRuntime): TemplateResult
return html`<hp-dialog id="marker-dialog" .hass=${this.host.hass} data-kind="marker"
.title=${d.devId ? this.host._t('info.device_header') : this.host._t('marker.new_device')}
icon="mdi:shape-plus" wide @hp-close=${() => this._closeMarkerDialog()}>
<div class="body">
<div class="body hpf-form">
${formCard({
title: this.host._t('marker.card_basics'),
body: html`
${bindingStatus?.kind === 'ha_disabled'
? html`<div class="habindingbanner" role="status">
<ha-icon icon="mdi:power-plug-off-outline"></ha-icon>
@@ -260,7 +265,11 @@ export function renderMarkerDialog(this: HouseplanEditorRuntime): TemplateResult
</select>
${this._renderRadarSection(d, previewDevice)}
${this._renderVacSection(d)}
${this._renderVacSection(d)}`,
})}
${formCard({
title: this.host._t('marker.card_tap'),
body: html`
<label>${this.host._t('marker.tap_label')}</label>
<select id="marker-tap-action" class="areasel"
@@ -388,7 +397,11 @@ export function renderMarkerDialog(this: HouseplanEditorRuntime): TemplateResult
</button>`)}
</div>`
: nothing}
`,
})}
${formCard({
title: this.host._t('marker.card_light'),
body: html`
${this.host._bindingHasClimate(d.binding)
? html`<label class="srcrow climrow" title=${this.host._t('marker.use_climate_temp_tip')}>
${this._boolInput(d.useClimateTemp, (v) => (this.host._markerDialog = { ...d, useClimateTemp: v }))}
@@ -502,7 +515,11 @@ export function renderMarkerDialog(this: HouseplanEditorRuntime): TemplateResult
<ha-icon icon="mdi:information-outline"></ha-icon>${glowDisabledHint}
</p>`
: nothing}
`,
})}
${formCard({
title: this.host._t('marker.card_appearance'),
body: html`
<label>${this.host._t('marker.icon_label')}</label>
${customElements.get('ha-icon-picker')
// Feed the effective icon to HA's picker so its field renders both
@@ -691,7 +708,11 @@ export function renderMarkerDialog(this: HouseplanEditorRuntime): TemplateResult
line up with a wall that is not on a 10-degree grid. */}
${this._rangeInput(0, 355, 5, d.angle, (n) => (this.host._markerDialog = { ...d, angle: n }))}
<span class="opv">${d.angle}°</span>
</div>
</div>`,
})}
${formCard({
title: this.host._t('marker.card_details'),
body: html`
<label>${this.host._t('marker.model_label')}</label>
<input class="namein" type="text" placeholder=${this.host._t('marker.model_ph')}
@@ -724,7 +745,8 @@ export function renderMarkerDialog(this: HouseplanEditorRuntime): TemplateResult
@change=${(e: Event) => this._pickMarkerFiles(e)} />
</span>
</div>
${this._renderRadarSection(d, previewDevice, 'additional')}
${this._renderRadarSection(d, previewDevice, 'additional')}`,
})}
</div>
<div class="row markerfooter" slot="footer">
<div class="markeractions">
+83 -66
View File
@@ -7,6 +7,8 @@
*/
import { html, nothing, type TemplateResult } from 'lit';
import { formCard, segmented } from './form-kit';
import { gridCellFieldToCm, gridCellFieldValue } from '../grid-scale';
import { DEFAULT_CUSTOM_FILL, SPACE_FILL_UI_MODES, stageBgOf } from '../logic';
import { openSpaceCopyDialog, renderSpaceCopyDialog } from '../space-copy-runtime';
@@ -37,7 +39,10 @@ export function renderSpaceSettingsDialog(this: HouseplanEditorRuntime): Templat
return html`<hp-dialog .hass=${this.host.hass} data-kind="space"
.title=${`${d.mode === 'create' ? this.host._t('space.new') : this.host._t('space.header')}${progress ? ` · ${progress}` : ''}`}
icon="mdi:floor-plan" wide @hp-close=${close}>
<div class="body">
<div class="body hpf-form">
${formCard({
title: this.host._t('space.card_basics'),
body: html`
<label>${this.host._t('space.title_label')}</label>
<input class="namein" type="text" placeholder=${this.host._t('space.title_ph')}
.value=${d.title}
@@ -100,9 +105,11 @@ export function renderSpaceSettingsDialog(this: HouseplanEditorRuntime): Templat
<span class="opl">${this.host._t(
this.host._imperial ? 'space.scale_unit_imperial' : 'space.scale_unit',
)}</span>
</div>
<label class="dispsection">${this.host._t('space.display_section')}</label>
</div>`,
})}
${formCard({
title: this.host._t('space.display_section'),
body: html`
<label class="srcrow">
${this._boolInput(d.showBorders, (v) => (this.host._spaceDialog = touchSpaceDisplay(d, 'showBorders', v)))}
<span>${this.host._t('space.show_borders')}</span>
@@ -140,14 +147,76 @@ export function renderSpaceSettingsDialog(this: HouseplanEditorRuntime): Templat
<label class="srcrow">
${this._boolInput(d.hideDecor, (v) => (this.host._spaceDialog = { ...d, hideDecor: v }))}
<span>${this.host._t('space.hide_decor')}</span>
${this._help('space.hide_decor.help')}
</label>
<div class="rhint">${this.host._t('space.hide_decor_tip')}</div>
<label class="srcrow">
${this._boolInput(d.hideOpenings, (v) => (this.host._spaceDialog = { ...d, hideOpenings: v }))}
<span>${this.host._t('space.hide_openings')}</span>
${this._help('space.hide_openings.help')}
</label>
<div class="rhint">${this.host._t('space.hide_openings_tip')}</div>
<label class="dispsection">${this.host._t('space.roomcard_section')}</label>
<div class="colorrow">
<hp-color-opacity .label=${this.host._t('space.room_color')}
.opacityLabel=${this.host._t('space.opacity')}
.pickerLabels=${this.host._colorPickerLabels}
.color=${d.roomColor} .opacity=${d.roomOpacity} .showOpacity=${true}
@hp-color-opacity-change=${(e: CustomEvent<{ color: string; opacity: number }>) => {
this.host._spaceDialog = {
...d, roomColor: e.detail.color, roomOpacity: e.detail.opacity,
};
}}></hp-color-opacity>
</div>
<div class="helpfieldlabel">
<span>${this.host._t('space.fill_label')}</span>
${this._help('space.fill_mode.help')}
</div>
${SPACE_FILL_UI_MODES.map((v) => [v, 'fill.' + v] as const).map(
([v, k]) => html`<label class="srcrow">
<input type="radio" name="fillmode" .checked=${d.fillMode === v}
@change=${() => (this.host._spaceDialog = { ...d, fillMode: v as any })} />
<span>${this.host._t(k as any)}</span>
${v === 'temp' && d.fillMode === 'temp'
? html`<span class="temprange">
<input class="namein tempin" type="number" step="0.5" .value=${String(d.tempMin)}
@input=${(e: Event) => {
const n = strictNumber((e.target as HTMLInputElement).value);
if (n != null) this.host._spaceDialog = { ...d, tempMin: n };
}} />
–
<input class="namein tempin" type="number" step="0.5" .value=${String(d.tempMax)}
@input=${(e: Event) => {
const n = strictNumber((e.target as HTMLInputElement).value);
if (n != null) this.host._spaceDialog = { ...d, tempMax: n };
}} />
°C
</span>`
: nothing}
</label>
${v === 'custom' && d.fillMode === 'custom'
? html`<div class="colorrow gsrow">
<span class="gsl">${this.host._t('space.custom_fill')}</span>
<hp-color-opacity
.label=${this.host._t('space.custom_fill')}
.opacityLabel=${this.host._t('space.opacity')}
.pickerLabels=${this.host._colorPickerLabels}
.color=${(d.customFill || DEFAULT_CUSTOM_FILL).c}
.opacity=${(d.customFill || DEFAULT_CUSTOM_FILL).a}
@hp-color-opacity-change=${(e: CustomEvent<{ color: string; opacity: number }>) => {
this.host._spaceDialog = { ...d, customFill: { c: e.detail.color, a: e.detail.opacity } };
}}></hp-color-opacity>
${d.customFill
? html`<button class="btn ghost" type="button"
@click=${() => (this.host._spaceDialog = { ...d, customFill: null })}>
${this.host._t('btn.reset')}</button>`
: nothing}
</div>`
: nothing}`,
)}`,
})}
${formCard({
title: this.host._t('space.roomcard_section'),
body: html`
${([['labelTemp', 'space.label_temp'], ['labelHum', 'space.label_hum'],
['labelLqi', 'space.label_lqi'], ['labelLight', 'space.label_light']] as const).map(
([f, k]) => html`<label class="srcrow">
@@ -160,18 +229,11 @@ export function renderSpaceSettingsDialog(this: HouseplanEditorRuntime): Templat
${this._rangeInput(50, 300, 5, Math.round(d.cardFontScale * 100), (n) => (this.host._spaceDialog = { ...d, cardFontScale: n / 100 }))}
<span class="opv">${Math.round(d.cardFontScale * 100)}%</span>
</div>
${this.host._renderCardPreview(d.cardFontScale, 1, 1)}
<div class="colorrow">
<hp-color-opacity .label=${this.host._t('space.room_color')}
.opacityLabel=${this.host._t('space.opacity')}
.pickerLabels=${this.host._colorPickerLabels}
.color=${d.roomColor} .opacity=${d.roomOpacity} .showOpacity=${true}
@hp-color-opacity-change=${(e: CustomEvent<{ color: string; opacity: number }>) => {
this.host._spaceDialog = {
...d, roomColor: e.detail.color, roomOpacity: e.detail.opacity,
};
}}></hp-color-opacity>
</div>
${this.host._renderCardPreview(d.cardFontScale, 1, 1)}`,
})}
${formCard({
title: this.host._t('space.card_sun'),
body: html`
<div class="helpfieldlabel">
<label for="space-bg-mode">${this.host._t('space.bg_mode')}</label>
${this._help('space.bg_mode.help')}
@@ -229,58 +291,13 @@ export function renderSpaceSettingsDialog(this: HouseplanEditorRuntime): Templat
<option value="1" ?selected=${d.sunRays === true}>${this.host._t('space.sun_on')}</option>
<option value="0" ?selected=${d.sunRays === false}>${this.host._t('space.sun_off')}</option>
</select>
<div class="helpfieldlabel">
<span>${this.host._t('space.fill_label')}</span>
${this._help('space.fill_mode.help')}
</div>
${SPACE_FILL_UI_MODES.map((v) => [v, 'fill.' + v] as const).map(
([v, k]) => html`<label class="srcrow">
<input type="radio" name="fillmode" .checked=${d.fillMode === v}
@change=${() => (this.host._spaceDialog = { ...d, fillMode: v as any })} />
<span>${this.host._t(k as any)}</span>
${v === 'temp' && d.fillMode === 'temp'
? html`<span class="temprange">
<input class="namein tempin" type="number" step="0.5" .value=${String(d.tempMin)}
@input=${(e: Event) => {
const n = strictNumber((e.target as HTMLInputElement).value);
if (n != null) this.host._spaceDialog = { ...d, tempMin: n };
}} />
–
<input class="namein tempin" type="number" step="0.5" .value=${String(d.tempMax)}
@input=${(e: Event) => {
const n = strictNumber((e.target as HTMLInputElement).value);
if (n != null) this.host._spaceDialog = { ...d, tempMax: n };
}} />
°C
</span>`
: nothing}
</label>
${v === 'custom' && d.fillMode === 'custom'
? html`<div class="colorrow gsrow">
<span class="gsl">${this.host._t('space.custom_fill')}</span>
<hp-color-opacity
.label=${this.host._t('space.custom_fill')}
.opacityLabel=${this.host._t('space.opacity')}
.pickerLabels=${this.host._colorPickerLabels}
.color=${(d.customFill || DEFAULT_CUSTOM_FILL).c}
.opacity=${(d.customFill || DEFAULT_CUSTOM_FILL).a}
@hp-color-opacity-change=${(e: CustomEvent<{ color: string; opacity: number }>) => {
this.host._spaceDialog = { ...d, customFill: { c: e.detail.color, a: e.detail.opacity } };
}}></hp-color-opacity>
${d.customFill
? html`<button class="btn ghost" type="button"
@click=${() => (this.host._spaceDialog = { ...d, customFill: null })}>
${this.host._t('btn.reset')}</button>`
: nothing}
</div>`
: nothing}`,
)}
<label class="srcrow">
${this._boolInput(d.glowEnabled, (checked) => {
this.host._spaceDialog = { ...d, glowEnabled: checked };
})}
<span>${this.host._t('space.glow_enabled')}</span>
</label>
</label>`,
})}
${d.deleteBlockers
? html`<div class="backuperror" role="alert">${this.host._t('space.delete_blocked', {
n: String(d.deleteBlockers),
+2 -2
View File
@@ -745,14 +745,14 @@ export class HouseplanOnboardingRuntime {
})}
<span>${this.host._t('space.hide_decor')}</span>
</label>
<div class="rhint">${this.host._t('space.hide_decor_tip')}</div>
<div class="rhint">${this.host._t('space.hide_decor.help')}</div>
<label class="srcrow">
${this._boolInput(dialog.hideOpenings, (value) => {
this.host._spaceDialog = { ...dialog, hideOpenings: value };
})}
<span>${this.host._t('space.hide_openings')}</span>
</label>
<div class="rhint">${this.host._t('space.hide_openings_tip')}</div>
<div class="rhint">${this.host._t('space.hide_openings.help')}</div>
<label class="dispsection">${this.host._t('space.roomcard_section')}</label>
${([
['labelTemp', 'space.label_temp'], ['labelHum', 'space.label_hum'],
+25 -6
View File
@@ -469,7 +469,6 @@
"gs.reset": "Auf Standard zurücksetzen",
"gs.saved": "Allgemeine Einstellungen gespeichert",
"gs.radar_show_live": "Live-Präsenz auf dem Plan anzeigen",
"gs.radar_show_live_hint": "Blendet alle Live-Radarziele ein oder aus. Eine Beobachtungsaufzeichnung wird dadurch weder gestartet noch beendet.",
"radar.title": "Präsenz auf dem Plan",
"radar.help": "Verbinde diese Markierung mit exakten Home-Assistant-Radarentitäten und wähle Position sowie Koordinatenprofil. Live-Positionen werden auf den ausgewählten Raum begrenzt.",
"radar.help.aria": "Hilfe: Einstellungen des Präsenzradars",
@@ -576,9 +575,7 @@
"radar.report_age": "vor {seconds} s",
"space.show_lqi": "Zigbee-Signal (LQI) neben Geräten anzeigen",
"space.hide_decor": "Dekorationsschicht ausblenden",
"space.hide_decor_tip": "Linien, Formen, Beschriftungen und Möbel bleiben an ihrem Platz — im Hintergrundeditor sichtbar, aber nicht auf dem Plan.",
"space.hide_openings": "Öffnungen ausblenden",
"space.hide_openings_tip": "Tür-, Fenster- und Tor-Symbole werden nicht gezeichnet, aber die Öffnungen funktionieren weiterhin: Licht fällt hindurch, die Sonne kommt durch ein Fenster herein, Kontaktsensoren öffnen sie weiterhin. Der Planeditor zeigt sie immer an.",
"gs.light_none": "Keine Lichtquellen",
"mode.plan": "Planeditor",
"mode.devices": "Geräteeditor",
@@ -1032,7 +1029,6 @@
"canvas.show_far": "Anzeigen",
"canvas.home_tip": "Der Plan ist dort drüben — klicken, um ihn anzupassen",
"gs.grid_group": "Planwartung",
"gs.grid_hint": "Aktualisiert Datenmodelle, richtet Planelemente am Raster aus und fasst redundante Wandfragmente zusammen. Ein exakter Bericht wird angezeigt, bevor etwas gespeichert wird.",
"gs.align_all": "Pläne optimieren",
"gs.align_title": "Pläne optimieren",
"gs.align_none": "Alle Pläne verwenden bereits das aktuelle optimierte Datenmodell.",
@@ -1254,7 +1250,6 @@
"vac.residual_message": "Die zugeordneten Räume weichen um bis zu {error} voneinander ab. Diese ungefähre Kalibrierung anwenden, manuell verfeinern oder ohne Änderung der gespeicherten Einrichtung abbrechen.",
"vac.apply_proposal": "Anwenden",
"gs.backup_group": "Sichern und übertragen",
"gs.backup_hint": "Laden Sie ein tragbares JSON-Backup herunter oder sehen Sie eine Vorschau und importieren Sie ein Backup, das mit House Plan erstellt wurde.",
"backup.export_open": "Exportieren",
"backup.import_open": "Importieren",
"backup.export_title": "House Plan exportieren",
@@ -1384,5 +1379,29 @@
"backdrop.keep_original": "Original behalten",
"backdrop.too_large_title": "Bild ist zu groß",
"backdrop.too_large_body": "Dieses Bild überschreitet die Anzeigegrenzen des Browsers ({w}×{h}, Limit {limit} px pro Seite). Bitte verkleinern Sie es in einem Desktop-Editor und laden Sie es erneut hoch.",
"backdrop.downscale_failed": "Die verkleinerte Kopie konnte nicht erstellt werden. Der aktuelle Plan wurde nicht geändert."
"backdrop.downscale_failed": "Die verkleinerte Kopie konnte nicht erstellt werden. Der aktuelle Plan wurde nicht geändert.",
"gs.card_display": "Anzeige",
"gs.card_zigbee": "Zigbee-Verbindungen",
"gs.card_fills": "Füllfarben der Räume",
"gs.card_plan": "Plan",
"gs.card_data": "Daten",
"gs.radar_show_live.help.aria": "Über die Live-Radaranwesenheit",
"gs.card_fills.help.aria": "Über die Füllfarben der Räume",
"gs.backup_group.help.aria": "Über Sicherung und Übertragung",
"gs.grid_group.help.aria": "Über die Planwartung",
"gs.radar_show_live.help": "Blendet alle Live-Radarziele ein oder aus. Eine Beobachtungsaufzeichnung wird dadurch weder gestartet noch beendet.",
"gs.backup_group.help": "Laden Sie ein tragbares JSON-Backup herunter oder sehen Sie eine Vorschau und importieren Sie ein Backup, das mit House Plan erstellt wurde.",
"gs.grid_group.help": "Aktualisiert Datenmodelle, richtet Planelemente am Raster aus und fasst redundante Wandfragmente zusammen. Ein exakter Bericht wird angezeigt, bevor etwas gespeichert wird.",
"gs.card_fills.help": "Füllfarben gelten für jeden Bereich; jede Farbe hat ihre eigene Deckkraft. Der Füllmodus wird in den Einstellungen des jeweiligen Bereichs festgelegt.",
"space.card_basics": "Allgemein",
"space.card_sun": "Sonne und Licht",
"space.hide_decor.help.aria": "Über das Ausblenden der Dekorschicht",
"space.hide_openings.help.aria": "Über das Ausblenden der Öffnungen",
"space.hide_decor.help": "Linien, Formen, Beschriftungen und Möbel bleiben an ihrem Platz — im Hintergrundeditor sichtbar, aber nicht auf dem Plan.",
"space.hide_openings.help": "Tür-, Fenster- und Tor-Symbole werden nicht gezeichnet, aber die Öffnungen funktionieren weiterhin: Licht fällt hindurch, die Sonne kommt durch ein Fenster herein, Kontaktsensoren öffnen sie weiterhin. Der Planeditor zeigt sie immer an.",
"marker.card_basics": "Allgemein",
"marker.card_tap": "Tippaktion",
"marker.card_light": "Licht und Schein",
"marker.card_appearance": "Aussehen",
"marker.card_details": "Details"
}
+25 -6
View File
@@ -469,7 +469,6 @@
"gs.reset": "Reset to defaults",
"gs.saved": "General settings saved",
"gs.radar_show_live": "Show live presence on the plan",
"gs.radar_show_live_hint": "Hides or shows all live radar targets. This does not start or stop observation recording.",
"radar.title": "Presence on the plan",
"radar.help": "Connect this marker to exact Home Assistant radar entities, place the sensor and choose its coordinate profile. Live positions are clipped to the selected room.",
"radar.help.aria": "Help: presence radar settings",
@@ -576,9 +575,7 @@
"radar.report_age": "{seconds} s ago",
"space.show_lqi": "Show zigbee signal (LQI) next to devices",
"space.hide_decor": "Hide the decorative layer",
"space.hide_decor_tip": "Lines, shapes, labels and furniture stay where they are — visible in the backdrop editor, not on the plan.",
"space.hide_openings": "Hide openings",
"space.hide_openings_tip": "Door, window and gate symbols are not drawn, but the openings keep working: light passes through, the sun comes in at a window, contact sensors still open them. The plan editor always shows them.",
"gs.light_none": "No light sources",
"mode.plan": "Plan editor",
"mode.devices": "Device editor",
@@ -1032,7 +1029,6 @@
"canvas.show_far": "Show",
"canvas.home_tip": "The plan is over there — click to fit it",
"gs.grid_group": "Plan maintenance",
"gs.grid_hint": "Updates data models, aligns plan elements to the grid and merges redundant wall fragments. An exact report is shown before anything is stored.",
"gs.align_all": "Optimize plans",
"gs.align_title": "Optimize plans",
"gs.align_none": "All plans already use the current optimized data model.",
@@ -1254,7 +1250,6 @@
"vac.residual_message": "The matched rooms disagree by up to {error}. Apply this approximate calibration, refine it manually, or cancel without changing the saved setup.",
"vac.apply_proposal": "Apply",
"gs.backup_group": "Backup and transfer",
"gs.backup_hint": "Download a portable JSON backup or preview and import a backup made by House Plan.",
"backup.export_open": "Export",
"backup.import_open": "Import",
"backup.export_title": "Export House Plan",
@@ -1384,5 +1379,29 @@
"backdrop.keep_original": "Keep the original",
"backdrop.too_large_title": "Image is too large",
"backdrop.too_large_body": "This image exceeds what browsers can display ({w}×{h}, limit {limit} px per side). Please reduce it in a desktop editor and upload again.",
"backdrop.downscale_failed": "Could not create the reduced copy. The original plan was not changed."
"backdrop.downscale_failed": "Could not create the reduced copy. The original plan was not changed.",
"gs.card_display": "Display",
"gs.card_zigbee": "Zigbee links",
"gs.card_fills": "Room fill colors",
"gs.card_plan": "Plan",
"gs.card_data": "Data",
"gs.radar_show_live.help.aria": "About live radar presence",
"gs.card_fills.help.aria": "About room fill colors",
"gs.backup_group.help.aria": "About backup and transfer",
"gs.grid_group.help.aria": "About plan maintenance",
"gs.radar_show_live.help": "Hides or shows all live radar targets. This does not start or stop observation recording.",
"gs.backup_group.help": "Download a portable JSON backup or preview and import a backup made by House Plan.",
"gs.grid_group.help": "Updates data models, aligns plan elements to the grid and merges redundant wall fragments. An exact report is shown before anything is stored.",
"gs.card_fills.help": "Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.",
"space.card_basics": "Basics",
"space.card_sun": "Sun & light",
"space.hide_decor.help.aria": "About hiding the decorative layer",
"space.hide_openings.help.aria": "About hiding openings",
"space.hide_decor.help": "Lines, shapes, labels and furniture stay where they are — visible in the backdrop editor, not on the plan.",
"space.hide_openings.help": "Door, window and gate symbols are not drawn, but the openings keep working: light passes through, the sun comes in at a window, contact sensors still open them. The plan editor always shows them.",
"marker.card_basics": "Basics",
"marker.card_tap": "Tap action",
"marker.card_light": "Light and glow",
"marker.card_appearance": "Appearance",
"marker.card_details": "Details"
}
+25 -6
View File
@@ -469,7 +469,6 @@
"gs.reset": "Réinitialiser par défaut",
"gs.saved": "Paramètres généraux enregistrés",
"gs.radar_show_live": "Afficher la présence en direct sur le plan",
"gs.radar_show_live_hint": "Masque ou affiche toutes les cibles radar en direct. Cela ne démarre ni n’arrête l’enregistrement des observations.",
"radar.title": "Présence sur le plan",
"radar.help": "Reliez ce marqueur aux entités radar exactes de Home Assistant, placez le capteur et choisissez son profil de coordonnées. Les positions en direct sont limitées à la pièce sélectionnée.",
"radar.help.aria": "Aide : paramètres du radar de présence",
@@ -576,9 +575,7 @@
"radar.report_age": "il y a {seconds} s",
"space.show_lqi": "Afficher le signal Zigbee (LQI) à côté des appareils",
"space.hide_decor": "Masquer la couche décorative",
"space.hide_decor_tip": "Les lignes, formes, étiquettes et meubles restent à leur place — visibles dans l’éditeur d’arrière-plan, mais pas sur le plan.",
"space.hide_openings": "Masquer les ouvertures",
"space.hide_openings_tip": "Les symboles de portes, fenêtres et portails ne sont pas dessinés, mais les ouvertures continuent de fonctionner : la lumière les traverse, le soleil entre par les fenêtres et les capteurs de contact continuent de les ouvrir. L’éditeur de plan les affiche toujours.",
"gs.light_none": "Aucune source lumineuse",
"mode.plan": "Éditeur de plan",
"mode.devices": "Éditeur d’appareils",
@@ -1032,7 +1029,6 @@
"canvas.show_far": "Afficher",
"canvas.home_tip": "Le plan est là-bas — cliquez pour l’ajuster à l’écran",
"gs.grid_group": "Maintenance du plan",
"gs.grid_hint": "Met à jour les modèles de données, aligne les éléments du plan sur la grille et fusionne les fragments de murs redondants. Un rapport précis est affiché avant tout enregistrement.",
"gs.align_all": "Optimiser les plans",
"gs.align_title": "Optimiser les plans",
"gs.align_none": "Tous les plans utilisent déjà le modèle de données optimisé actuel.",
@@ -1254,7 +1250,6 @@
"vac.residual_message": "Les pièces correspondantes présentent un écart maximal de {error}. Appliquez cette calibration approximative, affinez-la manuellement ou annulez sans modifier la configuration enregistrée.",
"vac.apply_proposal": "Appliquer",
"gs.backup_group": "Sauvegarde et transfert",
"gs.backup_hint": "Téléchargez une sauvegarde JSON portable ou prévisualisez et importez une sauvegarde créée par House Plan.",
"backup.export_open": "Exporter",
"backup.import_open": "Importer",
"backup.export_title": "Exporter House Plan",
@@ -1384,5 +1379,29 @@
"backdrop.keep_original": "Conserver l'original",
"backdrop.too_large_title": "Image trop grande",
"backdrop.too_large_body": "Cette image dépasse ce que les navigateurs peuvent afficher ({w}×{h}, limite {limit} px par côté). Réduisez-la dans un éditeur de bureau puis téléversez-la à nouveau.",
"backdrop.downscale_failed": "Impossible de créer la copie réduite. Le plan actuel n'a pas été modifié."
"backdrop.downscale_failed": "Impossible de créer la copie réduite. Le plan actuel n'a pas été modifié.",
"gs.card_display": "Affichage",
"gs.card_zigbee": "Liens Zigbee",
"gs.card_fills": "Couleurs de remplissage des pièces",
"gs.card_plan": "Plan",
"gs.card_data": "Données",
"gs.radar_show_live.help.aria": "À propos de la présence radar en direct",
"gs.card_fills.help.aria": "À propos des couleurs de remplissage",
"gs.backup_group.help.aria": "À propos de la sauvegarde et du transfert",
"gs.grid_group.help.aria": "À propos de la maintenance du plan",
"gs.radar_show_live.help": "Masque ou affiche toutes les cibles radar en direct. Cela ne démarre ni n’arrête l’enregistrement des observations.",
"gs.backup_group.help": "Téléchargez une sauvegarde JSON portable ou prévisualisez et importez une sauvegarde créée par House Plan.",
"gs.grid_group.help": "Met à jour les modèles de données, aligne les éléments du plan sur la grille et fusionne les fragments de murs redondants. Un rapport précis est affiché avant tout enregistrement.",
"gs.card_fills.help": "Les couleurs de remplissage s’appliquent à tous les espaces ; chaque couleur possède sa propre opacité. Le mode de remplissage utilisé par un espace se règle dans la boîte de dialogue de cet espace.",
"space.card_basics": "Général",
"space.card_sun": "Soleil et lumière",
"space.hide_decor.help.aria": "À propos du masquage de la couche décorative",
"space.hide_openings.help.aria": "À propos du masquage des ouvertures",
"space.hide_decor.help": "Les lignes, formes, étiquettes et meubles restent à leur place — visibles dans l’éditeur d’arrière-plan, mais pas sur le plan.",
"space.hide_openings.help": "Les symboles de portes, fenêtres et portails ne sont pas dessinés, mais les ouvertures continuent de fonctionner : la lumière les traverse, le soleil entre par les fenêtres et les capteurs de contact continuent de les ouvrir. L’éditeur de plan les affiche toujours.",
"marker.card_basics": "Général",
"marker.card_tap": "Action au toucher",
"marker.card_light": "Lumière et halo",
"marker.card_appearance": "Apparence",
"marker.card_details": "Détails"
}
+25 -6
View File
@@ -469,7 +469,6 @@
"gs.reset": "Сбросить к умолчаниям",
"gs.saved": "Общие настройки сохранены",
"gs.radar_show_live": "Показывать текущее присутствие на плане",
"gs.radar_show_live_hint": "Скрывает или показывает все живые цели радаров. Это не запускает и не останавливает запись наблюдений.",
"radar.title": "Присутствие на плане",
"radar.help": "Свяжите маркер с точными сущностями радара Home Assistant, укажите положение датчика и профиль координат. Живые позиции обрезаются по выбранной комнате.",
"radar.help.aria": "Подсказка: настройки радара присутствия",
@@ -576,9 +575,7 @@
"radar.report_age": "{seconds} с назад",
"space.show_lqi": "Показывать зигби-сигнал (LQI) у устройств",
"space.hide_decor": "Скрыть декоративный слой",
"space.hide_decor_tip": "Линии, фигуры, надписи и мебель остаются на месте — их видно в редакторе подложки, но не на плане.",
"space.hide_openings": "Скрыть проёмы",
"space.hide_openings_tip": "Двери, окна и ворота не рисуются, но продолжают работать: свет проходит, солнце светит в окна, датчики открытия срабатывают. В редакторе плана проёмы видно всегда.",
"gs.light_none": "Нет источников света",
"mode.plan": "Редактор плана",
"mode.devices": "Редактор устройств",
@@ -1032,7 +1029,6 @@
"canvas.show_far": "Показать",
"canvas.home_tip": "План там — нажмите, чтобы вписать",
"gs.grid_group": "Обслуживание планов",
"gs.grid_hint": "Обновляет модели данных, выравнивает элементы по сетке и объединяет лишние фрагменты стен. Перед записью будет показан точный отчёт.",
"gs.align_all": "Оптимизировать планы",
"gs.align_title": "Оптимизировать планы",
"gs.align_none": "Все планы уже используют актуальную и оптимальную модель данных.",
@@ -1254,7 +1250,6 @@
"vac.residual_message": "Совпавшие комнаты расходятся максимум на {error}. Примените приблизительную калибровку, подгоните её вручную либо отмените без изменения сохранённых настроек.",
"vac.apply_proposal": "Применить",
"gs.backup_group": "Резервная копия и перенос",
"gs.backup_hint": "Скачайте переносимую JSON-копию или проверьте и импортируйте копию, созданную House Plan.",
"backup.export_open": "Экспорт",
"backup.import_open": "Импорт",
"backup.export_title": "Экспорт House Plan",
@@ -1384,5 +1379,29 @@
"backdrop.keep_original": "Оставить оригинал",
"backdrop.too_large_title": "Изображение слишком большое",
"backdrop.too_large_body": "Изображение превышает возможности браузера ({w}×{h}, предел {limit} px по стороне). Уменьшите его в редакторе на компьютере и загрузите снова.",
"backdrop.downscale_failed": "Не удалось создать уменьшенную копию. Текущий план не изменён."
"backdrop.downscale_failed": "Не удалось создать уменьшенную копию. Текущий план не изменён.",
"gs.card_display": "Отображение",
"gs.card_zigbee": "Связи Zigbee",
"gs.card_fills": "Цвета заливки комнат",
"gs.card_plan": "План",
"gs.card_data": "Данные",
"gs.radar_show_live.help.aria": "О живом присутствии радаров",
"gs.card_fills.help.aria": "О цветах заливки комнат",
"gs.backup_group.help.aria": "О резервной копии и переносе",
"gs.grid_group.help.aria": "Об обслуживании плана",
"gs.radar_show_live.help": "Скрывает или показывает все живые цели радаров. Это не запускает и не останавливает запись наблюдений.",
"gs.backup_group.help": "Скачайте переносимую JSON-копию или проверьте и импортируйте копию, созданную House Plan.",
"gs.grid_group.help": "Обновляет модели данных, выравнивает элементы по сетке и объединяет лишние фрагменты стен. Перед записью будет показан точный отчёт.",
"gs.card_fills.help": "Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.",
"space.card_basics": "Основное",
"space.card_sun": "Солнце и свет",
"space.hide_decor.help.aria": "О скрытии декоративного слоя",
"space.hide_openings.help.aria": "О скрытии проёмов",
"space.hide_decor.help": "Линии, фигуры, надписи и мебель остаются на месте — их видно в редакторе подложки, но не на плане.",
"space.hide_openings.help": "Двери, окна и ворота не рисуются, но продолжают работать: свет проходит, солнце светит в окна, датчики открытия срабатывают. В редакторе плана проёмы видно всегда.",
"marker.card_basics": "Основное",
"marker.card_tap": "Действие по нажатию",
"marker.card_light": "Свет и свечение",
"marker.card_appearance": "Внешний вид",
"marker.card_details": "Подробности"
}
-1
View File
@@ -1,5 +1,4 @@
{
"gs.hint": "Füllfarben gelten für jeden Bereich; jede Farbe hat ihre eigene Deckkraft. Der Füllmodus wird in den Einstellungen des jeweiligen Bereichs festgelegt.",
"gs.show_room_tooltip": "Rauminformationen beim Darüberfahren anzeigen",
"support.about_group": "Über die Karte",
"support.guide_group": "Dokumentation",
-1
View File
@@ -1,5 +1,4 @@
{
"gs.hint": "Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.",
"gs.show_room_tooltip": "Show the room information window on hover",
"support.about_group": "About the card",
"support.guide_group": "Documentation",
-1
View File
@@ -1,5 +1,4 @@
{
"gs.hint": "Les couleurs de remplissage s’appliquent à tous les espaces ; chaque couleur possède sa propre opacité. Le mode de remplissage utilisé par un espace se règle dans la boîte de dialogue de cet espace.",
"gs.show_room_tooltip": "Afficher les informations de la pièce au survol",
"support.about_group": "À propos de la carte",
"support.guide_group": "Documentation d’aide",
-1
View File
@@ -1,5 +1,4 @@
{
"gs.hint": "Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.",
"gs.show_room_tooltip": "Показывать окно с информацией при наведении на комнату",
"support.about_group": "О карточке",
"support.guide_group": "Документация",