fix(plans): загрузка плана по HTTP, предел 8 МБ проверяется до отправки (#617)

План уходил base64 в WebSocket-кадре: файл больше ~3 МиБ давал кадр больше
4 МиБ, HA закрывал сокет до обработчика, и обещанные 8 МБ были недостижимы.

- бэкенд: HouseplanPlanUploadView (POST /api/houseplan/plans/upload), потоковый
  предел MAX_PLAN_BYTES (read_bounded), общий writer store_plan_upload для view
  и ws_plan_set (контракт WS без изменений);
- карточка: stagePlanFile/uploadPlanFile/renderPlanBackdropGuard в
  backdrop-pick.ts для обоих рантаймов; PlanFilePayload хранит Blob вместо b64;
  SVG больше предела — тост при выборе, растр — диалог #39 только с уменьшенной
  копией, копия больше предела не попадает в staging, 413 называет предел;
- i18n toast.plan_too_large, backdrop.over_limit_body (en/ru/de/fr);
  USER-GUIDE ru/en, CHANGELOG ru/en, docs/testing-notes (#617);
- тесты: test/plan-upload-limit.test.mjs, tests_backend/test_plan_upload.py,
  test_ha_upload.py (#617), smoke_plan_upload_limit.mjs; три смока переведены
  с b64/plan/set на blob/fetchWithAuth; мутанты plan-upload-*;
- база монолита: hostRefs +3 (общий хелпер плана вместо двух копий в рантаймах,
  новый тост уменьшенной копии).

Issue: #617
User-Visible: yes
This commit is contained in:
Claude
2026-09-24 19:57:41 +03:00
parent f8e089bdf2
commit 857369b18b
56 changed files with 2077 additions and 232 deletions
File diff suppressed because one or more lines are too long
+8 -4
View File
@@ -1,14 +1,18 @@
<<<<<<<< HEAD:dist/houseplan-assets/editor-B8R_L8fh.js
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="e40dce8a0e21fa0d8552321bbcf22b164da42dda6875c015cf13dbdfe105abb4";import{aD as e,l as o,aG as t,ee as s,A as a,ef as i,eg as l,eh as n,E as r,c}from"./houseplan-card-B455eSod.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__="7ec6b12023747a2dd1b234bf6f39993245f064a827fc3f6e3bf62c6817451c0c";import{aD as e,l as o,aG as t,ee as s,A as a,ef as i,eg as l,eh as n,E as r,c}from"./houseplan-card-6pfPfOB6.js";class f 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,f={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")},h=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
>>>>>>>> f2c9321a (fix(plans): загрузка плана по HTTP, предел 8 МБ проверяется до отправки (#617)):dist/houseplan-assets/editor-q-9Og1RZ.js
.hass=${this.hass}
.data=${this._formData}
.schema=${e}
.computeLabel=${e=>h[e.name]||e.name}
.computeLabel=${e=>f[e.name]||e.name}
@value-changed=${this._valueChanged}
></ha-form>`;return c`
${d(f.slice(0,3))}
${d(h.slice(0,3))}
${_?c`<div class="default-floor-error" role="alert"
style="color:var(--error-color,#db4437);margin:-4px 0 12px;overflow-wrap:anywhere">
${t(s,"editor.default_floor_missing",{id:_})}
</div>`:a}
${d(f.slice(3))}
`}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}h.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",h);
${d(h.slice(3))}
`}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}f.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",f);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+55 -51
View File
@@ -1,4 +1,8 @@
<<<<<<<< HEAD:dist/houseplan-assets/summary-panel-editor-DTCyzN9Y.js
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="e40dce8a0e21fa0d8552321bbcf22b164da42dda6875c015cf13dbdfe105abb4";import{c as a,A as e}from"./houseplan-card-B455eSod.js";import{s,n as t,a as l,m as r,b as o,c as i,d as u}from"./summary-panel-runtime-loaded-zOTsmmsy.js";import"./form-kit.styles-C7T6wUDs.js";const m=m=>{const{host:c,dialog:d,problems:n,t:b}=m,y=n.find(a=>"error"===a.kind),$=d.localShow!==d.baseLocalShow||!d.localOnly&&!s(t(d.draft),d.base),p=$&&!d.busy&&!y,v=a=>n.find(e=>e.path===a),h=a=>a?b(`summary.problem.${a.code}`):"",k=()=>m.close(),g=a`<label class="summary-switch" for="summary-local-show">
========
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="7ec6b12023747a2dd1b234bf6f39993245f064a827fc3f6e3bf62c6817451c0c";import{c as a,A as s}from"./houseplan-card-6pfPfOB6.js";import{s as e,n as t,a as l,m as r,b as o,c as i,d as u}from"./summary-panel-runtime-loaded-CfOaau8N.js";import"./form-kit.styles-bTArvCsM.js";const m=m=>{const{host:c,dialog:d,problems:n,t:b}=m,y=n.find(a=>"error"===a.kind),$=d.localShow!==d.baseLocalShow||!d.localOnly&&!e(t(d.draft),d.base),p=$&&!d.busy&&!y,v=a=>n.find(s=>s.path===a),h=a=>a?b(`summary.problem.${a.code}`):"",k=()=>m.close(),g=a`<label class="summary-switch" for="summary-local-show">
>>>>>>>> f2c9321a (fix(plans): загрузка плана по HTTP, предел 8 МБ проверяется до отправки (#617)):custom_components/houseplan/frontend/houseplan-assets/summary-panel-editor-DVgdA45F.js
<span class="summary-switch-caption"><strong>${b("summary.show_local")}</strong>
<small id="summary-local-show-hint">${b("summary.show_local_hint")}</small></span>
<input id="summary-local-show" data-summary-local-show type="checkbox"
@@ -10,31 +14,31 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="e40dce8a0e21fa0d8552321bbcf22b164da4
dismiss-on-scrim aria-busy=${String(d.busy)}
@hp-close=${k}>
<div class="body summary-editor" @click=${()=>m.closeSource()}>
${d.localOnly?a`<p class="summary-local-hint">${b(d.localOnlyHint)}</p>`:e}
${d.localOnly?a`<p class="summary-local-hint">${b(d.localOnlyHint)}</p>`:s}
<section class="summary-general" aria-labelledby="summary-general-title">
<h3 id="summary-general-title">${b("summary.general_settings")}</h3>
<div class="summary-general-grid">
${d.localOnly?e:a`<div class="summary-field">
${d.localOnly?s:a`<div class="summary-field">
<label for="summary-panel-title">${b("summary.panel_title")}</label>
<input id="summary-panel-title" type="text" .value=${d.draft.title}
?disabled=${d.busy}
data-summary-error=${String(d.attempted&&"title"===y?.path)}
aria-invalid=${"error"===v("title")?.kind?"true":"false"}
@input=${a=>m.mutate(e=>{e.title=a.target.value})} />
${v("title")?a`<div class="summary-problem error">${h(v("title"))}</div>`:e}
@input=${a=>m.mutate(s=>{s.title=a.target.value})} />
${v("title")?a`<div class="summary-problem error">${h(v("title"))}</div>`:s}
</div>`}
${g}
${d.localOnly?e:a`<label class="summary-switch" for="summary-mobile-show">
${d.localOnly?s:a`<label class="summary-switch" for="summary-mobile-show">
<span class="summary-switch-caption"><strong>${b("summary.show_mobile")}</strong>
<small id="summary-mobile-show-hint">${b("summary.show_mobile_hint")}</small></span>
<input id="summary-mobile-show" data-summary-mobile-show type="checkbox"
.checked=${d.draft.show_on_mobile} ?disabled=${!d.localShow||d.busy}
aria-label=${b("summary.show_mobile")} aria-describedby="summary-mobile-show-hint"
@change=${a=>m.mutate(e=>{e.show_on_mobile=a.target.checked})} /></label>`}
@change=${a=>m.mutate(s=>{s.show_on_mobile=a.target.checked})} /></label>`}
</div>
${m.storageUnavailable?a`<div class="summary-problem warning">${b("summary.storage_unavailable")}</div>`:e}
${m.storageUnavailable?a`<div class="summary-problem warning">${b("summary.storage_unavailable")}</div>`:s}
</section>
${d.localOnly?e:a`
${d.localOnly?s:a`
<section class="summary-blocks-card" aria-labelledby="summary-blocks-title">
<header class="summary-blocks-heading">
<div><h3 id="summary-blocks-title">${b("summary.blocks")}</h3>
@@ -42,7 +46,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="e40dce8a0e21fa0d8552321bbcf22b164da4
<span class="summary-block-count">${b("summary.block_count").replace("{count}",String(d.draft.blocks.length)).replace("{limit}","10")}</span>
</header>
<div class="summary-editor-blocks">
${d.draft.blocks.map((s,t)=>{const u=`blocks.${t}`,n=v(`${u}.scope`);return a`<article class="summary-editor-block"
${d.draft.blocks.map((e,t)=>{const u=`blocks.${t}`,n=v(`${u}.scope`);return a`<article class="summary-editor-block"
@dragover=${a=>a.preventDefault()}
@drop=${a=>m.drop(a,`block:${t}`)}>
<div class="summary-block-head">
@@ -57,40 +61,40 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="e40dce8a0e21fa0d8552321bbcf22b164da4
?disabled=${t===d.draft.blocks.length-1||d.busy}
@click=${()=>m.mutate(a=>{a.blocks=r(a.blocks,t,t+1)})}>${l("down")}</button>
</div>
<input class="summary-block-title" type="text" .value=${s.title}
<input class="summary-block-title" type="text" .value=${e.title}
placeholder=${b("summary.block_title")} aria-label=${b("summary.block_title")}
?disabled=${d.busy}
data-summary-error=${String(d.attempted&&y?.path===`${u}.title`)}
aria-invalid=${"error"===v(`${u}.title`)?.kind?"true":"false"}
@input=${a=>m.mutate(e=>{e.blocks[t].title=a.target.value})} />
@input=${a=>m.mutate(s=>{s.blocks[t].title=a.target.value})} />
<button class="summary-icon-button summary-visibility" type="button"
?disabled=${d.busy} aria-pressed=${String(s.visible)}
title=${b(s.visible?"summary.hide_block":"summary.show_block")}
aria-label=${b(s.visible?"summary.hide_block":"summary.show_block")}
@click=${()=>m.mutate(a=>{a.blocks[t].visible=!a.blocks[t].visible})}>${l(s.visible?"eye":"eyeOff")}</button>
${v(`${u}.title`)?a`<div class="summary-problem error summary-block-title-error">${h(v(`${u}.title`))}</div>`:e}
?disabled=${d.busy} aria-pressed=${String(e.visible)}
title=${b(e.visible?"summary.hide_block":"summary.show_block")}
aria-label=${b(e.visible?"summary.hide_block":"summary.show_block")}
@click=${()=>m.mutate(a=>{a.blocks[t].visible=!a.blocks[t].visible})}>${l(e.visible?"eye":"eyeOff")}</button>
${v(`${u}.title`)?a`<div class="summary-problem error summary-block-title-error">${h(v(`${u}.title`))}</div>`:s}
<div class="summary-scope">
<select .value=${s.scope.type} ?disabled=${d.busy}
<select .value=${e.scope.type} ?disabled=${d.busy}
aria-label=${b("summary.scope")}
@change=${a=>m.mutate(e=>{e.blocks[t].scope="space"===a.target.value?{type:"space",space_id:c._space}:{type:"all"}})}>
<option value="all" ?selected=${"all"===s.scope.type}>${b("summary.scope_all")}</option>
<option value="space" ?selected=${"space"===s.scope.type}>${b("summary.scope_space")}</option>
@change=${a=>m.mutate(s=>{s.blocks[t].scope="space"===a.target.value?{type:"space",space_id:c._space}:{type:"all"}})}>
<option value="all" ?selected=${"all"===e.scope.type}>${b("summary.scope_all")}</option>
<option value="space" ?selected=${"space"===e.scope.type}>${b("summary.scope_space")}</option>
</select>
${"space"===s.scope.type?a`<select .value=${s.scope.space_id}
${"space"===e.scope.type?a`<select .value=${e.scope.space_id}
?disabled=${d.busy} aria-label=${b("summary.scope_space")}
data-summary-error=${String(d.attempted&&y?.path===`${u}.scope`)}
aria-invalid=${"error"===n?.kind?"true":"false"}
@change=${a=>m.mutate(e=>{const s=e.blocks[t].scope;"space"===s.type&&(s.space_id=a.target.value)})}>
${c._model.some(a=>a.id===s.scope.space_id)?e:a`<option value=${s.scope.space_id} selected>${s.scope.space_id}</option>`}
${c._model.map(e=>a`<option value=${e.id}
?selected=${e.id===s.scope.space_id}>${e.title}</option>`)}
</select>`:e}
@change=${a=>m.mutate(s=>{const e=s.blocks[t].scope;"space"===e.type&&(e.space_id=a.target.value)})}>
${c._model.some(a=>a.id===e.scope.space_id)?s:a`<option value=${e.scope.space_id} selected>${e.scope.space_id}</option>`}
${c._model.map(s=>a`<option value=${s.id}
?selected=${s.id===e.scope.space_id}>${s.title}</option>`)}
</select>`:s}
</div>
${n?a`<div class="summary-problem summary-scope-problem ${n.kind}">${h(n)}</div>`:e}
${n?a`<div class="summary-problem summary-scope-problem ${n.kind}">${h(n)}</div>`:s}
</div>
<div class="summary-block-body">
<div class="summary-editor-values">
${s.values.map((i,c)=>{const n=`${u}.values.${c}`,$=v(`${n}.source`),p=m.sourceToken(i.source),k=d.activeSource?.blockId===s.id&&d.activeSource.valueId===i.id,g="system"===i.source.type?b(`summary.system.${i.source.key}`):i.source.entity_id?m.entityIndex.labels.get(i.source.entity_id)||i.source.entity_id:b("summary.select_source"),_="system"===i.source.type?b("summary.system_source"):i.source.entity_id,f=k?o(m.entityIndex,d.entityFilter):null,w="entity"===i.source.type&&!!i.source.entity_id&&!m.entityIndex.labels.has(i.source.entity_id),S=`${s.id}\n${i.id}`;return a`<div class="summary-editor-value"
${e.values.map((i,c)=>{const n=`${u}.values.${c}`,$=v(`${n}.source`),p=m.sourceToken(i.source),k=d.activeSource?.blockId===e.id&&d.activeSource.valueId===i.id,g="system"===i.source.type?b(`summary.system.${i.source.key}`):i.source.entity_id?m.entityIndex.labels.get(i.source.entity_id)||i.source.entity_id:b("summary.select_source"),_="system"===i.source.type?b("summary.system_source"):i.source.entity_id,f=k?o(m.entityIndex,d.entityFilter):null,w="entity"===i.source.type&&!!i.source.entity_id&&!m.entityIndex.labels.has(i.source.entity_id),S=`${e.id}\n${i.id}`;return a`<div class="summary-editor-value"
@dragover=${a=>{a.preventDefault(),a.stopPropagation()}}
@drop=${a=>m.drop(a,`value:${t}:${c}`)}>
<span class="summary-drag" draggable=${String(!d.busy)} title=${b("summary.drag")}
@@ -101,7 +105,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="e40dce8a0e21fa0d8552321bbcf22b164da4
@click=${()=>m.mutate(a=>{a.blocks[t].values=r(a.blocks[t].values,c,c-1)})}>${l("up")}</button>
<button class="summary-icon-button" type="button" title=${b("summary.down")}
aria-label=${b("summary.down")}
?disabled=${c===s.values.length-1||d.busy}
?disabled=${c===e.values.length-1||d.busy}
@click=${()=>m.mutate(a=>{a.blocks[t].values=r(a.blocks[t].values,c,c+1)})}>${l("down")}</button>
</div>
<div class="summary-value-label">
@@ -109,8 +113,8 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="e40dce8a0e21fa0d8552321bbcf22b164da4
placeholder=${b("summary.value_name")} aria-label=${b("summary.value_name")}
data-summary-error=${String(d.attempted&&y?.path===`${n}.label`)}
aria-invalid=${"error"===v(`${n}.label`)?.kind?"true":"false"}
@input=${a=>m.mutate(e=>{e.blocks[t].values[c].label=a.target.value})} />
${v(`${n}.label`)?a`<div class="summary-problem error">${h(v(`${n}.label`))}</div>`:e}
@input=${a=>m.mutate(s=>{s.blocks[t].values[c].label=a.target.value})} />
${v(`${n}.label`)?a`<div class="summary-problem error">${h(v(`${n}.label`))}</div>`:s}
</div>
<div class="summary-source-field">
<button class="summary-source" type="button"
@@ -119,9 +123,9 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="e40dce8a0e21fa0d8552321bbcf22b164da4
data-summary-error=${String(d.attempted&&y?.path===`${n}.source`)}
aria-invalid=${"error"===$?.kind?"true":"false"}
aria-haspopup="listbox" aria-expanded=${k?"true":"false"}
@click=${a=>{a.stopPropagation(),k?m.closeSource():m.openSource(s.id,i.id)}}>
@click=${a=>{a.stopPropagation(),k?m.closeSource():m.openSource(e.id,i.id)}}>
<span class="summary-source-caption"><strong>${g}</strong>
${_?a`<small>${_}</small>`:e}</span>
${_?a`<small>${_}</small>`:s}</span>
${l("chevron")}
</button>
${k&&f?a`<div class="summary-source-picker"
@@ -135,39 +139,39 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="e40dce8a0e21fa0d8552321bbcf22b164da4
<div class="summary-source-results" role="listbox"
aria-label=${b("summary.select_source")}>
<div class="summary-source-group">${b("summary.system_group")}</div>
${["device_count","total_area","datetime"].map(e=>a`
${["device_count","total_area","datetime"].map(s=>a`
<button type="button" role="option"
?disabled=${d.busy}
aria-selected=${p===`system:${e}`?"true":"false"}
@click=${()=>m.setSource(s.id,i.id,`system:${e}`)}>
${b(`summary.system.${e}`)}
aria-selected=${p===`system:${s}`?"true":"false"}
@click=${()=>m.setSource(e.id,i.id,`system:${s}`)}>
${b(`summary.system.${s}`)}
</button>`)}
${w?a`<div class="summary-source-group">${b("summary.current_source")}</div>
<button type="button" role="option" class="broken" aria-selected="true"
?disabled=${d.busy}
@click=${()=>m.setSource(s.id,i.id,p)}>
@click=${()=>m.setSource(e.id,i.id,p)}>
${g}
</button>`:e}
</button>`:s}
<div class="summary-source-group">${b("summary.entities_group")}</div>
${f.entries.map(e=>a`<button type="button" role="option"
${f.entries.map(s=>a`<button type="button" role="option"
?disabled=${d.busy}
aria-selected=${p===`entity:${e.id}`?"true":"false"}
@click=${()=>m.setSource(s.id,i.id,`entity:${e.id}`)}>
<span>${e.label}</span><small>${e.id}</small>
aria-selected=${p===`entity:${s.id}`?"true":"false"}
@click=${()=>m.setSource(e.id,i.id,`entity:${s.id}`)}>
<span>${s.label}</span><small>${s.id}</small>
</button>`)}
${f.total?e:a`<div class="summary-empty">${b("summary.no_search_results")}</div>`}
${f.truncated?a`<div class="summary-refine">${b("summary.refine_search")}</div>`:e}
${f.total?s:a`<div class="summary-empty">${b("summary.no_search_results")}</div>`}
${f.truncated?a`<div class="summary-refine">${b("summary.refine_search")}</div>`:s}
</div>
</div>`:e}
${$?a`<div class="summary-problem ${$.kind}">${h($)}</div>`:e}
</div>`:s}
${$?a`<div class="summary-problem ${$.kind}">${h($)}</div>`:s}
</div>
<button class="summary-icon-button summary-value-remove" type="button"
title=${b("btn.delete")} aria-label=${b("btn.delete")} ?disabled=${d.busy}
@click=${()=>m.mutate(a=>{a.blocks[t].values.splice(c,1)})}>${l("close")}</button>
</div>`})}
<button class="summary-add summary-add-value" type="button"
?disabled=${s.values.length>=20||d.busy}
title=${s.values.length>=20?b("summary.limit_values"):""}
?disabled=${e.values.length>=20||d.busy}
title=${e.values.length>=20?b("summary.limit_values"):""}
@click=${()=>m.mutate(a=>{a.blocks[t].values.push(i())})}>
${l("plus")}${b("summary.add_value")}
</button>
@@ -186,11 +190,11 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="e40dce8a0e21fa0d8552321bbcf22b164da4
${l("plus")}${b("summary.add_block")}
</button>
</section>
${d.error?a`<div class="summary-problem error" role="alert">${d.error}</div>`:e}
${d.error?a`<div class="summary-problem error" role="alert">${d.error}</div>`:s}
${d.conflict?a`<button class="btn ghost summary-reload" type="button"
?disabled=${d.busy} @click=${()=>m.reload()}>
<ha-icon icon="mdi:reload"></ha-icon>${b("summary.reload_current")}
</button>`:e}
</button>`:s}
`}
</div>
<div class="row summary-editor-footer" slot="footer">
@@ -1 +1,5 @@
<<<<<<<< HEAD:dist/houseplan-assets/summary-panel-metrics-CO3xPXf-.js
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="e40dce8a0e21fa0d8552321bbcf22b164da42dda6875c015cf13dbdfe105abb4";import{em as e,N as t,a$ as n,L as i,ae as o,ac as r,bV as s,bU as a,a_ as c,aZ as l,b0 as d,en as u,eo as f,bX as m,ep as g}from"./houseplan-card-B455eSod.js";function v(e){if(!e.registry.authoritative)return null;const t=new Set,n=d(e.markers);for(const i of Object.values(e.registry.devices||{}))i?.id&&"service"!==i.entry_type&&!n.devices.has(i.id)&&i.area_id&&e.areaToSpace[i.area_id]&&t.add(i.id);for(const i of e.markers||[]){if(i.removed||"virtual"===i.binding)continue;const o=String(i.binding||"").indexOf(":");if(o<1)continue;const r=i.binding.slice(0,o),s=i.binding.slice(o+1),a="device"===r?e.registry.devices?.[s]:null,c="entity"===r?e.registry.entities?.[s]:null,l="device"===r?s:c?.device_id,d="device"===r?a?.area_id:c?.area_id||l&&e.registry.devices?.[l]?.area_id,u="string"==typeof i.room_id&&i.room_id.length>0&&null===i.area?"":i.area||d||"",f=u&&e.areaToSpace[u]||i.space||e.firstSpaceId;l&&e.registry.devices?.[l]&&e.spaceIds.has(f)&&(!n.devices.has(l)||"entity"===r&&n.liveEntities.has(s))&&t.add(l)}return t}function y(e,t){return e?.length?t?.length?g(e,t):e:t}function _(e,t){const n=c(e.rooms,t.walls,t.openCuts,[],i,t.cellCm,o,r);return{roomGeom:"ok"===n.status||"degraded-extra"===n.status?n.roomGeom:void 0,multiWallNodes:l(e.rooms,t.walls,t.openCuts,i,t.cellCm,o,r)}}function*b(c,l,d=_){try{let u=0;for(const f of l){const l=c.spaces.find(e=>String(e?.id)===f.id);if(!l)continue;const m=e(l,f),g=d(f,m);let v=null;for(const e of f.rooms){if(!e.id)continue;const a=t(e);if(!a)continue;const c=n(f.rooms,e.id,m.walls,m.openCuts,i,m.cellCm,o,r,g.roomGeom,g.multiWallNodes)||a;v=y(v,s(c,m.physicalBodies)),yield}const _=m.cellCm/o;u+=a(v)*_*_/1e4}return u}catch{return null}}function p(e,t,n=_){const i=b(e,t,n);let o=i.next();for(;!o.done;)o=i.next();return o.value}function h(e,t,n,i){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:m(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(i||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function S(e,t){const n=e?.states?.[t];if(!n)return null;const i=u(e,t);return i?f(i,String(n.attributes?.unit_of_measurement||"")):null}export{b as cleanFloorAreaSteps,v as representedHaDeviceIds,_ as spaceWallGeometry,S as summaryEntityValue,h as summarySystemValue,p as totalCleanFloorAreaM2};
========
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="7ec6b12023747a2dd1b234bf6f39993245f064a827fc3f6e3bf62c6817451c0c";import{ek as e,N as t,a$ as n,L as i,ae as o,ac as r,bT as s,bS as a,a_ as c,aZ as l,b0 as u,el as d,em as f,bV as m,en as g}from"./houseplan-card-6pfPfOB6.js";function v(e){if(!e.registry.authoritative)return null;const t=new Set,n=u(e.markers);for(const i of Object.values(e.registry.devices||{}))i?.id&&"service"!==i.entry_type&&!n.devices.has(i.id)&&i.area_id&&e.areaToSpace[i.area_id]&&t.add(i.id);for(const i of e.markers||[]){if(i.removed||"virtual"===i.binding)continue;const o=String(i.binding||"").indexOf(":");if(o<1)continue;const r=i.binding.slice(0,o),s=i.binding.slice(o+1),a="device"===r?e.registry.devices?.[s]:null,c="entity"===r?e.registry.entities?.[s]:null,l="device"===r?s:c?.device_id,u="device"===r?a?.area_id:c?.area_id||l&&e.registry.devices?.[l]?.area_id,d="string"==typeof i.room_id&&i.room_id.length>0&&null===i.area?"":i.area||u||"",f=d&&e.areaToSpace[d]||i.space||e.firstSpaceId;l&&e.registry.devices?.[l]&&e.spaceIds.has(f)&&(!n.devices.has(l)||"entity"===r&&n.liveEntities.has(s))&&t.add(l)}return t}function y(e,t){return e?.length?t?.length?g(e,t):e:t}function _(e,t){const n=c(e.rooms,t.walls,t.openCuts,[],i,t.cellCm,o,r);return{roomGeom:"ok"===n.status||"degraded-extra"===n.status?n.roomGeom:void 0,multiWallNodes:l(e.rooms,t.walls,t.openCuts,i,t.cellCm,o,r)}}function*h(c,l,u=_){try{let d=0;for(const f of l){const l=c.spaces.find(e=>String(e?.id)===f.id);if(!l)continue;const m=e(l,f),g=u(f,m);let v=null;for(const e of f.rooms){if(!e.id)continue;const a=t(e);if(!a)continue;const c=n(f.rooms,e.id,m.walls,m.openCuts,i,m.cellCm,o,r,g.roomGeom,g.multiWallNodes)||a;v=y(v,s(c,m.physicalBodies)),yield}const _=m.cellCm/o;d+=a(v)*_*_/1e4}return d}catch{return null}}function p(e,t,n=_){const i=h(e,t,n);let o=i.next();for(;!o.done;)o=i.next();return o.value}function b(e,t,n,i){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:m(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(i||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function S(e,t){const n=e?.states?.[t];if(!n)return null;const i=d(e,t);return i?f(i,String(n.attributes?.unit_of_measurement||"")):null}export{h as cleanFloorAreaSteps,v as representedHaDeviceIds,_ as spaceWallGeometry,S as summaryEntityValue,b as summarySystemValue,p as totalCleanFloorAreaM2};
>>>>>>>> f2c9321a (fix(plans): загрузка плана по HTTP, предел 8 МБ проверяется до отправки (#617)):dist/houseplan-assets/summary-panel-metrics-CV7ZVoeM.js
File diff suppressed because one or more lines are too long