mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 04:09:17 +00:00
committed by
claude[bot]
parent
fe8970ee84
commit
590d178e29
@@ -12,6 +12,7 @@ import math
|
||||
import re
|
||||
import struct
|
||||
import xml.etree.ElementTree as ET
|
||||
from xml.parsers import expat
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -33,6 +34,7 @@ MAX_RASTER_PIXELS = (128 * 1024 * 1024) // 4
|
||||
MAX_SVG_ELEMENTS = 5_000
|
||||
MAX_SVG_DEPTH = 64
|
||||
MAX_SVG_ATTR_CHARS = 512_000
|
||||
MAX_SVG_ATTR_VALUE_CHARS = 65_536
|
||||
_SVG_TAGS = frozenset({
|
||||
"svg", "g", "defs", "title", "desc", "path", "rect", "circle", "ellipse",
|
||||
"line", "polyline", "polygon", "clipPath", "mask", "linearGradient",
|
||||
@@ -49,6 +51,9 @@ _SVG_ATTRS = frozenset({
|
||||
_LOCAL_REF = re.compile(r"^url\(#[A-Za-z_][A-Za-z0-9_.:-]*\)$")
|
||||
_LENGTH = re.compile(r"^\s*([0-9]+(?:\.[0-9]+)?)\s*(?:px)?\s*$", re.I)
|
||||
_SVG_NAMESPACE = "http://www.w3.org/2000/svg"
|
||||
_SVG_UNIT_INTERVAL_ATTRS = frozenset({
|
||||
"opacity", "fill-opacity", "stroke-opacity", "stop-opacity", "offset",
|
||||
})
|
||||
|
||||
|
||||
class DecorAssetError(ValueError):
|
||||
@@ -157,15 +162,45 @@ def _svg_number(value: str | None) -> float | None:
|
||||
return float(match.group(1)) if match else None
|
||||
|
||||
|
||||
def _validate_svg(data: bytes) -> ValidatedAsset:
|
||||
lowered = data.lower()
|
||||
if any(token in lowered for token in (b"<!doctype", b"<!entity", b"<?xml-stylesheet")):
|
||||
def _validate_svg_unit_interval(name: str, value: str) -> None:
|
||||
"""Reject non-finite and out-of-range opacity/gradient values."""
|
||||
raw = value.strip()
|
||||
percent = raw.endswith("%")
|
||||
try:
|
||||
number = float(raw[:-1] if percent else raw)
|
||||
except ValueError as err:
|
||||
raise DecorAssetError("invalid_image", f"The SVG {name} value is invalid") from err
|
||||
limit = 100 if percent else 1
|
||||
if not math.isfinite(number) or number < 0 or number > limit:
|
||||
raise DecorAssetError("invalid_image", f"The SVG {name} value is out of range")
|
||||
|
||||
|
||||
def _reject_svg_declarations(data: bytes) -> None:
|
||||
"""Parse with encoding-aware expat guards before building an XML tree."""
|
||||
parser = expat.ParserCreate()
|
||||
|
||||
def reject(*_args: Any) -> None:
|
||||
raise DecorAssetError(
|
||||
"invalid_image", "DTD, entities and processing instructions are forbidden",
|
||||
)
|
||||
without_declaration = re.sub(br"^\s*<\?xml\b[^?]*\?>", b"", data, count=1, flags=re.I)
|
||||
if b"<?" in without_declaration:
|
||||
raise DecorAssetError("invalid_image", "XML processing instructions are forbidden")
|
||||
|
||||
parser.StartDoctypeDeclHandler = reject
|
||||
parser.EntityDeclHandler = reject
|
||||
parser.ExternalEntityRefHandler = reject
|
||||
parser.ProcessingInstructionHandler = reject
|
||||
try:
|
||||
parser.Parse(data, True)
|
||||
except DecorAssetError:
|
||||
raise
|
||||
except (expat.ExpatError, UnicodeError) as err:
|
||||
raise DecorAssetError("invalid_image", "The SVG is not valid XML") from err
|
||||
|
||||
|
||||
def _validate_svg(data: bytes) -> ValidatedAsset:
|
||||
# Byte-substring checks are bypassable with UTF-16/UTF-32. Expat detects
|
||||
# the declared/input encoding first and our handlers reject declarations
|
||||
# before entity expansion can allocate an ElementTree.
|
||||
_reject_svg_declarations(data)
|
||||
try:
|
||||
root = ET.fromstring(data)
|
||||
except (ET.ParseError, UnicodeError) as err:
|
||||
@@ -196,6 +231,8 @@ def _validate_svg(data: bytes) -> ValidatedAsset:
|
||||
if tag not in {"title", "desc"} or len(node.text) > 4096:
|
||||
raise DecorAssetError("unsupported_image", "Unsupported or oversized SVG text")
|
||||
for raw_name, value in node.attrib.items():
|
||||
if len(value) > MAX_SVG_ATTR_VALUE_CHARS:
|
||||
raise DecorAssetError("too_large", "An SVG attribute exceeds the safety limit")
|
||||
if raw_name.startswith("{"):
|
||||
raise DecorAssetError("unsupported_image", "Namespaced SVG attributes are unsupported")
|
||||
name = raw_name
|
||||
@@ -204,6 +241,8 @@ def _validate_svg(data: bytes) -> ValidatedAsset:
|
||||
raise DecorAssetError("too_large", "The SVG attributes exceed the safety limit")
|
||||
if name.lower().startswith("on") or name not in _SVG_ATTRS:
|
||||
raise DecorAssetError("unsupported_image", f"Unsupported SVG attribute: {name}")
|
||||
if name in _SVG_UNIT_INTERVAL_ATTRS:
|
||||
_validate_svg_unit_interval(name, value)
|
||||
low = value.strip().lower()
|
||||
if any(token in low for token in ("javascript:", "data:", "http:", "https:", "//")):
|
||||
raise DecorAssetError("invalid_image", "External SVG resources are forbidden")
|
||||
|
||||
@@ -1,125 +1,125 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "91d90ad5fe8f4f44a45669312cea92fc5c5c061316d8382ea8ec58dbef1603d8",
|
||||
"fingerprint": "4386b4e9f688f89cce5a1680c9f21b315933dd897345287b8c64700400b11c11",
|
||||
"entry": "houseplan-card.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-ruQDQ4Jw.js",
|
||||
"houseplan-assets/houseplan-card-qfwHeLJR.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 291026,
|
||||
"initialViewGzipBytes": 291041,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-CsM7GqVe.js",
|
||||
"houseplan-assets/de-MNYUkgT1.js",
|
||||
"houseplan-assets/editor-Ih3Tgb49.js",
|
||||
"houseplan-assets/fr-BB7LeC_Q.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-ipAn0IA6.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DB29lThG.js"
|
||||
"houseplan-assets/backdrop-pick-Di7bhm6e.js",
|
||||
"houseplan-assets/de-Dc1N8boU.js",
|
||||
"houseplan-assets/editor-DWFSXWYH.js",
|
||||
"houseplan-assets/fr-Cw28JMxl.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BQ2FUiZP.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-OhzyGbC7.js"
|
||||
],
|
||||
"lazyGzipBytes": 213816,
|
||||
"lazyGzipBytes": 213868,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-CsM7GqVe.js",
|
||||
"houseplan-assets/editor-Ih3Tgb49.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-ipAn0IA6.js"
|
||||
"houseplan-assets/backdrop-pick-Di7bhm6e.js",
|
||||
"houseplan-assets/editor-DWFSXWYH.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BQ2FUiZP.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 158012,
|
||||
"lazyEditorGzipBytes": 158042,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-CsM7GqVe.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DB29lThG.js"
|
||||
"houseplan-assets/backdrop-pick-Di7bhm6e.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-OhzyGbC7.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 14130,
|
||||
"lazyOnboardingGzipBytes": 14131,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-MNYUkgT1.js",
|
||||
"houseplan-assets/fr-BB7LeC_Q.js"
|
||||
"houseplan-assets/de-Dc1N8boU.js",
|
||||
"houseplan-assets/fr-Cw28JMxl.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 48848,
|
||||
"lazyLocaleGzipBytes": 48871,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-CsM7GqVe.js",
|
||||
"sha256": "136838d9a113801d3d59b5198facdf568a90e9384f62133d84ee8ccfbcf91725",
|
||||
"path": "houseplan-assets/backdrop-pick-Di7bhm6e.js",
|
||||
"sha256": "a166e065bb3b3d4905c6e8e46c5c5157c32d03d58b8ece49caa661d78a4bf4a9",
|
||||
"rawBytes": 20974,
|
||||
"gzipBytes": 7174,
|
||||
"gzipBytes": 7176,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-ruQDQ4Jw.js"
|
||||
"houseplan-assets/houseplan-card-qfwHeLJR.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-MNYUkgT1.js",
|
||||
"sha256": "8414e28bb23e0cc2e8d777a97b217f8a971d002109ce38168c0890b0bd4a38ff",
|
||||
"path": "houseplan-assets/de-Dc1N8boU.js",
|
||||
"sha256": "d5707d18ebb3f3f423037557e1692cf038337ac60655fa6684f3b596afbce5bf",
|
||||
"rawBytes": 81798,
|
||||
"gzipBytes": 24696,
|
||||
"gzipBytes": 24707,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-Ih3Tgb49.js",
|
||||
"sha256": "56f7be4294a42cb92bec171b3ea409425d70fb7ff5a4ca07ffae9190d9ea74d4",
|
||||
"path": "houseplan-assets/editor-DWFSXWYH.js",
|
||||
"sha256": "dcbb29ac4c28f8639044dee93688768b67d3044e6ec06dff59d028665a9e73e3",
|
||||
"rawBytes": 3826,
|
||||
"gzipBytes": 1580,
|
||||
"gzipBytes": 1584,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-ruQDQ4Jw.js"
|
||||
"houseplan-assets/houseplan-card-qfwHeLJR.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-BB7LeC_Q.js",
|
||||
"sha256": "63b7104bab6abf083d5478736b8f5fe16ed7ca09edf52fa8cdeda418aa041bf8",
|
||||
"path": "houseplan-assets/fr-Cw28JMxl.js",
|
||||
"sha256": "8209da223a0b5fe0ea3681a1a8df7f6d2a28ea4d6376953b21be47a5b2257397",
|
||||
"rawBytes": 83973,
|
||||
"gzipBytes": 24152,
|
||||
"gzipBytes": 24164,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-ruQDQ4Jw.js",
|
||||
"sha256": "99538870102416fc6844acba2719f23a26f08d9f12fb2619d2788a83ee7d9a5b",
|
||||
"rawBytes": 1033593,
|
||||
"gzipBytes": 290230,
|
||||
"path": "houseplan-assets/houseplan-card-qfwHeLJR.js",
|
||||
"sha256": "2bd83b9350a1b4e702d28660cdc3b38a5daa3da16f9de47ddd5a839417443cbc",
|
||||
"rawBytes": 1033605,
|
||||
"gzipBytes": 290242,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-MNYUkgT1.js",
|
||||
"houseplan-assets/editor-Ih3Tgb49.js",
|
||||
"houseplan-assets/fr-BB7LeC_Q.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-ipAn0IA6.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DB29lThG.js"
|
||||
"houseplan-assets/de-Dc1N8boU.js",
|
||||
"houseplan-assets/editor-DWFSXWYH.js",
|
||||
"houseplan-assets/fr-Cw28JMxl.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BQ2FUiZP.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-OhzyGbC7.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-ipAn0IA6.js",
|
||||
"sha256": "924d285e33b3254880331d8f944030705b1ba87f920fe4ba1f50f1d60a98207c",
|
||||
"rawBytes": 572891,
|
||||
"gzipBytes": 149258,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-BQ2FUiZP.js",
|
||||
"sha256": "504fe0626f810cd221ba57a8b9807e0d4250da17545cd8ba0609d44bef967c88",
|
||||
"rawBytes": 572919,
|
||||
"gzipBytes": 149282,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-CsM7GqVe.js",
|
||||
"houseplan-assets/houseplan-card-ruQDQ4Jw.js"
|
||||
"houseplan-assets/backdrop-pick-Di7bhm6e.js",
|
||||
"houseplan-assets/houseplan-card-qfwHeLJR.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-DB29lThG.js",
|
||||
"sha256": "c59a3e4ee8c2b799568e3ed82b1f35526bf72b9e75e0f9769d033cb67aa7b716",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-OhzyGbC7.js",
|
||||
"sha256": "54105c7da5f96f26c7ee199726687ef046058bcb58f3ef27a2ff7b51e73c7415",
|
||||
"rawBytes": 28088,
|
||||
"gzipBytes": 6956,
|
||||
"gzipBytes": 6955,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-CsM7GqVe.js",
|
||||
"houseplan-assets/houseplan-card-ruQDQ4Jw.js"
|
||||
"houseplan-assets/backdrop-pick-Di7bhm6e.js",
|
||||
"houseplan-assets/houseplan-card-qfwHeLJR.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "f6e82e778343cf243de470eeafd5671d9d7746a730d385249fee0ca56d0a3b56",
|
||||
"sha256": "ea553cfdc707a127f7098a3302bf8a6037ba8378b6afa8d8e3f0ea9601f19e41",
|
||||
"rawBytes": 1183,
|
||||
"gzipBytes": 796,
|
||||
"gzipBytes": 799,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-ruQDQ4Jw.js"
|
||||
"houseplan-assets/houseplan-card-qfwHeLJR.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
+7
-7
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="91d90ad5fe8f4f44a45669312cea92fc5c5c061316d8382ea8ec58dbef1603d8";import{b as e,l as o,t,df as s,A as a,dg as i,dh as l,di as n,E as r,c}from"./houseplan-card-ruQDQ4Jw.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="4386b4e9f688f89cce5a1680c9f21b315933dd897345287b8c64700400b11c11";import{b as e,l as o,t,df as s,A as a,dg as i,dh as l,di as n,E as r,c}from"./houseplan-card-qfwHeLJR.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._formData}
|
||||
.schema=${e}
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+64
-64
File diff suppressed because one or more lines are too long
+4
-4
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="91d90ad5fe8f4f44a45669312cea92fc5c5c061316d8382ea8ec58dbef1603d8";try{await import("./houseplan-assets/houseplan-card-ruQDQ4Jw.js")}catch(e){if(!customElements.get("houseplan-card")){const l=String(navigator.language||"en").toLowerCase();const m=l.startsWith("ru")?"House Plan обновился — перезагрузите страницу (Ctrl+F5).":l.startsWith("de")?"House Plan wurde aktualisiert — bitte laden Sie die Seite neu (Strg+F5).":l.startsWith("fr")?"House Plan a été mis à jour — veuillez recharger la page (Ctrl+F5).":"House Plan was updated — please reload the page (Ctrl+F5).";customElements.define("houseplan-card",class extends HTMLElement{setConfig(){}getCardSize(){return 1}connectedCallback(){this.style.cssText="display:block;box-sizing:border-box;padding:16px;border:1px solid var(--divider-color,#e0e0e0);border-radius:var(--ha-card-border-radius,12px);background:var(--card-background-color,#fff);color:var(--primary-text-color,#212121);font:14px/1.4 var(--paper-font-body1_-_font-family,sans-serif)";this.textContent=m}})}console.error("[houseplan] stale entry: the main chunk is unavailable",e)}
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="4386b4e9f688f89cce5a1680c9f21b315933dd897345287b8c64700400b11c11";try{await import("./houseplan-assets/houseplan-card-qfwHeLJR.js")}catch(e){if(!customElements.get("houseplan-card")){const l=String(navigator.language||"en").toLowerCase();const m=l.startsWith("ru")?"House Plan обновился — перезагрузите страницу (Ctrl+F5).":l.startsWith("de")?"House Plan wurde aktualisiert — bitte laden Sie die Seite neu (Strg+F5).":l.startsWith("fr")?"House Plan a été mis à jour — veuillez recharger la page (Ctrl+F5).":"House Plan was updated — please reload the page (Ctrl+F5).";customElements.define("houseplan-card",class extends HTMLElement{setConfig(){}getCardSize(){return 1}connectedCallback(){this.style.cssText="display:block;box-sizing:border-box;padding:16px;border:1px solid var(--divider-color,#e0e0e0);border-radius:var(--ha-card-border-radius,12px);background:var(--card-background-color,#fff);color:var(--primary-text-color,#212121);font:14px/1.4 var(--paper-font-body1_-_font-family,sans-serif)";this.textContent=m}})}console.error("[houseplan] stale entry: the main chunk is unavailable",e)}
|
||||
|
||||
@@ -189,7 +189,7 @@ const res = await page.evaluate(async () => {
|
||||
c._decorTextDialog = { ...c._decorTextDialog, text: 'Default probe' };
|
||||
c._decorSaveText();
|
||||
c._furnPalette = { symbol: 'chair', w: 45, h: 45 };
|
||||
c._furnPlace([gDefault * 24, gDefault * 12], true);
|
||||
c._editorRuntime._furnPlace([gDefault * 24, gDefault * 12], true);
|
||||
await c.updateComplete;
|
||||
const createdByDefault = c._decorList.filter((shape) => shape.id !== existingDefaultProbe.id);
|
||||
out.everyNewDecorKindUsesMainDefault = textInheritsDefault
|
||||
|
||||
@@ -145,6 +145,17 @@ const result = await page.evaluate(async () => {
|
||||
out.cancelledTouchDoesNotPlace = sp.decor.length === countBeforeTouch
|
||||
&& c._furnTouchPending === null;
|
||||
|
||||
let committedPointerType = '';
|
||||
const originalImagePlace = c._editorRuntime._decorImagePlace;
|
||||
c._editorRuntime._decorImagePlace = (_raw, pointerType) => { committedPointerType = pointerType; };
|
||||
c._decorTool = 'image';
|
||||
c._decorImagePalette = asset;
|
||||
pointer('pointerdown', 360, 360, { pointerType: 'touch', pointerId: 512 });
|
||||
pointer('pointerup', 360, 360, { pointerType: 'touch', pointerId: 512 });
|
||||
await settle();
|
||||
c._editorRuntime._decorImagePlace = originalImagePlace;
|
||||
out.touchCommitUsesTouchSnapTolerance = committedPointerType === 'touch';
|
||||
|
||||
c._decorTool = 'select';
|
||||
const imageBeforeReplace = { ...sp.decor.find((shape) => shape.id === image.id) };
|
||||
c._editorRuntime._openDecorProperties(imageBeforeReplace);
|
||||
@@ -190,12 +201,16 @@ const result = await page.evaluate(async () => {
|
||||
await settle();
|
||||
await customElements.whenDefined('houseplan-space-card');
|
||||
const config = JSON.parse(JSON.stringify(c._serverCfg));
|
||||
let staticResolveCalls = 0;
|
||||
const staticHass = { ...c.hass, callWS: async (message) => {
|
||||
if (message.type === 'houseplan/config/get') return {
|
||||
config, rev: 1, decor_assets_api: 1, virtual_lights: { rev: 0, config_rev: 1, off: [] },
|
||||
};
|
||||
if (message.type === 'houseplan/layout/get') return { layout: {}, rev: 1 };
|
||||
if (message.type === 'houseplan/assets/resolve') return { assets: [asset, asset2], missing: [] };
|
||||
if (message.type === 'houseplan/assets/resolve') {
|
||||
staticResolveCalls++;
|
||||
return { assets: [asset, asset2], missing: [] };
|
||||
}
|
||||
if (message.type === 'houseplan/content/sign') return { urls: Object.fromEntries(
|
||||
message.paths.map((path) => [path, path === rawUrl ? url : path === rawUrl2 ? url2 : path]),
|
||||
) };
|
||||
@@ -227,6 +242,10 @@ const result = await page.evaluate(async () => {
|
||||
&& viewBox.y + viewBox.height >= y + h;
|
||||
out.staticImageIsInert = !!staticImage
|
||||
&& getComputedStyle(staticImage).pointerEvents === 'none';
|
||||
const resolveCallsAfterFirstLoad = staticResolveCalls;
|
||||
await staticCard._load(true);
|
||||
out.staticResolveCachesCompleteAssetSet = resolveCallsAfterFirstLoad === 1
|
||||
&& staticResolveCalls === resolveCallsAfterFirstLoad;
|
||||
host.remove();
|
||||
|
||||
sp.decor = saved.decor;
|
||||
|
||||
Vendored
+57
-57
@@ -1,125 +1,125 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "91d90ad5fe8f4f44a45669312cea92fc5c5c061316d8382ea8ec58dbef1603d8",
|
||||
"fingerprint": "4386b4e9f688f89cce5a1680c9f21b315933dd897345287b8c64700400b11c11",
|
||||
"entry": "houseplan-card.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-ruQDQ4Jw.js",
|
||||
"houseplan-assets/houseplan-card-qfwHeLJR.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 291026,
|
||||
"initialViewGzipBytes": 291041,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-CsM7GqVe.js",
|
||||
"houseplan-assets/de-MNYUkgT1.js",
|
||||
"houseplan-assets/editor-Ih3Tgb49.js",
|
||||
"houseplan-assets/fr-BB7LeC_Q.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-ipAn0IA6.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DB29lThG.js"
|
||||
"houseplan-assets/backdrop-pick-Di7bhm6e.js",
|
||||
"houseplan-assets/de-Dc1N8boU.js",
|
||||
"houseplan-assets/editor-DWFSXWYH.js",
|
||||
"houseplan-assets/fr-Cw28JMxl.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BQ2FUiZP.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-OhzyGbC7.js"
|
||||
],
|
||||
"lazyGzipBytes": 213816,
|
||||
"lazyGzipBytes": 213868,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-CsM7GqVe.js",
|
||||
"houseplan-assets/editor-Ih3Tgb49.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-ipAn0IA6.js"
|
||||
"houseplan-assets/backdrop-pick-Di7bhm6e.js",
|
||||
"houseplan-assets/editor-DWFSXWYH.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BQ2FUiZP.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 158012,
|
||||
"lazyEditorGzipBytes": 158042,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-CsM7GqVe.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DB29lThG.js"
|
||||
"houseplan-assets/backdrop-pick-Di7bhm6e.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-OhzyGbC7.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 14130,
|
||||
"lazyOnboardingGzipBytes": 14131,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-MNYUkgT1.js",
|
||||
"houseplan-assets/fr-BB7LeC_Q.js"
|
||||
"houseplan-assets/de-Dc1N8boU.js",
|
||||
"houseplan-assets/fr-Cw28JMxl.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 48848,
|
||||
"lazyLocaleGzipBytes": 48871,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-CsM7GqVe.js",
|
||||
"sha256": "136838d9a113801d3d59b5198facdf568a90e9384f62133d84ee8ccfbcf91725",
|
||||
"path": "houseplan-assets/backdrop-pick-Di7bhm6e.js",
|
||||
"sha256": "a166e065bb3b3d4905c6e8e46c5c5157c32d03d58b8ece49caa661d78a4bf4a9",
|
||||
"rawBytes": 20974,
|
||||
"gzipBytes": 7174,
|
||||
"gzipBytes": 7176,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-ruQDQ4Jw.js"
|
||||
"houseplan-assets/houseplan-card-qfwHeLJR.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-MNYUkgT1.js",
|
||||
"sha256": "8414e28bb23e0cc2e8d777a97b217f8a971d002109ce38168c0890b0bd4a38ff",
|
||||
"path": "houseplan-assets/de-Dc1N8boU.js",
|
||||
"sha256": "d5707d18ebb3f3f423037557e1692cf038337ac60655fa6684f3b596afbce5bf",
|
||||
"rawBytes": 81798,
|
||||
"gzipBytes": 24696,
|
||||
"gzipBytes": 24707,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-Ih3Tgb49.js",
|
||||
"sha256": "56f7be4294a42cb92bec171b3ea409425d70fb7ff5a4ca07ffae9190d9ea74d4",
|
||||
"path": "houseplan-assets/editor-DWFSXWYH.js",
|
||||
"sha256": "dcbb29ac4c28f8639044dee93688768b67d3044e6ec06dff59d028665a9e73e3",
|
||||
"rawBytes": 3826,
|
||||
"gzipBytes": 1580,
|
||||
"gzipBytes": 1584,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-ruQDQ4Jw.js"
|
||||
"houseplan-assets/houseplan-card-qfwHeLJR.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-BB7LeC_Q.js",
|
||||
"sha256": "63b7104bab6abf083d5478736b8f5fe16ed7ca09edf52fa8cdeda418aa041bf8",
|
||||
"path": "houseplan-assets/fr-Cw28JMxl.js",
|
||||
"sha256": "8209da223a0b5fe0ea3681a1a8df7f6d2a28ea4d6376953b21be47a5b2257397",
|
||||
"rawBytes": 83973,
|
||||
"gzipBytes": 24152,
|
||||
"gzipBytes": 24164,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-ruQDQ4Jw.js",
|
||||
"sha256": "99538870102416fc6844acba2719f23a26f08d9f12fb2619d2788a83ee7d9a5b",
|
||||
"rawBytes": 1033593,
|
||||
"gzipBytes": 290230,
|
||||
"path": "houseplan-assets/houseplan-card-qfwHeLJR.js",
|
||||
"sha256": "2bd83b9350a1b4e702d28660cdc3b38a5daa3da16f9de47ddd5a839417443cbc",
|
||||
"rawBytes": 1033605,
|
||||
"gzipBytes": 290242,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-MNYUkgT1.js",
|
||||
"houseplan-assets/editor-Ih3Tgb49.js",
|
||||
"houseplan-assets/fr-BB7LeC_Q.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-ipAn0IA6.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-DB29lThG.js"
|
||||
"houseplan-assets/de-Dc1N8boU.js",
|
||||
"houseplan-assets/editor-DWFSXWYH.js",
|
||||
"houseplan-assets/fr-Cw28JMxl.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BQ2FUiZP.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-OhzyGbC7.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-ipAn0IA6.js",
|
||||
"sha256": "924d285e33b3254880331d8f944030705b1ba87f920fe4ba1f50f1d60a98207c",
|
||||
"rawBytes": 572891,
|
||||
"gzipBytes": 149258,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-BQ2FUiZP.js",
|
||||
"sha256": "504fe0626f810cd221ba57a8b9807e0d4250da17545cd8ba0609d44bef967c88",
|
||||
"rawBytes": 572919,
|
||||
"gzipBytes": 149282,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-CsM7GqVe.js",
|
||||
"houseplan-assets/houseplan-card-ruQDQ4Jw.js"
|
||||
"houseplan-assets/backdrop-pick-Di7bhm6e.js",
|
||||
"houseplan-assets/houseplan-card-qfwHeLJR.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-DB29lThG.js",
|
||||
"sha256": "c59a3e4ee8c2b799568e3ed82b1f35526bf72b9e75e0f9769d033cb67aa7b716",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-OhzyGbC7.js",
|
||||
"sha256": "54105c7da5f96f26c7ee199726687ef046058bcb58f3ef27a2ff7b51e73c7415",
|
||||
"rawBytes": 28088,
|
||||
"gzipBytes": 6956,
|
||||
"gzipBytes": 6955,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-CsM7GqVe.js",
|
||||
"houseplan-assets/houseplan-card-ruQDQ4Jw.js"
|
||||
"houseplan-assets/backdrop-pick-Di7bhm6e.js",
|
||||
"houseplan-assets/houseplan-card-qfwHeLJR.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "f6e82e778343cf243de470eeafd5671d9d7746a730d385249fee0ca56d0a3b56",
|
||||
"sha256": "ea553cfdc707a127f7098a3302bf8a6037ba8378b6afa8d8e3f0ea9601f19e41",
|
||||
"rawBytes": 1183,
|
||||
"gzipBytes": 796,
|
||||
"gzipBytes": 799,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-ruQDQ4Jw.js"
|
||||
"houseplan-assets/houseplan-card-qfwHeLJR.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
Vendored
+7
-7
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="91d90ad5fe8f4f44a45669312cea92fc5c5c061316d8382ea8ec58dbef1603d8";import{b as e,l as o,t,df as s,A as a,dg as i,dh as l,di as n,E as r,c}from"./houseplan-card-ruQDQ4Jw.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="4386b4e9f688f89cce5a1680c9f21b315933dd897345287b8c64700400b11c11";import{b as e,l as o,t,df as s,A as a,dg as i,dh as l,di as n,E as r,c}from"./houseplan-card-qfwHeLJR.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._formData}
|
||||
.schema=${e}
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+64
-64
File diff suppressed because one or more lines are too long
+4
-4
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="91d90ad5fe8f4f44a45669312cea92fc5c5c061316d8382ea8ec58dbef1603d8";try{await import("./houseplan-assets/houseplan-card-ruQDQ4Jw.js")}catch(e){if(!customElements.get("houseplan-card")){const l=String(navigator.language||"en").toLowerCase();const m=l.startsWith("ru")?"House Plan обновился — перезагрузите страницу (Ctrl+F5).":l.startsWith("de")?"House Plan wurde aktualisiert — bitte laden Sie die Seite neu (Strg+F5).":l.startsWith("fr")?"House Plan a été mis à jour — veuillez recharger la page (Ctrl+F5).":"House Plan was updated — please reload the page (Ctrl+F5).";customElements.define("houseplan-card",class extends HTMLElement{setConfig(){}getCardSize(){return 1}connectedCallback(){this.style.cssText="display:block;box-sizing:border-box;padding:16px;border:1px solid var(--divider-color,#e0e0e0);border-radius:var(--ha-card-border-radius,12px);background:var(--card-background-color,#fff);color:var(--primary-text-color,#212121);font:14px/1.4 var(--paper-font-body1_-_font-family,sans-serif)";this.textContent=m}})}console.error("[houseplan] stale entry: the main chunk is unavailable",e)}
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="4386b4e9f688f89cce5a1680c9f21b315933dd897345287b8c64700400b11c11";try{await import("./houseplan-assets/houseplan-card-qfwHeLJR.js")}catch(e){if(!customElements.get("houseplan-card")){const l=String(navigator.language||"en").toLowerCase();const m=l.startsWith("ru")?"House Plan обновился — перезагрузите страницу (Ctrl+F5).":l.startsWith("de")?"House Plan wurde aktualisiert — bitte laden Sie die Seite neu (Strg+F5).":l.startsWith("fr")?"House Plan a été mis à jour — veuillez recharger la page (Ctrl+F5).":"House Plan was updated — please reload the page (Ctrl+F5).";customElements.define("houseplan-card",class extends HTMLElement{setConfig(){}getCardSize(){return 1}connectedCallback(){this.style.cssText="display:block;box-sizing:border-box;padding:16px;border:1px solid var(--divider-color,#e0e0e0);border-radius:var(--ha-card-border-radius,12px);background:var(--card-background-color,#fff);color:var(--primary-text-color,#212121);font:14px/1.4 var(--paper-font-body1_-_font-family,sans-serif)";this.textContent=m}})}console.error("[houseplan] stale entry: the main chunk is unavailable",e)}
|
||||
|
||||
+36
-2
@@ -1,4 +1,5 @@
|
||||
import type { DecorShape } from './editors/decor/types';
|
||||
import { clamp01, normalizeAngle } from './editors/decor/geometry';
|
||||
|
||||
export const DECOR_ASSETS_API_VERSION = 1;
|
||||
export const DECOR_ASSET_ID_RE = /^[0-9a-f]{64}$/;
|
||||
@@ -15,6 +16,12 @@ export interface DecorAsset {
|
||||
used_by?: { space_id: string; decor_id: string }[];
|
||||
}
|
||||
|
||||
export type DecorImageProjection = readonly [
|
||||
x: number, y: number, w: number, h: number, opacity: number, transform: string,
|
||||
];
|
||||
|
||||
const resolveCache = new WeakMap<object, [string, Map<string, DecorAsset>]>();
|
||||
|
||||
export function decorAssetIds(config: any): string[] {
|
||||
const ids = new Set<string>();
|
||||
for (const space of config?.spaces || []) {
|
||||
@@ -25,6 +32,25 @@ export function decorAssetIds(config: any): string[] {
|
||||
return [...ids];
|
||||
}
|
||||
|
||||
/** One projection contract shared by the full and static renderers. */
|
||||
export function projectDecorImage(
|
||||
shape: Extract<DecorShape, { kind: 'image' }>,
|
||||
canvasWidth: number,
|
||||
canvasHeight: number,
|
||||
): DecorImageProjection | null {
|
||||
const x = Number(shape.x) * canvasWidth;
|
||||
const y = Number(shape.y) * canvasHeight;
|
||||
const w = Number(shape.w) * canvasWidth;
|
||||
const h = Number(shape.h) * canvasHeight;
|
||||
if (![x, y, w, h].every(Number.isFinite) || w <= 0 || h <= 0) return null;
|
||||
const cx = x + w / 2;
|
||||
const cy = y + h / 2;
|
||||
const angle = normalizeAngle(shape.angle);
|
||||
const opacity = clamp01(shape.opacity, 1);
|
||||
const transform = `translate(${cx} ${cy}) rotate(${angle}) scale(${shape.flip_h ? -1 : 1} ${shape.flip_v ? -1 : 1}) translate(${-cx} ${-cy})`;
|
||||
return [x, y, w, h, opacity, transform];
|
||||
}
|
||||
|
||||
export function adoptDecorAssets(value: unknown): Map<string, DecorAsset> {
|
||||
const out = new Map<string, DecorAsset>();
|
||||
const rows = (value as { assets?: unknown })?.assets;
|
||||
@@ -47,10 +73,17 @@ export function adoptDecorAssets(value: unknown): Map<string, DecorAsset> {
|
||||
|
||||
/** Resolve every unique id without ever exceeding the backend message cap. */
|
||||
export async function resolveDecorAssets(
|
||||
hass: { callWS: (message: Record<string, unknown>) => Promise<unknown> },
|
||||
hass: {
|
||||
callWS: (message: Record<string, unknown>) => Promise<unknown>;
|
||||
connection?: object;
|
||||
},
|
||||
assetIds: readonly string[],
|
||||
): Promise<Map<string, DecorAsset>> {
|
||||
const unique = [...new Set(assetIds.filter((id) => DECOR_ASSET_ID_RE.test(id)))];
|
||||
const unique = [...new Set(assetIds.filter((id) => DECOR_ASSET_ID_RE.test(id)))].sort();
|
||||
const owner = hass.connection || hass;
|
||||
const key = unique.join(',');
|
||||
const cached = resolveCache.get(owner);
|
||||
if (cached?.[0] === key) return cached[1];
|
||||
const out = new Map<string, DecorAsset>();
|
||||
for (let offset = 0; offset < unique.length; offset += DECOR_ASSET_RESOLVE_BATCH) {
|
||||
const response = await hass.callWS({
|
||||
@@ -59,6 +92,7 @@ export async function resolveDecorAssets(
|
||||
});
|
||||
for (const [id, asset] of adoptDecorAssets(response)) out.set(id, asset);
|
||||
}
|
||||
resolveCache.set(owner, [key, out]);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -343,7 +343,7 @@ import {
|
||||
} from './ha-binding-status';
|
||||
import type { DecorShape, DecorStyle } from './editors/decor/types';
|
||||
import {
|
||||
DECOR_ASSETS_API_VERSION, decorAssetIds,
|
||||
DECOR_ASSETS_API_VERSION, decorAssetIds, projectDecorImage,
|
||||
resolveDecorAssets, type DecorAsset,
|
||||
} from './decor-assets';
|
||||
import {
|
||||
@@ -8760,10 +8760,10 @@ export class HouseplanCard extends LitElement {
|
||||
transform=${ang ? `rotate(${ang} ${cx} ${cy})` : nothing} @pointerdown=${down}></ellipse>` : nothing}`;
|
||||
}
|
||||
if (sh.kind === 'image') {
|
||||
const projection = projectDecorImage(sh, W, H);
|
||||
if (!projection) return nothing;
|
||||
const [x, y, w, h, opacity, transform] = projection;
|
||||
const asset = this._decorAssets.get(sh.asset_id);
|
||||
const x = sh.x * W, y = sh.y * H, w = sh.w * W, h = sh.h * H;
|
||||
const cx = x + w / 2, cy = y + h / 2;
|
||||
const transform = `translate(${cx} ${cy}) rotate(${normalizeAngle(sh.angle)}) scale(${sh.flip_h ? -1 : 1} ${sh.flip_v ? -1 : 1}) translate(${-cx} ${-cy})`;
|
||||
if (!asset) return editing && this._editorRuntime
|
||||
? this._editorRuntime._renderMissingDecorImage(sh, cls, transform, x, y, w, h, down, dbl)
|
||||
: nothing;
|
||||
@@ -8771,7 +8771,7 @@ export class HouseplanCard extends LitElement {
|
||||
if (!href) return nothing;
|
||||
return svg`<image class="${cls} dimage" data-hp="decor" data-id=${sh.id}
|
||||
data-kind="image" href=${href} x=${x} y=${y} width=${w} height=${h}
|
||||
opacity=${clamp01(sh.opacity, 1)} preserveAspectRatio="none" transform=${transform}
|
||||
opacity=${opacity} preserveAspectRatio="none" transform=${transform}
|
||||
@load=${() => this._signer.markLoaded(this._renderPlanHass, asset.url, href)}
|
||||
@pointerdown=${down} @dblclick=${dbl}></image>
|
||||
${erasing ? svg`<rect class="dshape derasehit" data-hp="decor" data-id=${sh.id}
|
||||
|
||||
@@ -4200,7 +4200,7 @@ public _decorPointerDown(ev: PointerEvent): boolean {
|
||||
const pointerType = ev.pointerType || 'mouse';
|
||||
if (pointerType === 'mouse') {
|
||||
if (t === 'furniture') this._furnPlace(this._svgPoint(ev), ev.shiftKey, pointerType);
|
||||
else this._decorImagePlace(this._svgPoint(ev));
|
||||
else this._decorImagePlace(this._svgPoint(ev), pointerType);
|
||||
return true;
|
||||
}
|
||||
const pending = this.host._furnTouchPending;
|
||||
@@ -5042,7 +5042,7 @@ public _furnPointerUp(ev: PointerEvent): boolean {
|
||||
|| (this.host._decorTool === 'image' && this.host._decorImagePalette))) {
|
||||
if (this.host._decorTool === 'furniture')
|
||||
this._furnPlace(this._svgPoint(ev), ev.shiftKey, pending.pointerType);
|
||||
else this._decorImagePlace(this._svgPoint(ev));
|
||||
else this._decorImagePlace(this._svgPoint(ev), pending.pointerType);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -5127,11 +5127,11 @@ public _decorApplyBox(id: string, box: { x: number; y: number; w: number; h: num
|
||||
this.host.requestUpdate();
|
||||
}
|
||||
|
||||
public _decorImagePlace(raw: number[]): void {
|
||||
public _decorImagePlace(raw: number[], pointerType = 'mouse'): void {
|
||||
const asset = this.host._decorImagePalette;
|
||||
const sp = this.host._curSpaceCfg;
|
||||
if (!asset || !sp) return;
|
||||
const at = this._decorSnap(raw);
|
||||
const at = this._decorSnap(raw, pointerType);
|
||||
const size = initialDecorImageCm(asset.width, asset.height);
|
||||
const w = decorCmToUnits(size.w, this.host._cellCm, this.host._gridPitch) / NORM_W;
|
||||
const h = decorCmToUnits(size.h, this.host._cellCm, this.host._gridPitch) / this.host._decorH;
|
||||
|
||||
+1
-1
@@ -730,7 +730,7 @@ class HouseplanSpaceCard extends LitElement {
|
||||
if (configChanged) this._continuity.note('config-candidate', { configRev: snap.rev });
|
||||
try {
|
||||
this._decorAssets = await resolveDecorAssets(this.hass, decorAssetIds(snap.config));
|
||||
} catch { /* retain the last complete frame across a transient resolve failure */ }
|
||||
} catch { /* retain the last complete frame; failed sets are not cached and retry next load */ }
|
||||
if (layoutChanged) this._continuity.note('layout-candidate', { layoutRev: snap.layoutRev });
|
||||
if (virtualLightsChanged) this._capturedSnapshotSequence = -1;
|
||||
this._loadedOnce = true;
|
||||
|
||||
+5
-8
@@ -57,6 +57,7 @@ import {
|
||||
import { resolveZeroWalls } from './zero-walls';
|
||||
import { geometryOpenings } from './plan-geometry-preflight';
|
||||
import { resolveDeviceAreaRelocations } from './device-area-relocation';
|
||||
import { projectDecorImage } from './decor-assets';
|
||||
import {
|
||||
buildGlowClipGeometry, buildLightBarrierScene, forgetGlowSource, forgetGlowSpace,
|
||||
glowSourceInOpaqueBody, pruneGlowSources, readGlowClip, renderGlowPools,
|
||||
@@ -638,16 +639,12 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
|
||||
const raw = o.decorAssetUrl?.(String(shape.asset_id || '')) || '';
|
||||
const href = raw && o.displayUrl ? o.displayUrl(raw) : raw;
|
||||
if (!href) return [];
|
||||
const x = Number(shape.x) * NORM_W, y = Number(shape.y) * NORM_W;
|
||||
const w = Number(shape.w) * NORM_W, h = Number(shape.h) * NORM_W;
|
||||
if (![x, y, w, h].every(Number.isFinite) || w <= 0 || h <= 0) return [];
|
||||
const cx = x + w / 2, cy = y + h / 2;
|
||||
const angle = Number(shape.angle) || 0;
|
||||
const transform = `translate(${cx} ${cy}) rotate(${angle}) scale(${shape.flip_h ? -1 : 1} ${shape.flip_v ? -1 : 1}) translate(${-cx} ${-cy})`;
|
||||
const projection = projectDecorImage(shape, NORM_W, NORM_W);
|
||||
if (!projection) return [];
|
||||
const [x, y, w, h, opacity, transform] = projection;
|
||||
return [svg`<image class="dimage" data-hp="decor"
|
||||
href=${href} x=${x} y=${y} width=${w} height=${h}
|
||||
opacity=${Number.isFinite(Number(shape.opacity))
|
||||
? Math.max(0, Math.min(1, Number(shape.opacity))) : 1}
|
||||
opacity=${opacity}
|
||||
@load=${() => o.assetLoaded?.(raw, href)}
|
||||
preserveAspectRatio="none" transform=${transform} pointer-events="none"></image>`];
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
adoptDecorAssets, decorAssetIds, initialDecorImageCm, resolveDecorAssets,
|
||||
adoptDecorAssets, decorAssetIds, initialDecorImageCm,
|
||||
projectDecorImage, resolveDecorAssets,
|
||||
} from '../test-build/decor-assets.js';
|
||||
import { decorBoxItem } from '../test-build/space-geometry.js';
|
||||
|
||||
@@ -20,6 +21,19 @@ test('#51 asset ids are unique across spaces and malformed ids fail closed', ()
|
||||
] }), [id('b'), id('a')]);
|
||||
});
|
||||
|
||||
test('#51 full and static renderers share one fail-closed image projection', () => {
|
||||
const shape = {
|
||||
id: 'image', kind: 'image', asset_id: id('d'),
|
||||
x: 0.1, y: 0.2, w: 0.3, h: 0.4, angle: 405, opacity: 2,
|
||||
flip_h: true,
|
||||
};
|
||||
assert.deepEqual(projectDecorImage(shape, 1000, 500), [
|
||||
100, 100, 300, 200, 1,
|
||||
'translate(250 200) rotate(45) scale(-1 1) translate(-250 -200)',
|
||||
]);
|
||||
assert.equal(projectDecorImage({ ...shape, w: 0 }, 1000, 500), null);
|
||||
});
|
||||
|
||||
test('#51 resolve projection rejects malformed catalog rows', () => {
|
||||
const good = {
|
||||
asset_id: id('c'), name: 'safe.svg', mime: 'image/svg+xml',
|
||||
@@ -54,4 +68,20 @@ test('#51 resolve batches and deduplicates ids at the backend cap', async () =>
|
||||
const resolved = await resolveDecorAssets(hass, [ids[0], ...ids]);
|
||||
assert.equal(resolved.size, 201);
|
||||
assert.deepEqual(calls.map((batch) => batch.length), [200, 1]);
|
||||
assert.equal(await resolveDecorAssets(hass, [...ids].reverse()), resolved);
|
||||
assert.deepEqual(calls.map((batch) => batch.length), [200, 1]);
|
||||
});
|
||||
|
||||
test('#51 missing asset ids are negative-cached with their complete set', async () => {
|
||||
let calls = 0;
|
||||
const connection = {};
|
||||
const hass = {
|
||||
connection,
|
||||
callWS: async () => { calls++; return { assets: [], missing: [id('f')] }; },
|
||||
};
|
||||
const first = await resolveDecorAssets(hass, [id('f')]);
|
||||
const second = await resolveDecorAssets({ ...hass }, [id('f')]);
|
||||
assert.equal(first.size, 0);
|
||||
assert.equal(second, first);
|
||||
assert.equal(calls, 1);
|
||||
});
|
||||
|
||||
@@ -53,6 +53,20 @@ def test_svg_rejects_the_whole_unsafe_document(payload: bytes) -> None:
|
||||
validate_asset(payload, "unsafe.svg")
|
||||
|
||||
|
||||
def test_svg_utf16_doctype_is_rejected_before_entity_expansion() -> None:
|
||||
payload = '''<?xml version="1.0" encoding="UTF-16"?>
|
||||
<!DOCTYPE svg [
|
||||
<!ENTITY a "0123456789">
|
||||
<!ENTITY b "&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;">
|
||||
<!ENTITY c "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
|
||||
]>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1">
|
||||
<path d="&c;"/>
|
||||
</svg>'''.encode("utf-16")
|
||||
with pytest.raises(DecorAssetError, match="DTD, entities"):
|
||||
validate_asset(payload, "encoded.svg")
|
||||
|
||||
|
||||
def test_svg_canonicalizes_safe_geometry_and_requires_aspect_ratio() -> None:
|
||||
asset = validate_asset(
|
||||
b'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 20"><path d="M0 0L1 1"/></svg>',
|
||||
@@ -92,6 +106,28 @@ def test_svg_resource_limits_fail_closed() -> None:
|
||||
validate_asset(payload, "bounded.svg")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("attribute", [
|
||||
'opacity="NaN"', 'opacity="1.1"', 'stop-opacity="101%"', 'offset="-0.1"',
|
||||
])
|
||||
def test_svg_rejects_non_finite_or_out_of_range_unit_values(attribute: str) -> None:
|
||||
tag = "stop" if attribute.startswith(("stop", "offset")) else "path"
|
||||
payload = (
|
||||
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1">'
|
||||
f'<{tag} {attribute}/></svg>'
|
||||
).encode()
|
||||
with pytest.raises(DecorAssetError, match="invalid|range"):
|
||||
validate_asset(payload, "bounded.svg")
|
||||
|
||||
|
||||
def test_svg_rejects_one_oversized_attribute_before_tree_use() -> None:
|
||||
payload = (
|
||||
b'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1"><path d="'
|
||||
+ b"M" * 65_537 + b'"/></svg>'
|
||||
)
|
||||
with pytest.raises(DecorAssetError, match="safety limit"):
|
||||
validate_asset(payload, "bounded.svg")
|
||||
|
||||
|
||||
def test_valid_looking_but_truncated_raster_is_rejected_by_full_decode() -> None:
|
||||
with pytest.raises(DecorAssetError, match="corrupt|decode"):
|
||||
validate_asset(PNG_1X1[:33], "truncated.png", "image/png")
|
||||
|
||||
Reference in New Issue
Block a user