feat v1.1.0: vector floor plans (REMPLANNER SVG, auto-aligned scale/offset) + icon size proportional to plan width (icon_size = % of width, default 2.5)

This commit is contained in:
JB
2026-07-03 22:58:00 +03:00
parent 17f8e8164e
commit 7f9bbc1226
8 changed files with 158 additions and 157 deletions
+2
View File
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 138 KiB

+2
View File
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 76 KiB

File diff suppressed because one or more lines are too long
+62 -61
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "houseplan-card", "name": "houseplan-card",
"version": "1.0.1", "version": "1.1.0",
"description": "Interactive house plan Lovelace card for Home Assistant (dacha Kirillovskoe)", "description": "Interactive house plan Lovelace card for Home Assistant (dacha Kirillovskoe)",
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -16,7 +16,7 @@ const SCHEMA = [
}, },
}, },
}, },
{ name: 'icon_size', selector: { number: { min: 14, max: 48, mode: 'box' } } }, { name: 'icon_size', selector: { number: { min: 1, max: 6, step: 0.1, mode: 'box' } } },
{ name: 'show_temperature', selector: { boolean: {} } }, { name: 'show_temperature', selector: { boolean: {} } },
{ name: 'live_states', selector: { boolean: {} } }, { name: 'live_states', selector: { boolean: {} } },
]; ];
@@ -24,7 +24,7 @@ const SCHEMA = [
const LABELS: Record<string, string> = { const LABELS: Record<string, string> = {
title: 'Заголовок', title: 'Заголовок',
default_floor: 'Этаж по умолчанию', default_floor: 'Этаж по умолчанию',
icon_size: 'Размер иконок, px', icon_size: 'Размер иконок, % ширины плана',
show_temperature: 'Показывать температуру', show_temperature: 'Показывать температуру',
live_states: 'Живые состояния (вкл/выкл, открыто…)', live_states: 'Живые состояния (вкл/выкл, открыто…)',
}; };
+23 -29
View File
@@ -4,12 +4,12 @@
* через WS-команды интеграции `houseplan` (fallback localStorage). * через WS-команды интеграции `houseplan` (fallback localStorage).
*/ */
import { LitElement, html, svg, css, nothing, TemplateResult, PropertyValues } from 'lit'; import { LitElement, html, svg, css, nothing, TemplateResult, PropertyValues } from 'lit';
import { ROOMS, FLOOR_VB, FLOOR_TITLES, AREA_NAMES, IMG_W, IMG_H, Room } from './data/house'; import { ROOMS, FLOOR_VB, FLOOR_TITLES, AREA_NAMES, Room } from './data/house';
import { FLOOR_BG } from './data/backgrounds'; import { FLOOR_BG, FLOOR_BG_RECT } from './data/backgrounds';
import { EXCLUDED_DOMAINS, GROUP_TITLES, iconFor, DOMAIN_PRIORITY } from './rules'; import { EXCLUDED_DOMAINS, GROUP_TITLES, iconFor, DOMAIN_PRIORITY } from './rules';
import './editor'; import './editor';
const CARD_VERSION = '1.0.1'; const CARD_VERSION = '1.1.0';
const LS_KEY = 'houseplan_card_layout_v1'; const LS_KEY = 'houseplan_card_layout_v1';
interface DevItem { interface DevItem {
@@ -100,7 +100,7 @@ class HouseplanCard extends LitElement {
} }
public setConfig(config: CardConfig): void { public setConfig(config: CardConfig): void {
this._config = { icon_size: 22, show_temperature: true, live_states: true, ...config }; this._config = { icon_size: 2.5, show_temperature: true, live_states: true, ...config };
if (config.default_floor) this._floor = config.default_floor; if (config.default_floor) this._floor = config.default_floor;
} }
@@ -463,9 +463,12 @@ class HouseplanCard extends LitElement {
if (!this._config || !this.hass) return nothing; if (!this._config || !this.hass) return nothing;
const vb = FLOOR_VB[this._floor]; const vb = FLOOR_VB[this._floor];
const bg = FLOOR_BG[this._floor]; const bg = FLOOR_BG[this._floor];
const bgRect = FLOOR_BG_RECT[this._floor];
const rooms = this._rooms.filter((r) => r.floor === this._floor && r.area); const rooms = this._rooms.filter((r) => r.floor === this._floor && r.area);
const devs = this._devices.filter((d) => d.floor === this._floor); const devs = this._devices.filter((d) => d.floor === this._floor);
const iconSize = this._config.icon_size || 22; // размер иконки в % от ширины видимой области плана (legacy px-значения > 8 игнорируются)
const cfgSize = this._config.icon_size ?? 2.5;
const iconPct = cfgSize > 8 ? 2.5 : cfgSize;
return html` return html`
<ha-card> <ha-card>
@@ -499,8 +502,8 @@ class HouseplanCard extends LitElement {
<div class="stage ${this._edit ? 'edit' : ''}" style="aspect-ratio:${vb[2]}/${vb[3]}"> <div class="stage ${this._edit ? 'edit' : ''}" style="aspect-ratio:${vb[2]}/${vb[3]}">
<svg viewBox="${vb.join(' ')}" preserveAspectRatio="xMidYMid meet"> <svg viewBox="${vb.join(' ')}" preserveAspectRatio="xMidYMid meet">
${bg ${bg && bgRect
? svg`<image href="${bg}" x="0" y="0" width="${IMG_W}" height="${IMG_H}" preserveAspectRatio="none" />` ? svg`<image href="${bg}" x="${bgRect[0]}" y="${bgRect[1]}" width="${bgRect[2]}" height="${bgRect[3]}" preserveAspectRatio="none" />`
: nothing} : nothing}
${rooms.map( ${rooms.map(
(r) => svg`<rect (r) => svg`<rect
@@ -513,7 +516,7 @@ class HouseplanCard extends LitElement {
${!bg ? svg`<text class="rlabel" x="${r.x + r.w / 2}" y="${r.y + 26}">${r.name}</text>` : nothing}`, ${!bg ? svg`<text class="rlabel" x="${r.x + r.w / 2}" y="${r.y + 26}">${r.name}</text>` : nothing}`,
)} )}
</svg> </svg>
<div class="devlayer" style="--icon-size:${iconSize}px"> <div class="devlayer" style="--icon-size:${iconPct}cqw">
${devs.map((d) => this._renderDevice(d, vb))} ${devs.map((d) => this._renderDevice(d, vb))}
</div> </div>
</div> </div>
@@ -693,6 +696,7 @@ class HouseplanCard extends LitElement {
.stage { .stage {
position: relative; position: relative;
width: 100%; width: 100%;
container-type: inline-size; /* cqw = % ширины плана для размеров иконок */
} }
.stage svg { .stage svg {
position: absolute; position: absolute;
@@ -740,10 +744,10 @@ class HouseplanCard extends LitElement {
} }
.dev { .dev {
position: absolute; position: absolute;
width: var(--icon-size, 22px); width: var(--icon-size, 2.5cqw);
height: var(--icon-size, 22px); height: var(--icon-size, 2.5cqw);
margin: calc(var(--icon-size, 22px) / -2) 0 0 calc(var(--icon-size, 22px) / -2); margin: calc(var(--icon-size, 2.5cqw) / -2) 0 0 calc(var(--icon-size, 2.5cqw) / -2);
border-radius: 5px; border-radius: 22%;
background: var(--hp-bg); background: var(--hp-bg);
border: 1px solid var(--hp-line); border: 1px solid var(--hp-line);
display: flex; display: flex;
@@ -757,7 +761,7 @@ class HouseplanCard extends LitElement {
z-index: 2; z-index: 2;
} }
.dev ha-icon { .dev ha-icon {
--mdc-icon-size: calc(var(--icon-size, 22px) * 0.62); --mdc-icon-size: calc(var(--icon-size, 2.5cqw) * 0.62);
} }
.dev:hover { .dev:hover {
background: var(--hp-accent); background: var(--hp-accent);
@@ -791,14 +795,14 @@ class HouseplanCard extends LitElement {
left: 100%; left: 100%;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
margin-left: 2px; margin-left: calc(var(--icon-size, 2.5cqw) * 0.1);
background: rgba(4, 18, 31, 0.9); background: rgba(4, 18, 31, 0.9);
border: 1px solid var(--hp-accent); border: 1px solid var(--hp-accent);
border-radius: 4px; border-radius: calc(var(--icon-size, 2.5cqw) * 0.18);
padding: 0 3px; padding: 0 calc(var(--icon-size, 2.5cqw) * 0.14);
font-size: 10px; font-size: calc(var(--icon-size, 2.5cqw) * 0.45);
font-weight: 700; font-weight: 700;
line-height: 15px; line-height: calc(var(--icon-size, 2.5cqw) * 0.68);
color: #dff1ff; color: #dff1ff;
white-space: nowrap; white-space: nowrap;
pointer-events: none; pointer-events: none;
@@ -921,14 +925,4 @@ if (!customElements.get('houseplan-card')) {
customElements.define('houseplan-card', HouseplanCard); customElements.define('houseplan-card', HouseplanCard);
} }
(window as any).customCards = (window as any).customCards || []; (wi
if (!(window as any).customCards.find((c: any) => c.type === 'houseplan-card')) {
(window as any).customCards.push({
type: 'houseplan-card',
name: 'House Plan Card',
description: 'Интерактивный план дома: этажи, комнаты, устройства с живыми состояниями и drag-раскладкой.',
});
}
// eslint-disable-next-line no-console
console.info(`%c HOUSEPLAN-CARD %c v${CARD_VERSION} `, 'background:#3ea6ff;color:#04121f;font-weight:700', '')