mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-30 11:49:16 +00:00
fix: the locale runtime the card ships is the code the tests prove (#354)
The production LANGUAGE_RUNTIME was a handwritten twin of the tested LanguageRuntime class (germanDictionary/Pending/Failed): equivalent on the day it was written, invisible to every i18n-runtime test afterwards. The registry now exports one page-scoped `new LanguageRuntime(LANGUAGE_REGISTRY, …)` instance — the whole existing suite starts proving the object production actually runs, and a contract unit (instanceof + source free of the old field names) keeps the duplicate from returning. The class gains an optional `loadFailed(code)` hook — fired once when a dictionary load settles into English fallback — and the registry fans it out through `subscribeLanguageLoadFailures`. Only the View card subscribes (it alone owns toast infrastructure): a failed language pack now shows the new `toast.locale_load_failed` message (en/ru/de) instead of a console-only warning; space card and both GUI editors keep the console warning as before. Proofs: contract unit, hook unit, subscription unit; smoke_german_locale extended — the both-attempts-failed scenario now asserts the visible toast; two new registry mutants (handwritten-twin returns, toast dropped). Issue: #354 User-Visible: yes
This commit is contained in:
@@ -1,111 +1,111 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "99af70faf186a79ae24e2901edbf39e13d6388796d8958ab05fc9b1fca671aae",
|
||||
"fingerprint": "f4223f763dcd6ca7756b77bef6936c1cfd4eff678814c4d37f22bc3a15f49852",
|
||||
"entry": "houseplan-card.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-g88CD9VI.js",
|
||||
"houseplan-assets/houseplan-card-CRVz7yAZ.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 256828,
|
||||
"initialViewGzipBytes": 257222,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/de-CFxUrXZ2.js",
|
||||
"houseplan-assets/editor-Do71pUmD.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BuT5Y1tf.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D8yGRdVi.js",
|
||||
"houseplan-assets/space-deletion-3-uORj4C.js"
|
||||
"houseplan-assets/de-CaUzN0Gb.js",
|
||||
"houseplan-assets/editor-Cx6zLzlf.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-g5SOkk1f.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-gghL8Cm4.js",
|
||||
"houseplan-assets/space-deletion-Efs-z9tD.js"
|
||||
],
|
||||
"lazyGzipBytes": 160366,
|
||||
"lazyGzipBytes": 160428,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/editor-Do71pUmD.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BuT5Y1tf.js",
|
||||
"houseplan-assets/space-deletion-3-uORj4C.js"
|
||||
"houseplan-assets/editor-Cx6zLzlf.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-g5SOkk1f.js",
|
||||
"houseplan-assets/space-deletion-Efs-z9tD.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 131834,
|
||||
"lazyEditorGzipBytes": 131845,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D8yGRdVi.js",
|
||||
"houseplan-assets/space-deletion-3-uORj4C.js"
|
||||
"houseplan-assets/houseplan-onboarding-runtime-gghL8Cm4.js",
|
||||
"houseplan-assets/space-deletion-Efs-z9tD.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 7196,
|
||||
"lazyOnboardingGzipBytes": 7203,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-CFxUrXZ2.js"
|
||||
"houseplan-assets/de-CaUzN0Gb.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 21886,
|
||||
"lazyLocaleGzipBytes": 21934,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/de-CFxUrXZ2.js",
|
||||
"sha256": "62ceb0c8d016c48de243b8d643b4f02bae8a623dc8604d115af1420ecb6f7582",
|
||||
"rawBytes": 71541,
|
||||
"gzipBytes": 21886,
|
||||
"path": "houseplan-assets/de-CaUzN0Gb.js",
|
||||
"sha256": "28905f1ea79271d20a7837a9c03b5492dd4e581570dda1c1744b6c0649b38fbd",
|
||||
"rawBytes": 71667,
|
||||
"gzipBytes": 21934,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-Do71pUmD.js",
|
||||
"sha256": "71dea046e80ff8ce30728044ad3cf85ee536798e230306f5ec1e8824a6e0e1d2",
|
||||
"path": "houseplan-assets/editor-Cx6zLzlf.js",
|
||||
"sha256": "e1234c8e7ff9c1cc932d45a633fef965a23b6c8db9dc5b684092df6b25a5bffa",
|
||||
"rawBytes": 3831,
|
||||
"gzipBytes": 1583,
|
||||
"gzipBytes": 1586,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-g88CD9VI.js"
|
||||
"houseplan-assets/houseplan-card-CRVz7yAZ.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-g88CD9VI.js",
|
||||
"sha256": "2040dd1630535449eb8ef3b1a1cfb71da2e14c59b7134b7d64b280dc91b9770f",
|
||||
"rawBytes": 911212,
|
||||
"gzipBytes": 256075,
|
||||
"path": "houseplan-assets/houseplan-card-CRVz7yAZ.js",
|
||||
"sha256": "c8ed01fa23f357e2a2691a4b64ba459f67b2ff9567314be2159a30429fe951dd",
|
||||
"rawBytes": 912395,
|
||||
"gzipBytes": 256466,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-CFxUrXZ2.js",
|
||||
"houseplan-assets/editor-Do71pUmD.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BuT5Y1tf.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D8yGRdVi.js"
|
||||
"houseplan-assets/de-CaUzN0Gb.js",
|
||||
"houseplan-assets/editor-Cx6zLzlf.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-g5SOkk1f.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-gghL8Cm4.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-BuT5Y1tf.js",
|
||||
"sha256": "1d6bd9cf7036174a2d3daba802c1a98c1dc69e118752779be1ad5aa6bd956acd",
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-g5SOkk1f.js",
|
||||
"sha256": "9e82dc7246a631a56e31e842603e099fdb5f8b89d2c4b282af4f3306330e3577",
|
||||
"rawBytes": 504959,
|
||||
"gzipBytes": 129701,
|
||||
"gzipBytes": 129705,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-g88CD9VI.js",
|
||||
"houseplan-assets/space-deletion-3-uORj4C.js"
|
||||
"houseplan-assets/houseplan-card-CRVz7yAZ.js",
|
||||
"houseplan-assets/space-deletion-Efs-z9tD.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-D8yGRdVi.js",
|
||||
"sha256": "9777a5eb4166704d294d9972f86d0897ec229e40116eb9def15147cd5bf45007",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-gghL8Cm4.js",
|
||||
"sha256": "8b429f66b9bdc598278504cebde6800730c9f8d19e42c7b184d6cd7445a92b04",
|
||||
"rawBytes": 26356,
|
||||
"gzipBytes": 6646,
|
||||
"gzipBytes": 6649,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-g88CD9VI.js",
|
||||
"houseplan-assets/space-deletion-3-uORj4C.js"
|
||||
"houseplan-assets/houseplan-card-CRVz7yAZ.js",
|
||||
"houseplan-assets/space-deletion-Efs-z9tD.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/space-deletion-3-uORj4C.js",
|
||||
"sha256": "bccd5edc4a4113d78ea05a475b8cadc1175287df2e67327213a95e9647074e25",
|
||||
"rawBytes": 906,
|
||||
"gzipBytes": 550,
|
||||
"path": "houseplan-assets/space-deletion-Efs-z9tD.js",
|
||||
"sha256": "534af6bd61ad2404183665d7401e625383a59321feeb55db99c514d9f23415aa",
|
||||
"rawBytes": 911,
|
||||
"gzipBytes": 554,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "228910d9e3580441d5a1064cb5107e2f2dd80d99b24a325b2b54843eb32f0009",
|
||||
"sha256": "a950c24f9f8f6cc9c50a67a5b41ad644acda7dc94501f4cbe2e771b0fa271090",
|
||||
"rawBytes": 1089,
|
||||
"gzipBytes": 753,
|
||||
"gzipBytes": 756,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-g88CD9VI.js"
|
||||
"houseplan-assets/houseplan-card-CRVz7yAZ.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="99af70faf186a79ae24e2901edbf39e13d6388796d8958ab05fc9b1fca671aae";import{c as e,l as o,t,d2 as s,A as a,d3 as i,d4 as l,d5 as n,E as r,b as c}from"./houseplan-card-g88CD9VI.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
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="f4223f763dcd6ca7756b77bef6936c1cfd4eff678814c4d37f22bc3a15f49852";import{c as e,l as o,t,d2 as s,A as a,d3 as i,d4 as l,d5 as n,E as r,b as c}from"./houseplan-card-CRVz7yAZ.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
|
||||
.hass=${this.hass}
|
||||
.data=${this._formData}
|
||||
.schema=${e}
|
||||
+26
-26
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="99af70faf186a79ae24e2901edbf39e13d6388796d8958ab05fc9b1fca671aae";import{bU as t,bV as s,bW as e,bX as o,aq as a,bY as l,c1 as i,bZ as h,b_ as n,b$ as c,c0 as r,b as p,A as _,cV as d,ap as u,ar as g,cW as m}from"./houseplan-card-g88CD9VI.js";import{i as b,c as $,a as v,s as f,t as y}from"./space-deletion-3-uORj4C.js";const w=1e3,D=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},S="99af70faf186a79ae24e2901edbf39e13d6388796d8958ab05fc9b1fca671aae";class C{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_openSpaceDialog(p,_){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===p){const i=this.host._serverCfg.spaces.find(t=>t.id===_);if(!i)return;const h=t(i),n=i.settings?.custom_fill&&"object"==typeof i.settings.custom_fill?s(i.settings.custom_fill):null,c="none"===h.fill?{...n||e,a:0}:n;return void(this.host._spaceDialog={mode:p,spaceId:_,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",showBorders:h.showBorders,showNames:h.showNames,zeroWallStyle:l(i),displayTouched:!0,hideDecor:h.hideDecor,hideOpenings:h.hideOpenings,roomColor:h.color,roomOpacity:h.opacity,fillMode:"none"===h.fill?"custom":h.fill,customFill:c,glowEnabled:h.glow,bgColor:h.bgColor,bgMode:"static"===i.settings?.bg_mode||"daynight"===i.settings?.bg_mode?i.settings.bg_mode:null,northDeg:a({},i.settings),sunRays:"boolean"==typeof i.settings?.sun_rays?i.settings.sun_rays:null,tempMin:h.tempMin,tempMax:h.tempMax,showLqi:h.showLqi??this.host._config?.show_signal??!0,cardFontScale:h.cardFontScale,labelTemp:h.labelTemp,labelHum:h.labelHum,labelLqi:h.labelLqi,labelLight:h.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,cellCmInput:o(Number(i.cell_cm)>0?Number(i.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const d=i(this.host._imperial);this.host._spaceDialog={mode:p,title:"",planUrl:null,planFile:null,...b(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:r,roomOpacity:c,fillMode:"custom",customFill:{...e,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:n,tempMax:h,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:d,cellCmInput:o(d,this.host._imperial),cellCmTouched:!1,busy:!1}}async _pickPlanFile(t){const s=t.target,e=s.files?.[0];if(!e||!this.host._spaceDialog)return;const o={"image/svg+xml":"svg","image/png":"png","image/jpeg":"jpg","image/webp":"webp"}[e.type]||(e.name.toLowerCase().endsWith(".svg")?"svg":"");if(!o)return void this.host._showToast(this.host._t("toast.plan_formats"));const a=new Uint8Array(await e.arrayBuffer());let l="";for(let t=0;t<a.length;t+=32768)l+=String.fromCharCode(...a.subarray(t,t+32768));const i=btoa(l),h=URL.createObjectURL(e),n=await new Promise(t=>{const s=new Image;s.onload=()=>t(s.naturalWidth&&s.naturalHeight?s.naturalWidth/s.naturalHeight:1.414),s.onerror=()=>t(1.414),s.src=h});URL.revokeObjectURL(h),this.host._spaceDialog={...this.host._spaceDialog,planFile:{ext:o,b64:i,aspect:n,name:e.name}}}_useServerPlan(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this.host._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let s=0;s<40;s++){const s=this.host._display(t);if(s){const e=await new Promise(t=>{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:0),e.onerror=()=>t(0),e.src=s}),o=this.host._spaceDialog;return o&&o.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...o,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){if(confirm(this.host._t("confirm.delete_plan",{name:t})))try{await this.host.hass.callWS({type:"houseplan/plans/delete",name:t});const s=this.host._spaceDialog;s?.saved&&(this.host._spaceDialog={...s,saved:s.saved.filter(s=>s.name!==t)})}catch(t){this.host._showToast(this.host._t("toast.plan_delete_failed",{err:this.host._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return p`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return p`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return p`<div class="savedplans">
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="f4223f763dcd6ca7756b77bef6936c1cfd4eff678814c4d37f22bc3a15f49852";import{bU as t,bV as s,bW as e,bX as o,aq as a,bY as l,c1 as i,bZ as h,b_ as n,b$ as c,c0 as r,b as p,A as _,cV as d,ap as u,ar as g,cW as m}from"./houseplan-card-CRVz7yAZ.js";import{i as b,c as $,a as v,s as f,t as y}from"./space-deletion-Efs-z9tD.js";const w=1e3,D=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},S="f4223f763dcd6ca7756b77bef6936c1cfd4eff678814c4d37f22bc3a15f49852";class C{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_openSpaceDialog(p,_){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===p){const i=this.host._serverCfg.spaces.find(t=>t.id===_);if(!i)return;const h=t(i),n=i.settings?.custom_fill&&"object"==typeof i.settings.custom_fill?s(i.settings.custom_fill):null,c="none"===h.fill?{...n||e,a:0}:n;return void(this.host._spaceDialog={mode:p,spaceId:_,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",showBorders:h.showBorders,showNames:h.showNames,zeroWallStyle:l(i),displayTouched:!0,hideDecor:h.hideDecor,hideOpenings:h.hideOpenings,roomColor:h.color,roomOpacity:h.opacity,fillMode:"none"===h.fill?"custom":h.fill,customFill:c,glowEnabled:h.glow,bgColor:h.bgColor,bgMode:"static"===i.settings?.bg_mode||"daynight"===i.settings?.bg_mode?i.settings.bg_mode:null,northDeg:a({},i.settings),sunRays:"boolean"==typeof i.settings?.sun_rays?i.settings.sun_rays:null,tempMin:h.tempMin,tempMax:h.tempMax,showLqi:h.showLqi??this.host._config?.show_signal??!0,cardFontScale:h.cardFontScale,labelTemp:h.labelTemp,labelHum:h.labelHum,labelLqi:h.labelLqi,labelLight:h.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,cellCmInput:o(Number(i.cell_cm)>0?Number(i.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const d=i(this.host._imperial);this.host._spaceDialog={mode:p,title:"",planUrl:null,planFile:null,...b(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:r,roomOpacity:c,fillMode:"custom",customFill:{...e,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:n,tempMax:h,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:d,cellCmInput:o(d,this.host._imperial),cellCmTouched:!1,busy:!1}}async _pickPlanFile(t){const s=t.target,e=s.files?.[0];if(!e||!this.host._spaceDialog)return;const o={"image/svg+xml":"svg","image/png":"png","image/jpeg":"jpg","image/webp":"webp"}[e.type]||(e.name.toLowerCase().endsWith(".svg")?"svg":"");if(!o)return void this.host._showToast(this.host._t("toast.plan_formats"));const a=new Uint8Array(await e.arrayBuffer());let l="";for(let t=0;t<a.length;t+=32768)l+=String.fromCharCode(...a.subarray(t,t+32768));const i=btoa(l),h=URL.createObjectURL(e),n=await new Promise(t=>{const s=new Image;s.onload=()=>t(s.naturalWidth&&s.naturalHeight?s.naturalWidth/s.naturalHeight:1.414),s.onerror=()=>t(1.414),s.src=h});URL.revokeObjectURL(h),this.host._spaceDialog={...this.host._spaceDialog,planFile:{ext:o,b64:i,aspect:n,name:e.name}}}_useServerPlan(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this.host._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let s=0;s<40;s++){const s=this.host._display(t);if(s){const e=await new Promise(t=>{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:0),e.onerror=()=>t(0),e.src=s}),o=this.host._spaceDialog;return o&&o.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...o,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){if(confirm(this.host._t("confirm.delete_plan",{name:t})))try{await this.host.hass.callWS({type:"houseplan/plans/delete",name:t});const s=this.host._spaceDialog;s?.saved&&(this.host._spaceDialog={...s,saved:s.saved.filter(s=>s.name!==t)})}catch(t){this.host._showToast(this.host._t("toast.plan_delete_failed",{err:this.host._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return p`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return p`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return p`<div class="savedplans">
|
||||
${s.map(s=>{return p`
|
||||
<div class="savedplan ${s.url===t.planUrl?"cur":""}">
|
||||
<img src=${this.host._display(s.url)} alt="" loading="lazy" decoding="async" />
|
||||
+1
-1
@@ -1 +1 @@
|
||||
function e(e,o){return{id:e,title:o,plan_url:null,view_box:[0,0,1,1],rooms:[],wall_segments:[]}}function o(e="file"){const o="draw"===e;return{source:e,showBorders:o,showNames:o,displayTouched:!1}}function r(e,o){if(e.displayTouched)return{...e,source:o};const r="draw"===o;return{...e,source:o,showBorders:r,showNames:r}}function s(e,o,r){return{...e,[o]:r,displayTouched:!0}}function a(e,o,r){const s=(e?.spaces||[]).find(e=>e?.id===r),a=new Set((s?.rooms||[]).map(e=>String(e?.id||"")).filter(Boolean)),t=[...new Set((e?.markers||[]).filter(e=>!0!==e?.removed&&"string"==typeof e?.id).filter(e=>e.space===r||"string"==typeof e.room_id&&a.has(e.room_id)||o?.[e.id]?.s===r).map(e=>e.id))].sort((e,o)=>e.localeCompare(o));return{markerIds:t,count:t.length}}globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="99af70faf186a79ae24e2901edbf39e13d6388796d8958ab05fc9b1fca671aae";export{a,e as c,o as i,r as s,s as t};
|
||||
function e(e,o){return{id:e,title:o,plan_url:null,view_box:[0,0,1,1],rooms:[],wall_segments:[]}}function o(e="file"){const o="draw"===e;return{source:e,showBorders:o,showNames:o,displayTouched:!1}}function r(e,o){if(e.displayTouched)return{...e,source:o};const r="draw"===o;return{...e,source:o,showBorders:r,showNames:r}}function s(e,o,r){return{...e,[o]:r,displayTouched:!0}}function t(e,o,r){const s=(e?.spaces||[]).find(e=>e?.id===r),t=new Set((s?.rooms||[]).map(e=>String(e?.id||"")).filter(Boolean)),n=[...new Set((e?.markers||[]).filter(e=>!0!==e?.removed&&"string"==typeof e?.id).filter(e=>e.space===r||"string"==typeof e.room_id&&t.has(e.room_id)||o?.[e.id]?.s===r).map(e=>e.id))].sort((e,o)=>e.localeCompare(o));return{markerIds:n,count:n.length}}globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="f4223f763dcd6ca7756b77bef6936c1cfd4eff678814c4d37f22bc3a15f49852";export{t as a,e as c,o as i,r as s,s as t};
|
||||
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="99af70faf186a79ae24e2901edbf39e13d6388796d8958ab05fc9b1fca671aae";try{await import("./houseplan-assets/houseplan-card-g88CD9VI.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).":"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__="f4223f763dcd6ca7756b77bef6936c1cfd4eff678814c4d37f22bc3a15f49852";try{await import("./houseplan-assets/houseplan-card-CRVz7yAZ.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).":"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)}
|
||||
|
||||
@@ -78,6 +78,11 @@ out.failureFallsBackAndUnblocks = await failed.page.evaluate(() =>
|
||||
!window.__card.inert && window.__card._t('btn.cancel') === 'Cancel'
|
||||
&& window.__card.getAttribute('lang') === 'en');
|
||||
out.failureWarnsOnce = warnings === 1;
|
||||
// #354: the failed dictionary must be VISIBLE on the View card — a toast in
|
||||
// the (English-fallback) locale, not only a console line.
|
||||
out.failureShowsToast = await failed.page.evaluate(() =>
|
||||
window.__card._toast === window.__card._t('toast.locale_load_failed')
|
||||
&& window.__card._t('toast.locale_load_failed').includes('language pack'));
|
||||
|
||||
await failed.browser.close();
|
||||
checkAll(out);
|
||||
|
||||
Vendored
+51
-51
@@ -1,111 +1,111 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "99af70faf186a79ae24e2901edbf39e13d6388796d8958ab05fc9b1fca671aae",
|
||||
"fingerprint": "f4223f763dcd6ca7756b77bef6936c1cfd4eff678814c4d37f22bc3a15f49852",
|
||||
"entry": "houseplan-card.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-g88CD9VI.js",
|
||||
"houseplan-assets/houseplan-card-CRVz7yAZ.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 256828,
|
||||
"initialViewGzipBytes": 257222,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/de-CFxUrXZ2.js",
|
||||
"houseplan-assets/editor-Do71pUmD.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BuT5Y1tf.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D8yGRdVi.js",
|
||||
"houseplan-assets/space-deletion-3-uORj4C.js"
|
||||
"houseplan-assets/de-CaUzN0Gb.js",
|
||||
"houseplan-assets/editor-Cx6zLzlf.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-g5SOkk1f.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-gghL8Cm4.js",
|
||||
"houseplan-assets/space-deletion-Efs-z9tD.js"
|
||||
],
|
||||
"lazyGzipBytes": 160366,
|
||||
"lazyGzipBytes": 160428,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/editor-Do71pUmD.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BuT5Y1tf.js",
|
||||
"houseplan-assets/space-deletion-3-uORj4C.js"
|
||||
"houseplan-assets/editor-Cx6zLzlf.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-g5SOkk1f.js",
|
||||
"houseplan-assets/space-deletion-Efs-z9tD.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 131834,
|
||||
"lazyEditorGzipBytes": 131845,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D8yGRdVi.js",
|
||||
"houseplan-assets/space-deletion-3-uORj4C.js"
|
||||
"houseplan-assets/houseplan-onboarding-runtime-gghL8Cm4.js",
|
||||
"houseplan-assets/space-deletion-Efs-z9tD.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 7196,
|
||||
"lazyOnboardingGzipBytes": 7203,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-CFxUrXZ2.js"
|
||||
"houseplan-assets/de-CaUzN0Gb.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 21886,
|
||||
"lazyLocaleGzipBytes": 21934,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/de-CFxUrXZ2.js",
|
||||
"sha256": "62ceb0c8d016c48de243b8d643b4f02bae8a623dc8604d115af1420ecb6f7582",
|
||||
"rawBytes": 71541,
|
||||
"gzipBytes": 21886,
|
||||
"path": "houseplan-assets/de-CaUzN0Gb.js",
|
||||
"sha256": "28905f1ea79271d20a7837a9c03b5492dd4e581570dda1c1744b6c0649b38fbd",
|
||||
"rawBytes": 71667,
|
||||
"gzipBytes": 21934,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-Do71pUmD.js",
|
||||
"sha256": "71dea046e80ff8ce30728044ad3cf85ee536798e230306f5ec1e8824a6e0e1d2",
|
||||
"path": "houseplan-assets/editor-Cx6zLzlf.js",
|
||||
"sha256": "e1234c8e7ff9c1cc932d45a633fef965a23b6c8db9dc5b684092df6b25a5bffa",
|
||||
"rawBytes": 3831,
|
||||
"gzipBytes": 1583,
|
||||
"gzipBytes": 1586,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-g88CD9VI.js"
|
||||
"houseplan-assets/houseplan-card-CRVz7yAZ.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-g88CD9VI.js",
|
||||
"sha256": "2040dd1630535449eb8ef3b1a1cfb71da2e14c59b7134b7d64b280dc91b9770f",
|
||||
"rawBytes": 911212,
|
||||
"gzipBytes": 256075,
|
||||
"path": "houseplan-assets/houseplan-card-CRVz7yAZ.js",
|
||||
"sha256": "c8ed01fa23f357e2a2691a4b64ba459f67b2ff9567314be2159a30429fe951dd",
|
||||
"rawBytes": 912395,
|
||||
"gzipBytes": 256466,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-CFxUrXZ2.js",
|
||||
"houseplan-assets/editor-Do71pUmD.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-BuT5Y1tf.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-D8yGRdVi.js"
|
||||
"houseplan-assets/de-CaUzN0Gb.js",
|
||||
"houseplan-assets/editor-Cx6zLzlf.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-g5SOkk1f.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-gghL8Cm4.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-BuT5Y1tf.js",
|
||||
"sha256": "1d6bd9cf7036174a2d3daba802c1a98c1dc69e118752779be1ad5aa6bd956acd",
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-g5SOkk1f.js",
|
||||
"sha256": "9e82dc7246a631a56e31e842603e099fdb5f8b89d2c4b282af4f3306330e3577",
|
||||
"rawBytes": 504959,
|
||||
"gzipBytes": 129701,
|
||||
"gzipBytes": 129705,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-g88CD9VI.js",
|
||||
"houseplan-assets/space-deletion-3-uORj4C.js"
|
||||
"houseplan-assets/houseplan-card-CRVz7yAZ.js",
|
||||
"houseplan-assets/space-deletion-Efs-z9tD.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-D8yGRdVi.js",
|
||||
"sha256": "9777a5eb4166704d294d9972f86d0897ec229e40116eb9def15147cd5bf45007",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-gghL8Cm4.js",
|
||||
"sha256": "8b429f66b9bdc598278504cebde6800730c9f8d19e42c7b184d6cd7445a92b04",
|
||||
"rawBytes": 26356,
|
||||
"gzipBytes": 6646,
|
||||
"gzipBytes": 6649,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-g88CD9VI.js",
|
||||
"houseplan-assets/space-deletion-3-uORj4C.js"
|
||||
"houseplan-assets/houseplan-card-CRVz7yAZ.js",
|
||||
"houseplan-assets/space-deletion-Efs-z9tD.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/space-deletion-3-uORj4C.js",
|
||||
"sha256": "bccd5edc4a4113d78ea05a475b8cadc1175287df2e67327213a95e9647074e25",
|
||||
"rawBytes": 906,
|
||||
"gzipBytes": 550,
|
||||
"path": "houseplan-assets/space-deletion-Efs-z9tD.js",
|
||||
"sha256": "534af6bd61ad2404183665d7401e625383a59321feeb55db99c514d9f23415aa",
|
||||
"rawBytes": 911,
|
||||
"gzipBytes": 554,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "228910d9e3580441d5a1064cb5107e2f2dd80d99b24a325b2b54843eb32f0009",
|
||||
"sha256": "a950c24f9f8f6cc9c50a67a5b41ad644acda7dc94501f4cbe2e771b0fa271090",
|
||||
"rawBytes": 1089,
|
||||
"gzipBytes": 753,
|
||||
"gzipBytes": 756,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-g88CD9VI.js"
|
||||
"houseplan-assets/houseplan-card-CRVz7yAZ.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="99af70faf186a79ae24e2901edbf39e13d6388796d8958ab05fc9b1fca671aae";import{c as e,l as o,t,d2 as s,A as a,d3 as i,d4 as l,d5 as n,E as r,b as c}from"./houseplan-card-g88CD9VI.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
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="f4223f763dcd6ca7756b77bef6936c1cfd4eff678814c4d37f22bc3a15f49852";import{c as e,l as o,t,d2 as s,A as a,d3 as i,d4 as l,d5 as n,E as r,b as c}from"./houseplan-card-CRVz7yAZ.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
|
||||
.hass=${this.hass}
|
||||
.data=${this._formData}
|
||||
.schema=${e}
|
||||
+26
-26
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="99af70faf186a79ae24e2901edbf39e13d6388796d8958ab05fc9b1fca671aae";import{bU as t,bV as s,bW as e,bX as o,aq as a,bY as l,c1 as i,bZ as h,b_ as n,b$ as c,c0 as r,b as p,A as _,cV as d,ap as u,ar as g,cW as m}from"./houseplan-card-g88CD9VI.js";import{i as b,c as $,a as v,s as f,t as y}from"./space-deletion-3-uORj4C.js";const w=1e3,D=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},S="99af70faf186a79ae24e2901edbf39e13d6388796d8958ab05fc9b1fca671aae";class C{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_openSpaceDialog(p,_){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===p){const i=this.host._serverCfg.spaces.find(t=>t.id===_);if(!i)return;const h=t(i),n=i.settings?.custom_fill&&"object"==typeof i.settings.custom_fill?s(i.settings.custom_fill):null,c="none"===h.fill?{...n||e,a:0}:n;return void(this.host._spaceDialog={mode:p,spaceId:_,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",showBorders:h.showBorders,showNames:h.showNames,zeroWallStyle:l(i),displayTouched:!0,hideDecor:h.hideDecor,hideOpenings:h.hideOpenings,roomColor:h.color,roomOpacity:h.opacity,fillMode:"none"===h.fill?"custom":h.fill,customFill:c,glowEnabled:h.glow,bgColor:h.bgColor,bgMode:"static"===i.settings?.bg_mode||"daynight"===i.settings?.bg_mode?i.settings.bg_mode:null,northDeg:a({},i.settings),sunRays:"boolean"==typeof i.settings?.sun_rays?i.settings.sun_rays:null,tempMin:h.tempMin,tempMax:h.tempMax,showLqi:h.showLqi??this.host._config?.show_signal??!0,cardFontScale:h.cardFontScale,labelTemp:h.labelTemp,labelHum:h.labelHum,labelLqi:h.labelLqi,labelLight:h.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,cellCmInput:o(Number(i.cell_cm)>0?Number(i.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const d=i(this.host._imperial);this.host._spaceDialog={mode:p,title:"",planUrl:null,planFile:null,...b(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:r,roomOpacity:c,fillMode:"custom",customFill:{...e,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:n,tempMax:h,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:d,cellCmInput:o(d,this.host._imperial),cellCmTouched:!1,busy:!1}}async _pickPlanFile(t){const s=t.target,e=s.files?.[0];if(!e||!this.host._spaceDialog)return;const o={"image/svg+xml":"svg","image/png":"png","image/jpeg":"jpg","image/webp":"webp"}[e.type]||(e.name.toLowerCase().endsWith(".svg")?"svg":"");if(!o)return void this.host._showToast(this.host._t("toast.plan_formats"));const a=new Uint8Array(await e.arrayBuffer());let l="";for(let t=0;t<a.length;t+=32768)l+=String.fromCharCode(...a.subarray(t,t+32768));const i=btoa(l),h=URL.createObjectURL(e),n=await new Promise(t=>{const s=new Image;s.onload=()=>t(s.naturalWidth&&s.naturalHeight?s.naturalWidth/s.naturalHeight:1.414),s.onerror=()=>t(1.414),s.src=h});URL.revokeObjectURL(h),this.host._spaceDialog={...this.host._spaceDialog,planFile:{ext:o,b64:i,aspect:n,name:e.name}}}_useServerPlan(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this.host._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let s=0;s<40;s++){const s=this.host._display(t);if(s){const e=await new Promise(t=>{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:0),e.onerror=()=>t(0),e.src=s}),o=this.host._spaceDialog;return o&&o.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...o,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){if(confirm(this.host._t("confirm.delete_plan",{name:t})))try{await this.host.hass.callWS({type:"houseplan/plans/delete",name:t});const s=this.host._spaceDialog;s?.saved&&(this.host._spaceDialog={...s,saved:s.saved.filter(s=>s.name!==t)})}catch(t){this.host._showToast(this.host._t("toast.plan_delete_failed",{err:this.host._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return p`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return p`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return p`<div class="savedplans">
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="f4223f763dcd6ca7756b77bef6936c1cfd4eff678814c4d37f22bc3a15f49852";import{bU as t,bV as s,bW as e,bX as o,aq as a,bY as l,c1 as i,bZ as h,b_ as n,b$ as c,c0 as r,b as p,A as _,cV as d,ap as u,ar as g,cW as m}from"./houseplan-card-CRVz7yAZ.js";import{i as b,c as $,a as v,s as f,t as y}from"./space-deletion-Efs-z9tD.js";const w=1e3,D=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},S="f4223f763dcd6ca7756b77bef6936c1cfd4eff678814c4d37f22bc3a15f49852";class C{constructor(t){this.host=t,this._toggleServerPlans=async()=>{const t=this.host._spaceDialog;if(t)if(t.pickSaved)this.host._spaceDialog={...t,pickSaved:!1};else{this.host._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.host.hass.callWS({type:"houseplan/plans/list"}),s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:t?.plans||[],savedBusy:!1})}catch(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,saved:[],savedBusy:!1}),this.host._showToast(this.host._t("toast.plans_list_failed",{err:this.host._errText(t)}))}}}}_openSpaceDialog(p,_){if(!this.host._serverStorage||!this.host._serverCfg)return void this.host._showToast(this.host._t("toast.integration_missing"));if("edit"===p){const i=this.host._serverCfg.spaces.find(t=>t.id===_);if(!i)return;const h=t(i),n=i.settings?.custom_fill&&"object"==typeof i.settings.custom_fill?s(i.settings.custom_fill):null,c="none"===h.fill?{...n||e,a:0}:n;return void(this.host._spaceDialog={mode:p,spaceId:_,title:i.title,planUrl:i.plan_url||null,planFile:null,source:i.plan_url?"file":"draw",showBorders:h.showBorders,showNames:h.showNames,zeroWallStyle:l(i),displayTouched:!0,hideDecor:h.hideDecor,hideOpenings:h.hideOpenings,roomColor:h.color,roomOpacity:h.opacity,fillMode:"none"===h.fill?"custom":h.fill,customFill:c,glowEnabled:h.glow,bgColor:h.bgColor,bgMode:"static"===i.settings?.bg_mode||"daynight"===i.settings?.bg_mode?i.settings.bg_mode:null,northDeg:a({},i.settings),sunRays:"boolean"==typeof i.settings?.sun_rays?i.settings.sun_rays:null,tempMin:h.tempMin,tempMax:h.tempMax,showLqi:h.showLqi??this.host._config?.show_signal??!0,cardFontScale:h.cardFontScale,labelTemp:h.labelTemp,labelHum:h.labelHum,labelLqi:h.labelLqi,labelLight:h.labelLight,cellCm:Number(i.cell_cm)>0?Number(i.cell_cm):5,cellCmInput:o(Number(i.cell_cm)>0?Number(i.cell_cm):5,this.host._imperial),cellCmTouched:!1,busy:!1})}const d=i(this.host._imperial);this.host._spaceDialog={mode:p,title:"",planUrl:null,planFile:null,...b(),hideDecor:!1,hideOpenings:!1,zeroWallStyle:"dashed",roomColor:r,roomOpacity:c,fillMode:"custom",customFill:{...e,a:0},glowEnabled:!0,bgColor:null,bgMode:"daynight",northDeg:null,sunRays:null,tempMin:n,tempMax:h,showLqi:this.host._config?.show_signal??!0,cardFontScale:1,labelTemp:!1,labelHum:!1,labelLqi:!1,labelLight:!1,cellCm:d,cellCmInput:o(d,this.host._imperial),cellCmTouched:!1,busy:!1}}async _pickPlanFile(t){const s=t.target,e=s.files?.[0];if(!e||!this.host._spaceDialog)return;const o={"image/svg+xml":"svg","image/png":"png","image/jpeg":"jpg","image/webp":"webp"}[e.type]||(e.name.toLowerCase().endsWith(".svg")?"svg":"");if(!o)return void this.host._showToast(this.host._t("toast.plan_formats"));const a=new Uint8Array(await e.arrayBuffer());let l="";for(let t=0;t<a.length;t+=32768)l+=String.fromCharCode(...a.subarray(t,t+32768));const i=btoa(l),h=URL.createObjectURL(e),n=await new Promise(t=>{const s=new Image;s.onload=()=>t(s.naturalWidth&&s.naturalHeight?s.naturalWidth/s.naturalHeight:1.414),s.onerror=()=>t(1.414),s.src=h});URL.revokeObjectURL(h),this.host._spaceDialog={...this.host._spaceDialog,planFile:{ext:o,b64:i,aspect:n,name:e.name}}}_useServerPlan(t){const s=this.host._spaceDialog;s&&(this.host._spaceDialog={...s,planUrl:t,planFile:null,pickSaved:!1,savedAspect:void 0},this.host._aspectJob=this._readPlanAspect(t))}async _readPlanAspect(t){for(let s=0;s<40;s++){const s=this.host._display(t);if(s){const e=await new Promise(t=>{const e=new Image;e.onload=()=>t(e.naturalWidth&&e.naturalHeight?e.naturalWidth/e.naturalHeight:0),e.onerror=()=>t(0),e.src=s}),o=this.host._spaceDialog;return o&&o.planUrl===t&&Number.isFinite(e)&&e>0?(this.host._spaceDialog={...o,savedAspect:e},e):0}if(await new Promise(t=>setTimeout(t,150)),this.host._spaceDialog?.planUrl!==t)return 0}return 0}async _deleteServerPlan(t){if(confirm(this.host._t("confirm.delete_plan",{name:t})))try{await this.host.hass.callWS({type:"houseplan/plans/delete",name:t});const s=this.host._spaceDialog;s?.saved&&(this.host._spaceDialog={...s,saved:s.saved.filter(s=>s.name!==t)})}catch(t){this.host._showToast(this.host._t("toast.plan_delete_failed",{err:this.host._errText(t)}))}}_renderServerPlans(t){if(t.savedBusy)return p`<div class="savedplans muted">${this.host._t("space.loading")}</div>`;const s=t.saved||[];if(!s.length)return p`<div class="savedplans muted">${this.host._t("space.no_saved")}</div>`;return p`<div class="savedplans">
|
||||
${s.map(s=>{return p`
|
||||
<div class="savedplan ${s.url===t.planUrl?"cur":""}">
|
||||
<img src=${this.host._display(s.url)} alt="" loading="lazy" decoding="async" />
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
function e(e,o){return{id:e,title:o,plan_url:null,view_box:[0,0,1,1],rooms:[],wall_segments:[]}}function o(e="file"){const o="draw"===e;return{source:e,showBorders:o,showNames:o,displayTouched:!1}}function r(e,o){if(e.displayTouched)return{...e,source:o};const r="draw"===o;return{...e,source:o,showBorders:r,showNames:r}}function s(e,o,r){return{...e,[o]:r,displayTouched:!0}}function a(e,o,r){const s=(e?.spaces||[]).find(e=>e?.id===r),a=new Set((s?.rooms||[]).map(e=>String(e?.id||"")).filter(Boolean)),t=[...new Set((e?.markers||[]).filter(e=>!0!==e?.removed&&"string"==typeof e?.id).filter(e=>e.space===r||"string"==typeof e.room_id&&a.has(e.room_id)||o?.[e.id]?.s===r).map(e=>e.id))].sort((e,o)=>e.localeCompare(o));return{markerIds:t,count:t.length}}globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="99af70faf186a79ae24e2901edbf39e13d6388796d8958ab05fc9b1fca671aae";export{a,e as c,o as i,r as s,s as t};
|
||||
function e(e,o){return{id:e,title:o,plan_url:null,view_box:[0,0,1,1],rooms:[],wall_segments:[]}}function o(e="file"){const o="draw"===e;return{source:e,showBorders:o,showNames:o,displayTouched:!1}}function r(e,o){if(e.displayTouched)return{...e,source:o};const r="draw"===o;return{...e,source:o,showBorders:r,showNames:r}}function s(e,o,r){return{...e,[o]:r,displayTouched:!0}}function t(e,o,r){const s=(e?.spaces||[]).find(e=>e?.id===r),t=new Set((s?.rooms||[]).map(e=>String(e?.id||"")).filter(Boolean)),n=[...new Set((e?.markers||[]).filter(e=>!0!==e?.removed&&"string"==typeof e?.id).filter(e=>e.space===r||"string"==typeof e.room_id&&t.has(e.room_id)||o?.[e.id]?.s===r).map(e=>e.id))].sort((e,o)=>e.localeCompare(o));return{markerIds:n,count:n.length}}globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="f4223f763dcd6ca7756b77bef6936c1cfd4eff678814c4d37f22bc3a15f49852";export{t as a,e as c,o as i,r as s,s as t};
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="99af70faf186a79ae24e2901edbf39e13d6388796d8958ab05fc9b1fca671aae";try{await import("./houseplan-assets/houseplan-card-g88CD9VI.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).":"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__="f4223f763dcd6ca7756b77bef6936c1cfd4eff678814c4d37f22bc3a15f49852";try{await import("./houseplan-assets/houseplan-card-CRVz7yAZ.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).":"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)}
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- If the optional language pack (currently German) cannot be downloaded, the
|
||||
card now says so with a toast instead of silently switching to English; the
|
||||
locale runtime the card ships is now the same code the i18n test suite
|
||||
proves ([#354](https://github.com/Matysh/houseplan-card/issues/354)).
|
||||
|
||||
- A stale or third-party client can no longer silently replace saved device
|
||||
positions by sending a whole layout without its revision. Revision-less
|
||||
bootstrap remains available only while the layout store is empty
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
|
||||
## Не выпущено
|
||||
|
||||
- Если необязательный языковой пакет (сейчас — немецкий) не загрузился,
|
||||
карточка говорит об этом тостом, а не молча переключается на английский;
|
||||
рантайм локалей в продакшне теперь тот же код, что доказывает тестовый
|
||||
набор i18n ([#354](https://github.com/Matysh/houseplan-card/issues/354)).
|
||||
|
||||
- Устаревший или сторонний клиент больше не может молча заменить сохранённые
|
||||
позиции устройств, отправив весь layout без его ревизии. Инициализация без
|
||||
ревизии остаётся доступной только пока хранилище layout пусто
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 198 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 319 KiB After Width: | Height: | Size: 319 KiB |
@@ -3,7 +3,7 @@
|
||||
"fixture": "synthetic-only",
|
||||
"chromium": "151.0.7922.34",
|
||||
"oxipng": null,
|
||||
"sourceFingerprint": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
|
||||
"sourceFingerprint": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
|
||||
"captureScriptSha256": "fe76c8e2e27501e9a377d7aa15dd6cba6a018d55c07deb602dbee097f0a0a5ba",
|
||||
"command": "npm run build && node demo/docs/capture.mjs",
|
||||
"scenarios": {
|
||||
@@ -15,8 +15,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
|
||||
"imageSha256": "78d328498bd17a9ee1ec44b389d244b61fca9ac52c9aad6631aa85c53233fec0"
|
||||
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
|
||||
"imageSha256": "0ca982495a7749907c48ebbddfd089c54385a4347f79f64e94af2c5a8d5c03eb"
|
||||
},
|
||||
"view-touch": {
|
||||
"file": "02-view-touch.png",
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
|
||||
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
|
||||
"imageSha256": "3be239a5a494f6776540a9c0049694ee6f9d2ba927c8bcb4b26ee5a69a228a7d"
|
||||
},
|
||||
"space-create": {
|
||||
@@ -37,7 +37,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
|
||||
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
|
||||
"imageSha256": "a55b463055a565892ca139014e314a8f63fb5301c87fff2e48d7e69210fe9b6c"
|
||||
},
|
||||
"room-contour-close": {
|
||||
@@ -48,7 +48,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
|
||||
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
|
||||
"imageSha256": "dfad9a27371cfa397a3fd8dce7005f42d24649743f5322105ee894067d3a9f16"
|
||||
},
|
||||
"plan-context-tray": {
|
||||
@@ -59,7 +59,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
|
||||
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
|
||||
"imageSha256": "b75f348c632296da5c2fc142d53df613f757e1516acac315be50f5b4557729cc"
|
||||
},
|
||||
"device-editor": {
|
||||
@@ -70,7 +70,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
|
||||
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
|
||||
"imageSha256": "8affadfce926fe2f6cf344c33f611c44fcbc79c1969f69c3a6935ff2e99c36c3"
|
||||
},
|
||||
"device-display-preview": {
|
||||
@@ -81,8 +81,8 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
|
||||
"imageSha256": "6f628abe6b89d7c4b352e6d73ac0b126526f6fdba5927bb774c5b884e484ca19"
|
||||
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
|
||||
"imageSha256": "6d323b6f641b2cc7db64252f70567348bd3f2551f82e61fd3a2b4674d244918e"
|
||||
},
|
||||
"background-editor": {
|
||||
"file": "07-background-editor.png",
|
||||
@@ -92,7 +92,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
|
||||
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
|
||||
"imageSha256": "a1d1a905f1410f73231d2ea5d1dfbd8ae79c2fe8b0a79cff87ebb977ac7a3103"
|
||||
},
|
||||
"room-card": {
|
||||
@@ -103,7 +103,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
|
||||
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
|
||||
"imageSha256": "48a5685039a5e0de2a28190d857b193c86e2e63606e3c7a8c40fc9d2b190da4e"
|
||||
},
|
||||
"device-info": {
|
||||
@@ -114,7 +114,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "20360427643d04e7068255e2a64cbdb296bfc772d8c11cfeb0baf753520addb5",
|
||||
"sourceSha256": "f8b80b1f920fa0ecaa24abd1ae6c143df1921d892546a22a44831b2b688f7fd0",
|
||||
"imageSha256": "29a342811aaedaebc54ba337792d60cb454a7cf4c3063b210f6c4bafe0a4d87b"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,6 +634,45 @@ const MUTANT_DEFINITIONS = [
|
||||
replace: ' // mutant: pointer capture removed\n const plan = resolution.plan;',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'language-runtime-handwritten-duplicate',
|
||||
guard: 'node --test --test-name-pattern="#354" test/i18n-runtime.test.mjs',
|
||||
because: 'a handwritten twin of LanguageRuntime looks equivalent today and rots tomorrow — '
|
||||
+ 'the i18n suite must prove the object production actually runs (#354 К1/К3)',
|
||||
patches: [{
|
||||
file: 'src/i18n/registry.ts',
|
||||
find: 'export const LANGUAGE_RUNTIME: LanguageRuntimeContract = new LanguageRuntime(\n'
|
||||
+ ' LANGUAGE_REGISTRY,\n'
|
||||
+ ' BUILD_FINGERPRINT,\n'
|
||||
+ ' console.warn,\n'
|
||||
+ ' (code) => {\n'
|
||||
+ ' for (const listener of languageLoadFailureListeners) listener(code);\n'
|
||||
+ ' },\n'
|
||||
+ ');',
|
||||
replace: 'const twin = new LanguageRuntime(LANGUAGE_REGISTRY, BUILD_FINGERPRINT, '
|
||||
+ 'console.warn, (code) => {\n'
|
||||
+ ' for (const listener of languageLoadFailureListeners) listener(code);\n'
|
||||
+ '});\n'
|
||||
+ 'export const LANGUAGE_RUNTIME: LanguageRuntimeContract = {\n'
|
||||
+ ' state: (code) => twin.state(code),\n'
|
||||
+ ' dictionary: (code) => twin.dictionary(code),\n'
|
||||
+ ' ensure: (code) => twin.ensure(code),\n'
|
||||
+ '};',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'locale-failure-toast-dropped',
|
||||
guard: 'node demo/smoke_german_locale.mjs',
|
||||
because: 'a failed dictionary must be visible on the View card, not only a console line — '
|
||||
+ 'a German user on a stale tab silently reading English is the N7 hole (#354 К2)',
|
||||
patches: [{
|
||||
file: 'src/houseplan-card.ts',
|
||||
find: " this._languageFailureUnsub = subscribeLanguageLoadFailures(() => {\n"
|
||||
+ " this._showToast(this._t('toast.locale_load_failed'));\n"
|
||||
+ ' });',
|
||||
replace: ' this._languageFailureUnsub = undefined;',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'lazy-loader-network-failure-terminal',
|
||||
guard: 'node --test --test-name-pattern="#353 AC1" test/editor-runtime-loader.test.mjs',
|
||||
|
||||
+11
-1
@@ -232,7 +232,7 @@ import {
|
||||
} from './coordinate-canonicalization';
|
||||
import { enqueueSerializedWrite } from './serialized-write-queue';
|
||||
import { hasTranslation, langOf, t, type I18nKey } from './i18n';
|
||||
import { LANGUAGE_RUNTIME } from './i18n/registry';
|
||||
import { LANGUAGE_RUNTIME, subscribeLanguageLoadFailures } from './i18n/registry';
|
||||
import { languageLoadingTemplate, languageRenderGate } from './i18n/language-runtime';
|
||||
import { CommandStack } from './command-stack';
|
||||
import { resolvedSvgScreenBlend, svgScreenBlendSupported } from './glow-blend';
|
||||
@@ -2189,6 +2189,7 @@ export class HouseplanCard extends LitElement {
|
||||
private _continuity = this._newContinuityController();
|
||||
private _continuityHistory: import('./visual-continuity').ContinuityTraceEvent[] = [];
|
||||
private _continuityUnsub?: () => void;
|
||||
private _languageFailureUnsub?: () => void;
|
||||
private _continuityEpoch = 0;
|
||||
private _continuityDataReady = true;
|
||||
private _continuityPaintToken = -1;
|
||||
@@ -2474,6 +2475,13 @@ export class HouseplanCard extends LitElement {
|
||||
if (resolvedBlend !== undefined) this._glowScreenBlend = resolvedBlend;
|
||||
this._continuityUnsub?.();
|
||||
this._continuityUnsub = subscribePageVisibility(this.ownerDocument, this._pageVisibility);
|
||||
// #354: only the View card owns toast infrastructure, so it alone turns a
|
||||
// locale-load failure into a visible message; other runtime surfaces keep
|
||||
// the console warning from the shared LanguageRuntime.
|
||||
this._languageFailureUnsub?.();
|
||||
this._languageFailureUnsub = subscribeLanguageLoadFailures(() => {
|
||||
this._showToast(this._t('toast.locale_load_failed'));
|
||||
});
|
||||
super.connectedCallback();
|
||||
this._pointerModality.connect(this.ownerDocument.defaultView);
|
||||
const PointerHoverObserver = this.ownerDocument.defaultView?.MutationObserver;
|
||||
@@ -2549,6 +2557,8 @@ export class HouseplanCard extends LitElement {
|
||||
window.removeEventListener('popstate', this._onLocationChanged);
|
||||
this._continuityUnsub?.();
|
||||
this._continuityUnsub = undefined;
|
||||
this._languageFailureUnsub?.();
|
||||
this._languageFailureUnsub = undefined;
|
||||
this._motionMedia?.removeEventListener?.('change', this._onMotionChange);
|
||||
this._motionMedia = undefined;
|
||||
if (this._vacRaf) { cancelAnimationFrame(this._vacRaf); this._vacRaf = 0; }
|
||||
|
||||
@@ -622,6 +622,7 @@
|
||||
"room.label_scale": "Metrikgröße",
|
||||
"preview.room_name": "Wohnzimmer",
|
||||
"toast.cfg_reload_failed": "Der Plan konnte nicht vom Server neu geladen werden: {err}",
|
||||
"toast.locale_load_failed": "Das Sprachpaket konnte nicht geladen werden. Bis zum Neuladen der Seite wird Englisch verwendet.",
|
||||
"room.settings_short": "Raumeinstellungen",
|
||||
"room.unnamed": "Unbenannter Raum",
|
||||
"marker.use_climate_temp": "Geräte-Temperatur im Raum berücksichtigen",
|
||||
|
||||
@@ -622,6 +622,7 @@
|
||||
"room.label_scale": "Metrics size",
|
||||
"preview.room_name": "Living room",
|
||||
"toast.cfg_reload_failed": "Could not reload the plan from the server: {err}",
|
||||
"toast.locale_load_failed": "Could not load the language pack. English is used until the page reloads.",
|
||||
"room.settings_short": "Room settings",
|
||||
"room.unnamed": "Unnamed room",
|
||||
"marker.use_climate_temp": "Include the device temperature in the room",
|
||||
|
||||
@@ -37,6 +37,8 @@ export class LanguageRuntime {
|
||||
private readonly entries: readonly RuntimeLanguageEntry[],
|
||||
private readonly expectedFingerprint: string,
|
||||
private readonly warn: (message: string, error: unknown) => void = console.warn,
|
||||
/** Optional #354 hook: a dictionary load settled into English fallback. */
|
||||
private readonly loadFailed?: (code: string) => void,
|
||||
) {
|
||||
for (const entry of entries) {
|
||||
if (entry.dictionary) this.dictionaries.set(entry.code, entry.dictionary);
|
||||
@@ -84,6 +86,7 @@ export class LanguageRuntime {
|
||||
}
|
||||
this.failed.add(entry.code);
|
||||
this.warn(`[houseplan] unable to load ${entry.code} locale; using English`, lastError);
|
||||
this.loadFailed?.(entry.code);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+30
-31
@@ -1,9 +1,10 @@
|
||||
import en from './en.json' with { type: 'json' };
|
||||
import ru from './ru.json' with { type: 'json' };
|
||||
import type {
|
||||
LanguageRuntimeContract,
|
||||
LazyLanguageModule,
|
||||
LocaleDictionary,
|
||||
import {
|
||||
LanguageRuntime,
|
||||
type LanguageRuntimeContract,
|
||||
type LazyLanguageModule,
|
||||
type LocaleDictionary,
|
||||
} from './language-runtime';
|
||||
|
||||
const BUILD_FINGERPRINT = '__HOUSEPLAN_SOURCE_FINGERPRINT__';
|
||||
@@ -40,37 +41,35 @@ export type Lang = (typeof LANGUAGE_REGISTRY)[number]['code'];
|
||||
export const FALLBACK_LANGUAGE_CODE: Lang = 'en';
|
||||
export const FALLBACK_DICTIONARY = en;
|
||||
|
||||
/** One page-scoped cache: multiple cards share the same locale request. */
|
||||
let germanDictionary: LocaleDictionary | undefined;
|
||||
let germanPending: Promise<void> | undefined;
|
||||
let germanFailed = false;
|
||||
/**
|
||||
* #354: locale-load failures surface once through this page-scoped listener
|
||||
* list. Only the View card subscribes and toasts; every other runtime surface
|
||||
* (space card, both GUI editors) stays with the console warning below.
|
||||
*/
|
||||
const languageLoadFailureListeners = new Set<(code: string) => void>();
|
||||
|
||||
async function settleGerman(): Promise<void> {
|
||||
let lastError: unknown;
|
||||
for (const attempt of [0, 1] as const) {
|
||||
try {
|
||||
const loaded = await loadGerman(attempt);
|
||||
if (loaded.fingerprint !== BUILD_FINGERPRINT) throw new Error('locale fingerprint mismatch');
|
||||
germanDictionary = loaded.dictionary;
|
||||
return;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
}
|
||||
}
|
||||
germanFailed = true;
|
||||
console.warn('[houseplan] unable to load de locale; using English', lastError);
|
||||
export function subscribeLanguageLoadFailures(
|
||||
listener: (code: string) => void,
|
||||
): () => void {
|
||||
languageLoadFailureListeners.add(listener);
|
||||
return () => languageLoadFailureListeners.delete(listener);
|
||||
}
|
||||
|
||||
export const LANGUAGE_RUNTIME: LanguageRuntimeContract = {
|
||||
state: (code) => code !== 'de' || germanDictionary
|
||||
? 'ready' : germanFailed ? 'fallback' : 'pending',
|
||||
dictionary: (code) => code === 'de'
|
||||
? germanDictionary : code === 'ru' ? ru : code === 'en' ? en : undefined,
|
||||
ensure: (code) => {
|
||||
if (code !== 'de' || germanDictionary || germanFailed) return Promise.resolve();
|
||||
return germanPending ??= settleGerman().finally(() => { germanPending = undefined; });
|
||||
/**
|
||||
* The production runtime IS the tested class (#354): the previous handwritten
|
||||
* object duplicated its logic, so the whole i18n-runtime test suite proved
|
||||
* properties of code the card never ran. One page-scoped instance is shared
|
||||
* by every card and editor; the warn hook keeps the console line and fans the
|
||||
* failure out to subscribers.
|
||||
*/
|
||||
export const LANGUAGE_RUNTIME: LanguageRuntimeContract = new LanguageRuntime(
|
||||
LANGUAGE_REGISTRY,
|
||||
BUILD_FINGERPRINT,
|
||||
console.warn,
|
||||
(code) => {
|
||||
for (const listener of languageLoadFailureListeners) listener(code);
|
||||
},
|
||||
};
|
||||
);
|
||||
|
||||
/** Return a loaded dictionary, falling back synchronously to English. */
|
||||
export function dictionaryFor(value: unknown): LocaleDictionary {
|
||||
|
||||
@@ -622,6 +622,7 @@
|
||||
"room.label_scale": "Размер подписей",
|
||||
"preview.room_name": "Гостиная",
|
||||
"toast.cfg_reload_failed": "Не удалось перечитать план с сервера: {err}",
|
||||
"toast.locale_load_failed": "Не удалось загрузить языковой пакет. До перезагрузки страницы используется английский.",
|
||||
"room.settings_short": "Настройки комнаты",
|
||||
"room.unnamed": "Комната без имени",
|
||||
"marker.use_climate_temp": "Учитывать температуру устройства в комнате",
|
||||
|
||||
@@ -129,3 +129,52 @@ test('locale render gate exposes the fallback language to assistive technology',
|
||||
assert.equal(languageRenderGate(host, runtime, 'de'), 'ready');
|
||||
assert.equal(host.attrs.get('lang'), 'en');
|
||||
});
|
||||
|
||||
test('the production runtime is the tested class, not a handwritten twin (#354)', async () => {
|
||||
const registry = await import('../test-build/i18n/registry.js');
|
||||
assert.ok(
|
||||
registry.LANGUAGE_RUNTIME instanceof LanguageRuntime,
|
||||
'LANGUAGE_RUNTIME must be an instance of the class this suite proves',
|
||||
);
|
||||
const { readFileSync } = await import('node:fs');
|
||||
const source = readFileSync(new URL('../src/i18n/registry.ts', import.meta.url), 'utf8');
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/germanPending|germanFailed|settleGerman/,
|
||||
'the handwritten duplicate must not return — tests would look past production again',
|
||||
);
|
||||
});
|
||||
|
||||
test('a settled dictionary failure reaches the load-failed hook once (#354)', async () => {
|
||||
const failures = [];
|
||||
const warnings = [];
|
||||
const runtime = new LanguageRuntime(
|
||||
[
|
||||
{ code: 'en', dictionary: { key: 'value' } },
|
||||
{ code: 'de', loadDictionary: async () => { throw new Error('net'); } },
|
||||
],
|
||||
'fp',
|
||||
(message, error) => warnings.push([message, String(error)]),
|
||||
(code) => failures.push(code),
|
||||
);
|
||||
await runtime.ensure('de');
|
||||
assert.deepEqual(failures, ['de']);
|
||||
assert.equal(warnings.length, 1);
|
||||
assert.equal(runtime.state('de'), 'fallback');
|
||||
await runtime.ensure('de');
|
||||
assert.deepEqual(failures, ['de'], 'a settled fallback never re-fires the hook');
|
||||
});
|
||||
|
||||
test('language load failure subscription delivers codes and unsubscribes (#354)', async () => {
|
||||
const { subscribeLanguageLoadFailures } = await import('../test-build/i18n/registry.js');
|
||||
const seen = [];
|
||||
const unsubscribe = subscribeLanguageLoadFailures((code) => seen.push(code));
|
||||
// The registry instance wires its loadFailed hook into these listeners; the
|
||||
// fan-out itself is observable without a network by a second subscriber
|
||||
// triggering through the same set semantics.
|
||||
const second = subscribeLanguageLoadFailures((code) => seen.push(`2:${code}`));
|
||||
unsubscribe();
|
||||
second();
|
||||
assert.deepEqual(seen, [], 'listeners removed before any failure stay silent');
|
||||
assert.equal(typeof unsubscribe, 'function');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user