mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
@@ -1,111 +1,111 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "817110bad40d29ee0aa8e4ef2dadfda9e310dfa6346ddb5d2725ecafb359f073",
|
||||
"fingerprint": "e6c51f2467ae4e22a792e536ae954291ab5a46915e728b216c8c45bc6816b209",
|
||||
"entry": "houseplan-card.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-BrUCyMoE.js",
|
||||
"houseplan-assets/houseplan-card-DI__VUPO.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 271841,
|
||||
"initialViewGzipBytes": 272469,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/de-CndLXoON.js",
|
||||
"houseplan-assets/editor-H80bjnWS.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-LLPkWHY0.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BkWWzfSR.js",
|
||||
"houseplan-assets/space-deletion-CLA5ezlL.js"
|
||||
"houseplan-assets/de-gTJXGK7s.js",
|
||||
"houseplan-assets/editor-LGCA8WjH.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Cu2NFP8N.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B4PXZ4um.js",
|
||||
"houseplan-assets/space-deletion-Do1Uuvoz.js"
|
||||
],
|
||||
"lazyGzipBytes": 165904,
|
||||
"lazyGzipBytes": 166314,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/editor-H80bjnWS.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-LLPkWHY0.js",
|
||||
"houseplan-assets/space-deletion-CLA5ezlL.js"
|
||||
"houseplan-assets/editor-LGCA8WjH.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Cu2NFP8N.js",
|
||||
"houseplan-assets/space-deletion-Do1Uuvoz.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 136806,
|
||||
"lazyEditorGzipBytes": 137196,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BkWWzfSR.js",
|
||||
"houseplan-assets/space-deletion-CLA5ezlL.js"
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B4PXZ4um.js",
|
||||
"houseplan-assets/space-deletion-Do1Uuvoz.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 7193,
|
||||
"lazyOnboardingGzipBytes": 7204,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-CndLXoON.js"
|
||||
"houseplan-assets/de-gTJXGK7s.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 22454,
|
||||
"lazyLocaleGzipBytes": 22468,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/de-CndLXoON.js",
|
||||
"sha256": "c3fb30be483664a92a555e2a916d2d6201a5d950d77385a34869445625732be1",
|
||||
"path": "houseplan-assets/de-gTJXGK7s.js",
|
||||
"sha256": "2ed2a8aad10d9eaa16fe3fe2e591c2d736d948b2fd35aca01fcffb33264e1284",
|
||||
"rawBytes": 73919,
|
||||
"gzipBytes": 22454,
|
||||
"gzipBytes": 22468,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-H80bjnWS.js",
|
||||
"sha256": "bcd9f271360a94dfbf63d5bcdf3c965aed1e1b17d0c543ac0b554d5527b90250",
|
||||
"path": "houseplan-assets/editor-LGCA8WjH.js",
|
||||
"sha256": "0f4ce2555703ec01ee188cf84badfdee661587e849c1b604ec88354c3f94894f",
|
||||
"rawBytes": 3831,
|
||||
"gzipBytes": 1580,
|
||||
"gzipBytes": 1585,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BrUCyMoE.js"
|
||||
"houseplan-assets/houseplan-card-DI__VUPO.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-BrUCyMoE.js",
|
||||
"sha256": "bdcf3f7e9306428b2583f1e662541bebebcf2f106b48a53df117c6c9c54c5cde",
|
||||
"rawBytes": 958245,
|
||||
"gzipBytes": 271090,
|
||||
"path": "houseplan-assets/houseplan-card-DI__VUPO.js",
|
||||
"sha256": "35a537b289b388f34a5f6efc01a95254207be9880fa9fd931723ccfa258ad602",
|
||||
"rawBytes": 960284,
|
||||
"gzipBytes": 271715,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-CndLXoON.js",
|
||||
"houseplan-assets/editor-H80bjnWS.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-LLPkWHY0.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BkWWzfSR.js"
|
||||
"houseplan-assets/de-gTJXGK7s.js",
|
||||
"houseplan-assets/editor-LGCA8WjH.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Cu2NFP8N.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B4PXZ4um.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-LLPkWHY0.js",
|
||||
"sha256": "e49282b51d104baff861b3c21261e66dcbfd58807e0db444b04f1f171499e24d",
|
||||
"rawBytes": 520292,
|
||||
"gzipBytes": 134677,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-Cu2NFP8N.js",
|
||||
"sha256": "4eb656ee069026534dace297817908e97d0e82e63ba1059d0c3d6c283d913344",
|
||||
"rawBytes": 522188,
|
||||
"gzipBytes": 135057,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BrUCyMoE.js",
|
||||
"houseplan-assets/space-deletion-CLA5ezlL.js"
|
||||
"houseplan-assets/houseplan-card-DI__VUPO.js",
|
||||
"houseplan-assets/space-deletion-Do1Uuvoz.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-BkWWzfSR.js",
|
||||
"sha256": "5446043fbf2a383f84df3751b1fd97ecac72d8bcf3c254da80ad4176538c91da",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-B4PXZ4um.js",
|
||||
"sha256": "73ad6471b2395b982d233754598fcb6fb296c29b01a188cce724c2861e044187",
|
||||
"rawBytes": 26356,
|
||||
"gzipBytes": 6644,
|
||||
"gzipBytes": 6650,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BrUCyMoE.js",
|
||||
"houseplan-assets/space-deletion-CLA5ezlL.js"
|
||||
"houseplan-assets/houseplan-card-DI__VUPO.js",
|
||||
"houseplan-assets/space-deletion-Do1Uuvoz.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/space-deletion-CLA5ezlL.js",
|
||||
"sha256": "ec0519abd52ab649e2ed8f5744cc8284d96d8a83f3c9517df7f5070be63a66e9",
|
||||
"rawBytes": 906,
|
||||
"gzipBytes": 549,
|
||||
"path": "houseplan-assets/space-deletion-Do1Uuvoz.js",
|
||||
"sha256": "258cd7b7338ca746122d6afcfc63f81d34be23a7bd31f1599ed8d45ec0daa5b6",
|
||||
"rawBytes": 911,
|
||||
"gzipBytes": 554,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "9bb7a0cb60837b9a006d1c0d125c820ab083b270a9c6130147cd5c8c8b47cff1",
|
||||
"sha256": "031ef9710a85647066f82b62cd7a294c752493c0391cf59b1688380f7cac2a18",
|
||||
"rawBytes": 1089,
|
||||
"gzipBytes": 751,
|
||||
"gzipBytes": 754,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BrUCyMoE.js"
|
||||
"houseplan-assets/houseplan-card-DI__VUPO.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+5
-5
@@ -1,14 +1,14 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="817110bad40d29ee0aa8e4ef2dadfda9e310dfa6346ddb5d2725ecafb359f073";import{c as e,l as o,t,c_ as s,A as a,c$ as i,d0 as l,d1 as n,E as r,b as c}from"./houseplan-card-BrUCyMoE.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__="e6c51f2467ae4e22a792e536ae954291ab5a46915e728b216c8c45bc6816b209";import{c as e,l as o,t,cZ as s,A as a,c_ as i,c$ as l,d0 as n,E as r,b as c}from"./houseplan-card-DI__VUPO.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")},_=this._schema,f=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),u=e=>c`<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._formData}
|
||||
.schema=${e}
|
||||
.computeLabel=${e=>h[e.name]||e.name}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>`;return c`
|
||||
${d(f.slice(0,3))}
|
||||
${_?c`<div class="default-floor-error" role="alert"
|
||||
${u(_.slice(0,3))}
|
||||
${f?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:_})}
|
||||
${t(s,"editor.default_floor_missing",{id:f})}
|
||||
</div>`:a}
|
||||
${d(f.slice(3))}
|
||||
${u(_.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);
|
||||
+77
-68
File diff suppressed because one or more lines are too long
+93
-93
File diff suppressed because one or more lines are too long
+5
-5
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="817110bad40d29ee0aa8e4ef2dadfda9e310dfa6346ddb5d2725ecafb359f073";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 _,cS as d,ap as u,ar as g,cT as m}from"./houseplan-card-BrUCyMoE.js";import{i as b,c as $,a as v,s as f,t as y}from"./space-deletion-CLA5ezlL.js";const w=1e3,D=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},S="817110bad40d29ee0aa8e4ef2dadfda9e310dfa6346ddb5d2725ecafb359f073";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__="e6c51f2467ae4e22a792e536ae954291ab5a46915e728b216c8c45bc6816b209";import{bT as t,bU as s,bV as e,bW as o,aq as a,bX as l,c0 as i,bY as h,bZ as n,b_ as c,b$ as r,b as p,A as _,cR as d,ap as u,ar as g,cS as m}from"./houseplan-card-DI__VUPO.js";import{i as b,c as $,a as v,s as y,t as f}from"./space-deletion-Do1Uuvoz.js";const w=1e3,D=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},S="e6c51f2467ae4e22a792e536ae954291ab5a46915e728b216c8c45bc6816b209";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" />
|
||||
@@ -46,7 +46,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="817110bad40d29ee0aa8e4ef2dadfda9e310
|
||||
<label>${this.host._t("space.plan_label")}</label>
|
||||
<label class="srcrow">
|
||||
<input type="radio" name="plansrc" .checked=${"file"===t.source}
|
||||
@change=${()=>this.host._spaceDialog=f(t,"file")} />
|
||||
@change=${()=>this.host._spaceDialog=y(t,"file")} />
|
||||
<span>${this.host._t("space.source_file")}</span>
|
||||
</label>
|
||||
${"file"===t.source?p`<div class="planrow">
|
||||
@@ -68,7 +68,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="817110bad40d29ee0aa8e4ef2dadfda9e310
|
||||
${t.pickSaved?this._renderServerPlans(t):_}`:_}
|
||||
<label class="srcrow">
|
||||
<input type="radio" name="plansrc" .checked=${"draw"===t.source}
|
||||
@change=${()=>this.host._spaceDialog=f(t,"draw")} />
|
||||
@change=${()=>this.host._spaceDialog=y(t,"draw")} />
|
||||
<span>${this.host._t("space.source_draw")}</span>
|
||||
</label>
|
||||
|
||||
@@ -85,7 +85,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="817110bad40d29ee0aa8e4ef2dadfda9e310
|
||||
|
||||
<label class="dispsection">${this.host._t("space.display_section")}</label>
|
||||
<label class="srcrow">
|
||||
${this._boolInput(t.showBorders,s=>{this.host._spaceDialog=y(t,"showBorders",s)})}
|
||||
${this._boolInput(t.showBorders,s=>{this.host._spaceDialog=f(t,"showBorders",s)})}
|
||||
<span>${this.host._t("space.show_borders")}</span>
|
||||
</label>
|
||||
<label>${this.host._t("space.zero_wall_style")}</label>
|
||||
@@ -99,7 +99,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="817110bad40d29ee0aa8e4ef2dadfda9e310
|
||||
</select>
|
||||
<div class="rhint">${this.host._t("space.zero_wall_help")}</div>
|
||||
<label class="srcrow">
|
||||
${this._boolInput(t.showNames,s=>{this.host._spaceDialog=y(t,"showNames",s)})}
|
||||
${this._boolInput(t.showNames,s=>{this.host._spaceDialog=f(t,"showNames",s)})}
|
||||
<span>${this.host._t("space.show_names")}</span>
|
||||
</label>
|
||||
<label class="srcrow">
|
||||
+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__="817110bad40d29ee0aa8e4ef2dadfda9e310dfa6346ddb5d2725ecafb359f073";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)),a=[...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:a,count:a.length}}globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="e6c51f2467ae4e22a792e536ae954291ab5a46915e728b216c8c45bc6816b209";export{t as a,e as c,o as i,r as s,s as t};
|
||||
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="817110bad40d29ee0aa8e4ef2dadfda9e310dfa6346ddb5d2725ecafb359f073";try{await import("./houseplan-assets/houseplan-card-BrUCyMoE.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__="e6c51f2467ae4e22a792e536ae954291ab5a46915e728b216c8c45bc6816b209";try{await import("./houseplan-assets/houseplan-card-DI__VUPO.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)}
|
||||
|
||||
@@ -1274,6 +1274,41 @@ export async function prepareGoldenScenario(page, scenario) {
|
||||
if (!palette.querySelector(selector))
|
||||
throw new Error(`golden furniture palette level is empty: ${scenario.id}`);
|
||||
}
|
||||
if (scenario.furniturePlacementPreview) {
|
||||
const { symbol, widthCm, depthCm, pointer, free = false } = scenario.furniturePlacementPreview;
|
||||
if (typeof symbol !== 'string' || !(widthCm > 0) || !(depthCm > 0)
|
||||
|| !Array.isArray(pointer) || pointer.length !== 2 || !pointer.every(Number.isFinite)) {
|
||||
throw new Error(`invalid golden furniturePlacementPreview: ${scenario.id}`);
|
||||
}
|
||||
card._decorSel = null;
|
||||
card._decorTool = 'furniture';
|
||||
card._furnCategory = null;
|
||||
card._furnPalette = { symbol, w: widthCm, h: depthCm };
|
||||
card.requestUpdate();
|
||||
await card.updateComplete;
|
||||
await frame();
|
||||
const svgRoot = card.renderRoot.querySelector('.stage svg');
|
||||
const stageEl = card.renderRoot.querySelector('.stage');
|
||||
const screen = new DOMPoint(pointer[0] * 1000, pointer[1] * card._spaceH)
|
||||
.matrixTransform(svgRoot.getScreenCTM());
|
||||
stageEl.dispatchEvent(new PointerEvent('pointermove', {
|
||||
bubbles: true, composed: true, pointerId: 993, pointerType: 'mouse',
|
||||
clientX: screen.x, clientY: screen.y, shiftKey: free,
|
||||
}));
|
||||
await card.updateComplete;
|
||||
await frame();
|
||||
const preview = card.renderRoot.querySelector('.furniture-placement-preview');
|
||||
const wall = card.renderRoot.querySelector('[data-hp="wall"]');
|
||||
const previewBeforeWall = !!wall
|
||||
&& !!(preview?.compareDocumentPosition(wall) & Node.DOCUMENT_POSITION_FOLLOWING);
|
||||
if (!preview || preview.getAttribute('data-symbol') !== symbol
|
||||
|| preview.getAttribute('aria-hidden') !== 'true'
|
||||
|| getComputedStyle(preview).pointerEvents !== 'none'
|
||||
|| Math.abs(Number(getComputedStyle(preview).opacity) - 0.55) > 1e-6
|
||||
|| !preview.closest('.decorlayer') || !previewBeforeWall) {
|
||||
throw new Error(`golden furniture placement preview contract is incomplete: ${scenario.id}`);
|
||||
}
|
||||
}
|
||||
if (scenario.hoverRoom) {
|
||||
const room = card._spaceModel().rooms.find((item) => item.id === scenario.hoverRoom);
|
||||
if (!room) throw new Error(`golden hover room missing: ${scenario.hoverRoom}`);
|
||||
|
||||
@@ -418,6 +418,9 @@ export const GOLDEN_SCENARIOS = Object.freeze([
|
||||
{ id: 'furniture-variants-light', fixture: 'visual', space: 'golden-geometry', mode: 'decor',
|
||||
furniturePalette: 'variants', furnitureCategory: 'coffee_table', language: 'ru', theme: 'light',
|
||||
viewport: { width: 900, height: 820 }, ...page },
|
||||
{ id: 'furniture-placement-preview-light', fixture: 'visual', space: 'golden-geometry', mode: 'decor',
|
||||
furniturePlacementPreview: { symbol: 'sofa', widthCm: 180, depthCm: 90, pointer: [0.35, 0.90] },
|
||||
language: 'ru', theme: 'light', viewport: { width: 1000, height: 900 }, ...stage },
|
||||
{ id: 'furniture-plan-art-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
|
||||
decorOverride: [
|
||||
...decorLayerFixture.filter((shape) => shape.kind !== 'furniture'),
|
||||
|
||||
+115
-18
@@ -4,12 +4,14 @@
|
||||
// 2) выбранный символ ставится кликом по плану в РЕАЛЬНОМ размере — через
|
||||
// cell_cm пространства, и сразу оказывается выделенным в «выбрать»;
|
||||
// 3) поля ширины/глубины в палитре меняют размер ДО размещения;
|
||||
// 4) магнит к стене: предмет прижимается к ближайшей стене и доворачивается
|
||||
// по ней; Shift не отключает обязательную привязку (docs/CANVAS.md §9.4);
|
||||
// 5) у выделенного предмета та же рамка, что у текстового блока, — угловые
|
||||
// 4) мышь показывает точный полупрозрачный будущий символ без записи в
|
||||
// конфиг; touch/pen не рисуют hover и сохраняют только чистый tap;
|
||||
// 5) магнит к стене: предмет прижимается к ближайшей стене и доворачивается
|
||||
// по ней; Shift временно обходит магнит, сохраняя grid snap;
|
||||
// 6) у выделенного предмета та же рамка, что у текстового блока, — угловые
|
||||
// ручки (НЕзависимые ширина и глубина) и ручка поворота с шагом 5°;
|
||||
// 6) во время растягивания угла показываются живые плашки размеров;
|
||||
// 7) фигура пишется в конфиг и переживает пересборку, хуки card-mod на месте.
|
||||
// 7) во время растягивания угла показываются живые плашки размеров;
|
||||
// 8) фигура пишется в конфиг и переживает пересборку, хуки card-mod на месте.
|
||||
// ПАДАЕТ на сборке до этой задачи: инструмента «Мебель» нет, палитры нет,
|
||||
// kind:'furniture' не рисуется, магнита и плашек не существует. Смок при этом
|
||||
// null-safe — он показывает СПИСОК провалов, а не одно исключение.
|
||||
@@ -59,6 +61,16 @@ const res = await page.evaluate(async () => {
|
||||
await c.updateComplete;
|
||||
out.toolIsSelected = c._decorTool === 'furniture';
|
||||
const pal = () => sr().querySelector('.furnpalette');
|
||||
const openFurniture = async () => {
|
||||
if (c._decorTool !== 'furniture') {
|
||||
c._editorSecondary.openPalette();
|
||||
c._decorTool = 'furniture';
|
||||
c._furnPalette = null;
|
||||
c._furnCategory = null;
|
||||
c.requestUpdate();
|
||||
await c.updateComplete;
|
||||
}
|
||||
};
|
||||
out.paletteOpens = !!pal();
|
||||
const groups = [...(pal()?.querySelectorAll('.furngroup') || [])]
|
||||
.map((g) => g.getAttribute('data-group'));
|
||||
@@ -126,9 +138,67 @@ const res = await page.evaluate(async () => {
|
||||
out.fieldsShowMetres = +(wIn()?.value ?? NaN) === 1.8 && +(hIn()?.value ?? NaN) === 0.9;
|
||||
out.selectedItemIsMarked = !!item('sofa')?.classList.contains('on');
|
||||
|
||||
// ================= 3. размещение в реальном размере ======================
|
||||
// ================= 3. точное превью без мутации ==========================
|
||||
const ghost = () => sr().querySelector('.furniture-placement-preview');
|
||||
const beforePreview = {
|
||||
decor: c._decorList.length,
|
||||
epoch: c._cfgEpoch,
|
||||
undo: c._geometryHistory.undoName,
|
||||
redo: c._geometryHistory.redoName,
|
||||
};
|
||||
ev('pointermove', stageEl(), 300, 300, { pointerType: 'mouse' });
|
||||
await c.updateComplete;
|
||||
const firstPreview = c._furniturePreviewPlacement
|
||||
? JSON.parse(JSON.stringify(c._furniturePreviewPlacement)) : null;
|
||||
const firstTransform = ghost()?.getAttribute('transform');
|
||||
out.mouseHoverShowsRealSymbol = ghost()?.getAttribute('data-symbol') === 'sofa'
|
||||
&& (ghost()?.getAttribute('d') || '').length > 20;
|
||||
out.previewIsTransientAndInert = ghost()?.getAttribute('aria-hidden') === 'true'
|
||||
&& getComputedStyle(ghost()).pointerEvents === 'none'
|
||||
&& near(Number(getComputedStyle(ghost()).opacity), 0.55, 1e-6);
|
||||
out.previewDoesNotMutateConfigOrHistory = c._decorList.length === beforePreview.decor
|
||||
&& c._cfgEpoch === beforePreview.epoch
|
||||
&& c._geometryHistory.undoName === beforePreview.undo
|
||||
&& c._geometryHistory.redoName === beforePreview.redo;
|
||||
if (wIn()) {
|
||||
wIn().value = '1.85';
|
||||
wIn().dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
await c.updateComplete;
|
||||
out.sizeUpdatesPreviewWithoutPointerMove = c._furniturePreviewPlacement?.w > firstPreview?.w
|
||||
&& ghost()?.getAttribute('transform') !== firstTransform;
|
||||
if (wIn()) {
|
||||
wIn().value = '1.8';
|
||||
wIn().dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
await c.updateComplete;
|
||||
stageEl().dispatchEvent(new PointerEvent('pointerleave', {
|
||||
bubbles: true, composed: true, pointerId: 11, pointerType: 'mouse',
|
||||
}));
|
||||
await c.updateComplete;
|
||||
out.pointerLeaveClearsPreview = !ghost() && c._furnPreviewInput === null;
|
||||
|
||||
// Unknown/stale symbols fail dark: neither an invisible ghost nor a decor
|
||||
// record is allowed. Restoring a valid selection resumes the same session.
|
||||
c._furnPalette = { symbol: 'future_unknown_symbol', w: 180, h: 90 };
|
||||
await c.updateComplete;
|
||||
ev('pointermove', stageEl(), 300, 300, { pointerType: 'mouse' });
|
||||
await c.updateComplete;
|
||||
const beforeUnknown = c._decorList.length;
|
||||
ev('pointerdown', stageEl(), 300, 300, { pointerType: 'mouse' });
|
||||
await c.updateComplete;
|
||||
out.unknownSymbolFailsDark = !ghost() && c._decorList.length === beforeUnknown
|
||||
&& c._decorTool === 'furniture';
|
||||
c._furnPalette = { symbol: 'sofa', w: 180, h: 90 };
|
||||
await c.updateComplete;
|
||||
|
||||
// ================= 4. размещение в реальном размере ======================
|
||||
// середина комнаты r1 (40..550 × 140..580) — до стен дальше порога магнита
|
||||
ev('pointerdown', stageEl(), 300, 300);
|
||||
ev('pointermove', stageEl(), 300, 300, { pointerType: 'mouse' });
|
||||
await c.updateComplete;
|
||||
const committedPreview = c._furniturePreviewPlacement
|
||||
? JSON.parse(JSON.stringify(c._furniturePreviewPlacement)) : null;
|
||||
ev('pointerdown', stageEl(), 300, 300, { pointerType: 'mouse' });
|
||||
await c.updateComplete;
|
||||
const sofa = c._decorList.find((s) => s.kind === 'furniture');
|
||||
const sofaId = sofa?.id ?? null;
|
||||
@@ -140,6 +210,10 @@ const res = await page.evaluate(async () => {
|
||||
&& near((sofa.x + sofa.w / 2) * 1000, 300, 1e-6)
|
||||
&& near((sofa.y + sofa.h / 2) * 1000, 300, 1e-6);
|
||||
out.noAngleWhenStraight = !!sofa && sofa.angle === undefined;
|
||||
out.previewAndCommitAreIdentical = !!sofa && !!committedPreview
|
||||
&& ['x', 'y', 'w', 'h'].every((k) => near(sofa[k], committedPreview[k], 1e-12))
|
||||
&& (sofa.angle || 0) === committedPreview.angle;
|
||||
out.previewClearsAfterCommit = !ghost() && c._furnPreviewInput === null;
|
||||
out.selectedRightAway = !!sofaId && c._decorSel === sofaId;
|
||||
out.toolWentBackToSelect = c._decorTool === 'select';
|
||||
out.paletteDisarmed = c._furnPalette === null;
|
||||
@@ -154,8 +228,8 @@ const res = await page.evaluate(async () => {
|
||||
&& /scale\(/.test(attr(sofaId, 'transform') || '')
|
||||
&& attr(sofaId, 'vector-effect') === 'non-scaling-stroke';
|
||||
|
||||
// ================= 4. свой размер до размещения ==========================
|
||||
c._decorTool = 'furniture'; await c.updateComplete;
|
||||
// ================= 5. свой размер до размещения ==========================
|
||||
await openFurniture();
|
||||
category('toilet')?.click(); await c.updateComplete;
|
||||
pick('toilet'); await c.updateComplete;
|
||||
if (wIn()) {
|
||||
@@ -170,8 +244,8 @@ const res = await page.evaluate(async () => {
|
||||
out.typedSizeIsUsed = near((wc?.w ?? NaN) * 1000, cmToUnits(50), 1e-6)
|
||||
&& near((wc?.h ?? NaN) * 1000, cmToUnits(70), 1e-6);
|
||||
|
||||
// ================= 5. магнит к стене =====================================
|
||||
c._decorTool = 'furniture'; await c.updateComplete;
|
||||
// ================= 6. магнит к стене =====================================
|
||||
await openFurniture();
|
||||
category('bed')?.click(); await c.updateComplete;
|
||||
pick('bed_double'); await c.updateComplete;
|
||||
ev('pointerdown', stageEl(), 300, 150); // 10 единиц от стены y=140
|
||||
@@ -184,19 +258,42 @@ const res = await page.evaluate(async () => {
|
||||
out.wallMagnetQuantisesAlongTheWall = !!bed
|
||||
&& Math.abs((bed.x + bed.w / 2) * 1000 - 300) <= PITCH + 1e-6;
|
||||
|
||||
// …Shift больше не отключает магнит
|
||||
c._decorTool = 'furniture'; await c.updateComplete;
|
||||
// Shift обходит магнит, но оставляет обычную привязку декора к сетке.
|
||||
await openFurniture();
|
||||
category('bed')?.click(); await c.updateComplete;
|
||||
pick('bed_single'); await c.updateComplete;
|
||||
ev('pointerdown', stageEl(), 301.7, 151.3, { shiftKey: true });
|
||||
await c.updateComplete;
|
||||
const free = c._decorList.find((s) => s.symbol === 'bed_single');
|
||||
out.shiftKeepsTheMagnet = !!free
|
||||
out.shiftBypassesTheWallMagnet = !!free
|
||||
&& !near((free.y + free.h / 2) * 1000, 151.3, 1e-6)
|
||||
&& free.angle === undefined;
|
||||
|
||||
// Coarse pointers have no hover. A cancelled/dragged/second-contact gesture
|
||||
// must not become an accidental furniture save. Keep this after the palette
|
||||
// click checks because these synthetic pointer streams deliberately have no
|
||||
// browser-generated compatibility click tail.
|
||||
await openFurniture();
|
||||
category('chair')?.click(); await c.updateComplete;
|
||||
pick('chair'); await c.updateComplete;
|
||||
const beforeTouch = c._decorList.length;
|
||||
ev('pointerdown', stageEl(), 330, 330, { pointerType: 'touch', pointerId: 21 });
|
||||
ev('pointermove', stageEl(), 350, 350, { pointerType: 'touch', pointerId: 21 });
|
||||
ev('pointerup', stageEl(), 350, 350, { pointerType: 'touch', pointerId: 21 });
|
||||
ev('pointerdown', stageEl(), 330, 330, { pointerType: 'touch', pointerId: 22 });
|
||||
ev('pointercancel', stageEl(), 330, 330, { pointerType: 'touch', pointerId: 22 });
|
||||
ev('pointerdown', stageEl(), 330, 330, { pointerType: 'touch', pointerId: 23 });
|
||||
ev('pointerdown', stageEl(), 340, 340, { pointerType: 'touch', pointerId: 24, isPrimary: false });
|
||||
ev('pointerup', stageEl(), 330, 330, { pointerType: 'touch', pointerId: 23 });
|
||||
ev('pointerup', stageEl(), 340, 340, { pointerType: 'touch', pointerId: 24, isPrimary: false });
|
||||
await c.updateComplete;
|
||||
out.touchCancelMoveAndSecondContactDoNotSave = c._decorList.length === beforeTouch
|
||||
&& !ghost() && c._furnTouchPending === null;
|
||||
|
||||
// …магнит работает и при ПЕРЕТАСКИВАНИИ: тянем диван к левой стене (x=40)
|
||||
c._decorTool = 'select'; c._decorSel = sofaId; c.requestUpdate();
|
||||
c._editorRuntime._clearFurniturePreview();
|
||||
c._decorTool = 'select'; c._furnPalette = null; c._furnCategory = null;
|
||||
c._decorSel = sofaId; c.requestUpdate();
|
||||
await c.updateComplete; await sleep(40); await c.updateComplete;
|
||||
ev('pointerdown', el(sofaId), 300, 300);
|
||||
out.dragStarted = !!sofaId && c._decorMove?.id === sofaId;
|
||||
@@ -213,7 +310,7 @@ const res = await page.evaluate(async () => {
|
||||
ev('pointerup', stageEl(), 300, 300, { shiftKey: true });
|
||||
await c.updateComplete;
|
||||
|
||||
// ================= 6. рамка, углы, плашки, поворот =======================
|
||||
// ================= 7. рамка, углы, плашки, поворот =======================
|
||||
c._decorSel = sofaId; c.requestUpdate();
|
||||
await c.updateComplete; await sleep(60); await c.updateComplete;
|
||||
const frame = () => sr().querySelector('.dtframe');
|
||||
@@ -285,7 +382,7 @@ const res = await page.evaluate(async () => {
|
||||
await c.updateComplete;
|
||||
out.rotationIsRendered = /rotate\(/.test(attr(sofaId, 'transform') || '');
|
||||
|
||||
// ================= 7. конфиг и пересборка ================================
|
||||
// ================= 8. конфиг и пересборка ================================
|
||||
const stored = JSON.parse(JSON.stringify(sofaNow()));
|
||||
out.storedShapeIsComplete = stored.kind === 'furniture' && typeof stored.symbol === 'string'
|
||||
&& ['x', 'y', 'w', 'h'].every((k) => Number.isFinite(stored[k]));
|
||||
@@ -305,7 +402,7 @@ const res = await page.evaluate(async () => {
|
||||
)?.value === stored.symbol;
|
||||
c._decorShapeDialog = null;
|
||||
|
||||
// ================= 8. инертность и удаление ==============================
|
||||
// ================= 9. инертность и удаление ==============================
|
||||
c._decorTool = 'furniture'; c._furnPalette = null; await c.updateComplete;
|
||||
out.inertUnderTheStamp = pe(sofaId) === 'none';
|
||||
const n0 = c._decorList.length;
|
||||
|
||||
Vendored
+51
-51
@@ -1,111 +1,111 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "817110bad40d29ee0aa8e4ef2dadfda9e310dfa6346ddb5d2725ecafb359f073",
|
||||
"fingerprint": "e6c51f2467ae4e22a792e536ae954291ab5a46915e728b216c8c45bc6816b209",
|
||||
"entry": "houseplan-card.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-BrUCyMoE.js",
|
||||
"houseplan-assets/houseplan-card-DI__VUPO.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 271841,
|
||||
"initialViewGzipBytes": 272469,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/de-CndLXoON.js",
|
||||
"houseplan-assets/editor-H80bjnWS.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-LLPkWHY0.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BkWWzfSR.js",
|
||||
"houseplan-assets/space-deletion-CLA5ezlL.js"
|
||||
"houseplan-assets/de-gTJXGK7s.js",
|
||||
"houseplan-assets/editor-LGCA8WjH.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Cu2NFP8N.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B4PXZ4um.js",
|
||||
"houseplan-assets/space-deletion-Do1Uuvoz.js"
|
||||
],
|
||||
"lazyGzipBytes": 165904,
|
||||
"lazyGzipBytes": 166314,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/editor-H80bjnWS.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-LLPkWHY0.js",
|
||||
"houseplan-assets/space-deletion-CLA5ezlL.js"
|
||||
"houseplan-assets/editor-LGCA8WjH.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Cu2NFP8N.js",
|
||||
"houseplan-assets/space-deletion-Do1Uuvoz.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 136806,
|
||||
"lazyEditorGzipBytes": 137196,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BkWWzfSR.js",
|
||||
"houseplan-assets/space-deletion-CLA5ezlL.js"
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B4PXZ4um.js",
|
||||
"houseplan-assets/space-deletion-Do1Uuvoz.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 7193,
|
||||
"lazyOnboardingGzipBytes": 7204,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-CndLXoON.js"
|
||||
"houseplan-assets/de-gTJXGK7s.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 22454,
|
||||
"lazyLocaleGzipBytes": 22468,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/de-CndLXoON.js",
|
||||
"sha256": "c3fb30be483664a92a555e2a916d2d6201a5d950d77385a34869445625732be1",
|
||||
"path": "houseplan-assets/de-gTJXGK7s.js",
|
||||
"sha256": "2ed2a8aad10d9eaa16fe3fe2e591c2d736d948b2fd35aca01fcffb33264e1284",
|
||||
"rawBytes": 73919,
|
||||
"gzipBytes": 22454,
|
||||
"gzipBytes": 22468,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-H80bjnWS.js",
|
||||
"sha256": "bcd9f271360a94dfbf63d5bcdf3c965aed1e1b17d0c543ac0b554d5527b90250",
|
||||
"path": "houseplan-assets/editor-LGCA8WjH.js",
|
||||
"sha256": "0f4ce2555703ec01ee188cf84badfdee661587e849c1b604ec88354c3f94894f",
|
||||
"rawBytes": 3831,
|
||||
"gzipBytes": 1580,
|
||||
"gzipBytes": 1585,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BrUCyMoE.js"
|
||||
"houseplan-assets/houseplan-card-DI__VUPO.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-BrUCyMoE.js",
|
||||
"sha256": "bdcf3f7e9306428b2583f1e662541bebebcf2f106b48a53df117c6c9c54c5cde",
|
||||
"rawBytes": 958245,
|
||||
"gzipBytes": 271090,
|
||||
"path": "houseplan-assets/houseplan-card-DI__VUPO.js",
|
||||
"sha256": "35a537b289b388f34a5f6efc01a95254207be9880fa9fd931723ccfa258ad602",
|
||||
"rawBytes": 960284,
|
||||
"gzipBytes": 271715,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-CndLXoON.js",
|
||||
"houseplan-assets/editor-H80bjnWS.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-LLPkWHY0.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BkWWzfSR.js"
|
||||
"houseplan-assets/de-gTJXGK7s.js",
|
||||
"houseplan-assets/editor-LGCA8WjH.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-Cu2NFP8N.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-B4PXZ4um.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-LLPkWHY0.js",
|
||||
"sha256": "e49282b51d104baff861b3c21261e66dcbfd58807e0db444b04f1f171499e24d",
|
||||
"rawBytes": 520292,
|
||||
"gzipBytes": 134677,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-Cu2NFP8N.js",
|
||||
"sha256": "4eb656ee069026534dace297817908e97d0e82e63ba1059d0c3d6c283d913344",
|
||||
"rawBytes": 522188,
|
||||
"gzipBytes": 135057,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BrUCyMoE.js",
|
||||
"houseplan-assets/space-deletion-CLA5ezlL.js"
|
||||
"houseplan-assets/houseplan-card-DI__VUPO.js",
|
||||
"houseplan-assets/space-deletion-Do1Uuvoz.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-BkWWzfSR.js",
|
||||
"sha256": "5446043fbf2a383f84df3751b1fd97ecac72d8bcf3c254da80ad4176538c91da",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-B4PXZ4um.js",
|
||||
"sha256": "73ad6471b2395b982d233754598fcb6fb296c29b01a188cce724c2861e044187",
|
||||
"rawBytes": 26356,
|
||||
"gzipBytes": 6644,
|
||||
"gzipBytes": 6650,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BrUCyMoE.js",
|
||||
"houseplan-assets/space-deletion-CLA5ezlL.js"
|
||||
"houseplan-assets/houseplan-card-DI__VUPO.js",
|
||||
"houseplan-assets/space-deletion-Do1Uuvoz.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/space-deletion-CLA5ezlL.js",
|
||||
"sha256": "ec0519abd52ab649e2ed8f5744cc8284d96d8a83f3c9517df7f5070be63a66e9",
|
||||
"rawBytes": 906,
|
||||
"gzipBytes": 549,
|
||||
"path": "houseplan-assets/space-deletion-Do1Uuvoz.js",
|
||||
"sha256": "258cd7b7338ca746122d6afcfc63f81d34be23a7bd31f1599ed8d45ec0daa5b6",
|
||||
"rawBytes": 911,
|
||||
"gzipBytes": 554,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "9bb7a0cb60837b9a006d1c0d125c820ab083b270a9c6130147cd5c8c8b47cff1",
|
||||
"sha256": "031ef9710a85647066f82b62cd7a294c752493c0391cf59b1688380f7cac2a18",
|
||||
"rawBytes": 1089,
|
||||
"gzipBytes": 751,
|
||||
"gzipBytes": 754,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BrUCyMoE.js"
|
||||
"houseplan-assets/houseplan-card-DI__VUPO.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+5
-5
@@ -1,14 +1,14 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="817110bad40d29ee0aa8e4ef2dadfda9e310dfa6346ddb5d2725ecafb359f073";import{c as e,l as o,t,c_ as s,A as a,c$ as i,d0 as l,d1 as n,E as r,b as c}from"./houseplan-card-BrUCyMoE.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__="e6c51f2467ae4e22a792e536ae954291ab5a46915e728b216c8c45bc6816b209";import{c as e,l as o,t,cZ as s,A as a,c_ as i,c$ as l,d0 as n,E as r,b as c}from"./houseplan-card-DI__VUPO.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")},_=this._schema,f=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),u=e=>c`<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._formData}
|
||||
.schema=${e}
|
||||
.computeLabel=${e=>h[e.name]||e.name}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>`;return c`
|
||||
${d(f.slice(0,3))}
|
||||
${_?c`<div class="default-floor-error" role="alert"
|
||||
${u(_.slice(0,3))}
|
||||
${f?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:_})}
|
||||
${t(s,"editor.default_floor_missing",{id:f})}
|
||||
</div>`:a}
|
||||
${d(f.slice(3))}
|
||||
${u(_.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);
|
||||
+77
-68
File diff suppressed because one or more lines are too long
+93
-93
File diff suppressed because one or more lines are too long
+5
-5
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="817110bad40d29ee0aa8e4ef2dadfda9e310dfa6346ddb5d2725ecafb359f073";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 _,cS as d,ap as u,ar as g,cT as m}from"./houseplan-card-BrUCyMoE.js";import{i as b,c as $,a as v,s as f,t as y}from"./space-deletion-CLA5ezlL.js";const w=1e3,D=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},S="817110bad40d29ee0aa8e4ef2dadfda9e310dfa6346ddb5d2725ecafb359f073";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__="e6c51f2467ae4e22a792e536ae954291ab5a46915e728b216c8c45bc6816b209";import{bT as t,bU as s,bV as e,bW as o,aq as a,bX as l,c0 as i,bY as h,bZ as n,b_ as c,b$ as r,b as p,A as _,cR as d,ap as u,ar as g,cS as m}from"./houseplan-card-DI__VUPO.js";import{i as b,c as $,a as v,s as y,t as f}from"./space-deletion-Do1Uuvoz.js";const w=1e3,D=t=>{if(!t.trim())return null;const s=Number(t.replace(",","."));return Number.isFinite(s)?s:null},S="e6c51f2467ae4e22a792e536ae954291ab5a46915e728b216c8c45bc6816b209";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" />
|
||||
@@ -46,7 +46,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="817110bad40d29ee0aa8e4ef2dadfda9e310
|
||||
<label>${this.host._t("space.plan_label")}</label>
|
||||
<label class="srcrow">
|
||||
<input type="radio" name="plansrc" .checked=${"file"===t.source}
|
||||
@change=${()=>this.host._spaceDialog=f(t,"file")} />
|
||||
@change=${()=>this.host._spaceDialog=y(t,"file")} />
|
||||
<span>${this.host._t("space.source_file")}</span>
|
||||
</label>
|
||||
${"file"===t.source?p`<div class="planrow">
|
||||
@@ -68,7 +68,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="817110bad40d29ee0aa8e4ef2dadfda9e310
|
||||
${t.pickSaved?this._renderServerPlans(t):_}`:_}
|
||||
<label class="srcrow">
|
||||
<input type="radio" name="plansrc" .checked=${"draw"===t.source}
|
||||
@change=${()=>this.host._spaceDialog=f(t,"draw")} />
|
||||
@change=${()=>this.host._spaceDialog=y(t,"draw")} />
|
||||
<span>${this.host._t("space.source_draw")}</span>
|
||||
</label>
|
||||
|
||||
@@ -85,7 +85,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="817110bad40d29ee0aa8e4ef2dadfda9e310
|
||||
|
||||
<label class="dispsection">${this.host._t("space.display_section")}</label>
|
||||
<label class="srcrow">
|
||||
${this._boolInput(t.showBorders,s=>{this.host._spaceDialog=y(t,"showBorders",s)})}
|
||||
${this._boolInput(t.showBorders,s=>{this.host._spaceDialog=f(t,"showBorders",s)})}
|
||||
<span>${this.host._t("space.show_borders")}</span>
|
||||
</label>
|
||||
<label>${this.host._t("space.zero_wall_style")}</label>
|
||||
@@ -99,7 +99,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="817110bad40d29ee0aa8e4ef2dadfda9e310
|
||||
</select>
|
||||
<div class="rhint">${this.host._t("space.zero_wall_help")}</div>
|
||||
<label class="srcrow">
|
||||
${this._boolInput(t.showNames,s=>{this.host._spaceDialog=y(t,"showNames",s)})}
|
||||
${this._boolInput(t.showNames,s=>{this.host._spaceDialog=f(t,"showNames",s)})}
|
||||
<span>${this.host._t("space.show_names")}</span>
|
||||
</label>
|
||||
<label class="srcrow">
|
||||
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__="817110bad40d29ee0aa8e4ef2dadfda9e310dfa6346ddb5d2725ecafb359f073";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)),a=[...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:a,count:a.length}}globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="e6c51f2467ae4e22a792e536ae954291ab5a46915e728b216c8c45bc6816b209";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__="817110bad40d29ee0aa8e4ef2dadfda9e310dfa6346ddb5d2725ecafb359f073";try{await import("./houseplan-assets/houseplan-card-BrUCyMoE.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__="e6c51f2467ae4e22a792e536ae954291ab5a46915e728b216c8c45bc6816b209";try{await import("./houseplan-assets/houseplan-card-DI__VUPO.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,12 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Furniture placement in the Background editor now shows a translucent mouse
|
||||
preview with the exact future size, rotation and wall snap. It follows live
|
||||
size edits and disappears without a config change on cancel; on touch,
|
||||
movement, cancellation or a second contact cannot create an accidental item
|
||||
([#359](https://github.com/Matysh/houseplan-card/issues/359)).
|
||||
|
||||
- Robot-vacuum trails now follow the same recorded points with softly rounded
|
||||
corners instead of a visibly segmented polyline. Current and previous runs
|
||||
use the same bounded curve, preserve telemetry gaps and never deviate by more
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
|
||||
## Не выпущено
|
||||
|
||||
- При размещении мебели в редакторе подложки мышь теперь показывает на плане
|
||||
полупрозрачный символ с точным будущим размером, поворотом и привязкой к
|
||||
стене. Предпросмотр обновляется вместе с полями размера и исчезает без
|
||||
изменений при отмене; на touch движение, отмена или второй палец не создают
|
||||
случайный предмет ([#359](https://github.com/Matysh/houseplan-card/issues/359)).
|
||||
|
||||
- След пылесоса теперь повторяет те же записанные точки с мягко скруглёнными
|
||||
поворотами вместо заметной ломаной. Текущая и предыдущая уборки используют
|
||||
одну ограниченную кривую, сохраняют разрывы телеметрии и не отклоняются от
|
||||
|
||||
+15
-3
@@ -53,9 +53,21 @@ Furniture is a two-level non-modal palette in the editor context tray:
|
||||
3. The second level shows the real top-view drawings. Back returns to the
|
||||
category list and clears any armed symbol.
|
||||
4. Picking a variant arms one placement and reveals editable Width and Depth
|
||||
in the Home Assistant length unit.
|
||||
5. Clicking the plan places the object and returns to Select. Shift keeps the
|
||||
established free-placement behaviour.
|
||||
in the Home Assistant length unit. With a mouse, moving over the plan shows
|
||||
the real top-view symbol at its exact future size, wall magnet, rotation and
|
||||
canvas clamp. Editing Width or Depth updates that ghost immediately without
|
||||
requiring another pointer move.
|
||||
5. Clicking the plan places exactly the previewed object and returns to Select.
|
||||
Shift keeps the established free-placement behaviour. The ghost is a
|
||||
transient, 55%-opaque rendering aid: it is never saved and never enters
|
||||
Undo history.
|
||||
6. Pointer leave, Escape, palette/tool/editor/space changes and remount clear
|
||||
the ghost. Unknown or stale symbol ids show and save nothing.
|
||||
|
||||
Touch and pen do not emulate hover or show a placement ghost. Their best-effort
|
||||
editor path saves one object only after a clean tap; movement, pointer cancel
|
||||
or a second contact cancels that pending stamp so pinch/cancel cannot create
|
||||
furniture accidentally. View and kiosk touch guarantees remain unchanged.
|
||||
|
||||
The properties dialog remains a flat native select, grouped by category. Its
|
||||
optgroup label includes both the parent group and category because HTML selects
|
||||
|
||||
@@ -1302,7 +1302,11 @@ Power=`off`/`unavailable` подавляет даже устаревший ак
|
||||
|
||||
### Мебель
|
||||
|
||||
Библиотека содержит группы мебели, бытовой техники, сантехники и прочих символов. Размер можно задать в метрах или десятичных футах. При размещении рядом со стеной предмет:
|
||||
Библиотека содержит группы мебели, бытовой техники, сантехники и прочих символов. Размер можно задать в метрах или десятичных футах. После выбора символа наведите мышь на план: полупрозрачное изображение заранее показывает точное будущее положение, размер и поворот предмета. Изменение ширины или глубины сразу обновляет предпросмотр. Клик размещает предмет именно в показанной позиции; выход курсора с плана, `Esc`, смена инструмента, пространства или редактора убирают предпросмотр без изменения плана.
|
||||
|
||||
На сенсорных экранах предпросмотр не показывается: чистый тап размещает предмет, а движение, отмена касания или второй палец отменяют ожидающее размещение. Редактирование на touch-устройствах по-прежнему поддерживается по остаточному принципу; для полноценной работы используйте desktop.
|
||||
|
||||
При размещении рядом со стеной предмет:
|
||||
|
||||
- прижимается к ней и поворачивается параллельно;
|
||||
- всегда прижимается к ближайшей стене в радиусе магнита, иначе размещается по сетке;
|
||||
|
||||
+65
-1
@@ -28,7 +28,7 @@
|
||||
* colour, so a plan stays a drawing (owner: «символы рисуются линейно»).
|
||||
*/
|
||||
|
||||
import { NORM_W, GRID_PITCH, CANVAS_LIMIT } from './space-geometry';
|
||||
import { NORM_W, GRID_PITCH, CANVAS_LIMIT, clampCanvasN } from './space-geometry';
|
||||
import { GENERATED_FURNITURE_PLAN } from './furniture-plan-art.generated';
|
||||
|
||||
/** Groups the palette shows, in the order it shows them. */
|
||||
@@ -395,6 +395,33 @@ export interface FurnitureSnap {
|
||||
/** Wall segments (`[x1,y1,x2,y2]`) in whatever units the caller works in. */
|
||||
export type WallSeg = number[];
|
||||
|
||||
/** Final normalised box shared by the placement ghost and the saved decor
|
||||
* record. Keeping this result pure prevents a preview/commit jump when wall
|
||||
* magnetism or the canvas guard participates (#359). */
|
||||
export interface FurniturePlacement {
|
||||
symbol: string;
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
angle: number;
|
||||
}
|
||||
|
||||
export interface FurniturePlacementInput {
|
||||
symbol: string;
|
||||
widthCm: number;
|
||||
depthCm: number;
|
||||
/** Already resolved decor/grid snap point, in render units. */
|
||||
point: readonly [number, number];
|
||||
canvasW: number;
|
||||
canvasH: number;
|
||||
cellCm: number;
|
||||
gridPitch: number;
|
||||
walls: WallSeg[];
|
||||
wallReach: number;
|
||||
free?: boolean;
|
||||
}
|
||||
|
||||
const norm180 = (a: number): number => {
|
||||
let x = ((a % 360) + 360) % 360;
|
||||
if (x > 180) x -= 360;
|
||||
@@ -458,6 +485,43 @@ export function snapFurnitureToWall(
|
||||
return best;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the exact geometry one furniture stamp will own.
|
||||
*
|
||||
* The caller owns the higher-level decor/room/grid snap because that geometry
|
||||
* is editor state. Everything after that point is pure and therefore shared
|
||||
* byte-for-byte by hover preview and commit. Unknown symbols fail dark: a
|
||||
* newer config or a stale palette cannot create invisible furniture.
|
||||
*/
|
||||
export function resolveFurniturePlacement(input: FurniturePlacementInput): FurniturePlacement | null {
|
||||
const {
|
||||
symbol, widthCm, depthCm, point, canvasW, canvasH, cellCm, gridPitch,
|
||||
walls, wallReach, free = false,
|
||||
} = input;
|
||||
if (!furnitureGraphic(symbol) || !(canvasW > 0) || !(canvasH > 0)
|
||||
|| !Number.isFinite(point[0]) || !Number.isFinite(point[1])) return null;
|
||||
const w = clampFurnSize(cmToNorm(widthCm, cellCm, gridPitch, canvasW));
|
||||
const h = clampFurnSize(cmToNorm(depthCm, cellCm, gridPitch, canvasW));
|
||||
let cx = point[0], cy = point[1];
|
||||
let angle = 0;
|
||||
const snap = free ? null : snapFurnitureToWall(
|
||||
cx, cy, h * canvasH, walls, wallReach, gridPitch,
|
||||
);
|
||||
if (snap) {
|
||||
cx = snap.cx;
|
||||
cy = snap.cy;
|
||||
angle = snap.angle;
|
||||
}
|
||||
return {
|
||||
symbol,
|
||||
x: clampCanvasN(cx / canvasW - w / 2),
|
||||
y: clampCanvasN(cy / canvasH - h / 2),
|
||||
w,
|
||||
h,
|
||||
angle: Number(angle.toFixed(2)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The four corners of a rotated box, world units, in the order the frame
|
||||
* draws them: NW, NE, SE, SW. Pure, so the frame, the gestures and the tests
|
||||
|
||||
+53
-3
@@ -63,7 +63,8 @@ import { dayCycleStageVars, renderDayCycleEnvironment } from './day-cycle-render
|
||||
import {
|
||||
FURNITURE_GROUPS, furnitureOfGroup, furnitureSymbol, furnitureDefaultCm,
|
||||
furnitureGraphic, furnitureCorners, snapFurnitureToWall,
|
||||
cmToNorm, clampFurnSize, clampFurnCm, FURN_WALL_CELLS, type FurnitureGroup,
|
||||
cmToNorm, clampFurnSize, clampFurnCm, FURN_WALL_CELLS,
|
||||
type FurnitureGroup, type FurniturePlacement,
|
||||
} from './furniture';
|
||||
import {
|
||||
degradeWalls, rekeyWallsAfterMoveChecked, wallRecordCarrierViolations,
|
||||
@@ -1037,6 +1038,15 @@ export class HouseplanCard extends LitElement {
|
||||
private _furnPalette: { symbol: string; w: number; h: number } | null = null;
|
||||
/** Category currently open in the two-level furniture palette. */
|
||||
private _furnCategory: string | null = null;
|
||||
/** Last fine-pointer location while one furniture stamp is armed. The final
|
||||
* geometry is deliberately resolved on render from the live palette so a
|
||||
* Width/Depth edit updates the ghost without another mouse move (#359). */
|
||||
private _furnPreviewInput: { raw: [number, number]; free: boolean } | null = null;
|
||||
/** Touch/pen has no hover. Delay its one-shot stamp until a clean pointerup
|
||||
* so pointercancel or a second contact cannot save accidental geometry. */
|
||||
private _furnTouchPending: {
|
||||
pid: number; sx: number; sy: number; pointerType: string; cancelled: boolean;
|
||||
} | null = null;
|
||||
/** The selected shape's own unrotated frame (text is measured from SVG). */
|
||||
private _dtBox: { id: string; x: number; y: number; w: number; h: number } | null = null;
|
||||
/**
|
||||
@@ -1437,6 +1447,7 @@ export class HouseplanCard extends LitElement {
|
||||
if (id !== this._space) {
|
||||
this._clearTransientHover(true);
|
||||
this._cancelDevicePressFeedback();
|
||||
this._editorRuntime?._clearFurniturePreview();
|
||||
}
|
||||
this._space = id;
|
||||
return true;
|
||||
@@ -2431,6 +2442,7 @@ export class HouseplanCard extends LitElement {
|
||||
_backdropDialog: { state: true },
|
||||
_furnPalette: { state: true },
|
||||
_furnCategory: { state: true },
|
||||
_furnPreviewInput: { state: true },
|
||||
_bdDrag: { state: true },
|
||||
_dtBox: { state: true },
|
||||
_dtDrag: { state: true },
|
||||
@@ -2676,6 +2688,7 @@ export class HouseplanCard extends LitElement {
|
||||
this._warmRelease();
|
||||
this._clearPlanSnapHover();
|
||||
this._clearOpeningPlacement(true);
|
||||
this._editorRuntime?._clearFurniturePreview();
|
||||
this._touchContacts.clear();
|
||||
this._touchSequenceMultitouch = false;
|
||||
this._touchClickBlockUntil = 0;
|
||||
@@ -2781,6 +2794,7 @@ export class HouseplanCard extends LitElement {
|
||||
// The palette is one explicit surface: Escape closes it and returns
|
||||
// to Select in one step, regardless of whether a symbol was armed.
|
||||
else if (this._decorTool === 'furniture') {
|
||||
this._editorRuntime?._clearFurniturePreview();
|
||||
this._furnPalette = null;
|
||||
this._furnCategory = null;
|
||||
this._decorTool = 'select';
|
||||
@@ -6156,6 +6170,10 @@ export class HouseplanCard extends LitElement {
|
||||
this._decorMoveUpdate(ev);
|
||||
return;
|
||||
}
|
||||
if (this._mode === 'decor' && this._decorTool === 'furniture' && this._editorRuntime) {
|
||||
this._notePointer(ev);
|
||||
if (this._editorRuntime._furnPointerMove(ev, this._pointerModality.hoverEnabled)) return;
|
||||
}
|
||||
if (!this._pointers.has(ev.pointerId)) {
|
||||
this._markupMove(ev);
|
||||
return;
|
||||
@@ -6220,7 +6238,8 @@ export class HouseplanCard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _stagePointerLeave(_ev: PointerEvent): void {
|
||||
private _stagePointerLeave(ev: PointerEvent): void {
|
||||
if (this._mode === 'decor') this._editorRuntime?._furnPointerLeave(ev);
|
||||
if (!this._markup) return;
|
||||
if (this._tool === 'opening') {
|
||||
this._cursorPt = null;
|
||||
@@ -6297,6 +6316,7 @@ export class HouseplanCard extends LitElement {
|
||||
this._decorMove = null;
|
||||
return;
|
||||
}
|
||||
if (this._editorRuntime?._furnPointerUp(ev)) return;
|
||||
const viewportGestureEnded = !!this._pinchStart || !!this._panStart;
|
||||
this._pointers.delete(ev.pointerId);
|
||||
if (this._pointers.size < 2) this._pinchStart = null;
|
||||
@@ -8018,6 +8038,36 @@ export class HouseplanCard extends LitElement {
|
||||
</g>` as unknown as TemplateResult;
|
||||
}
|
||||
|
||||
private get _furniturePreviewPlacement(): FurniturePlacement | null {
|
||||
const input = this._furnPreviewInput;
|
||||
if (!input || !this._editorRuntime || this._mode !== 'decor'
|
||||
|| this._decorTool !== 'furniture' || !this._furnPalette
|
||||
|| !this._pointerModality.hoverEnabled) return null;
|
||||
return this._editorRuntime._resolveFurniturePlacement(input.raw, input.free, 'mouse');
|
||||
}
|
||||
|
||||
/** One real furniture path, in the same decor composition group as saved
|
||||
* shapes. It paints after them (so it is visible) and the whole decor layer
|
||||
* still paints below physical walls (#359). */
|
||||
private _renderFurniturePlacementPreview(): TemplateResult | typeof nothing {
|
||||
const placement = this._furniturePreviewPlacement;
|
||||
if (!placement) return nothing;
|
||||
const art = furnitureGraphic(placement.symbol);
|
||||
if (!art) return nothing;
|
||||
const W = NORM_W, H = this._decorH;
|
||||
const W2 = placement.w * W, H2 = placement.h * H;
|
||||
const cx = placement.x * W + W2 / 2, cy = placement.y * H + H2 / 2;
|
||||
const transform = `${placement.angle ? `rotate(${placement.angle} ${cx} ${cy}) ` : ''}`
|
||||
+ `translate(${placement.x * W} ${placement.y * H}) scale(${W2 / art.viewW} ${H2 / art.viewH})`;
|
||||
const style = this._decorStyle;
|
||||
return svg`<path class="furniture-placement-preview dfurn"
|
||||
data-symbol=${placement.symbol} d=${art.d} transform=${transform}
|
||||
stroke=${style.color} stroke-opacity=${style.opacity}
|
||||
stroke-width=${decorCmToUnits(style.widthCm, this._cellCm, this._gridPitch)}
|
||||
fill="none" stroke-linecap="round" stroke-linejoin="round"
|
||||
vector-effect="non-scaling-stroke" aria-hidden="true" pointer-events="none"></path>`;
|
||||
}
|
||||
|
||||
private _renderDecorLayer(): TemplateResult {
|
||||
const W = NORM_W, H = this._decorH;
|
||||
const editing = this._mode === 'decor';
|
||||
@@ -8137,7 +8187,7 @@ export class HouseplanCard extends LitElement {
|
||||
stroke="${st.color}" stroke-opacity="${st.opacity}" stroke-width="${sw}" fill="${st.fill ? st.fillColor : 'none'}" fill-opacity="${st.fill ? st.fillOpacity : 0}"></ellipse>`;
|
||||
}
|
||||
}
|
||||
return svg`<g class="decorlayer">${shapes}${draft}</g>` as unknown as TemplateResult;
|
||||
return svg`<g class="decorlayer">${shapes}${draft}${this._renderFurniturePlacementPreview()}</g>` as unknown as TemplateResult;
|
||||
}
|
||||
|
||||
// ================= shared editor secondary surface =================
|
||||
|
||||
+122
-16
@@ -65,7 +65,9 @@ import { dayCycleStageVars, renderDayCycleEnvironment } from './day-cycle-render
|
||||
import {
|
||||
FURNITURE_GROUPS, furnitureOfGroup, furnitureDefaultCm,
|
||||
furnitureGraphic, furnitureCorners, snapFurnitureToWall,
|
||||
cmToNorm, clampFurnSize, clampFurnCm, FURN_WALL_CELLS, type FurnitureGroup,
|
||||
resolveFurniturePlacement,
|
||||
clampFurnCm, FURN_WALL_CELLS,
|
||||
type FurnitureGroup, type FurniturePlacement,
|
||||
} from './furniture';
|
||||
import { GENERATED_FURNITURE_MENU } from './furniture-menu-art.generated';
|
||||
import {
|
||||
@@ -917,6 +919,10 @@ export interface HouseplanEditorHostPort {
|
||||
_fullRegistryHass: any;
|
||||
_furnPalette: { symbol: string; w: number; h: number; } | null;
|
||||
_furnCategory: string | null;
|
||||
_furnPreviewInput: { raw: [number, number]; free: boolean; } | null;
|
||||
_furnTouchPending: {
|
||||
pid: number; sx: number; sy: number; pointerType: string; cancelled: boolean;
|
||||
} | null;
|
||||
_furnWallReach: number;
|
||||
_furnWalls: number[][];
|
||||
_gearPtCache: WeakMap<number[][], number[]>;
|
||||
@@ -1169,6 +1175,7 @@ public _help(key: Extract<I18nKey, `${string}.help`>): TemplateResult | typeof n
|
||||
|
||||
public _setMode(mode: 'view' | 'plan' | 'devices' | 'decor', animate = true): void {
|
||||
this.host._endTabDrag();
|
||||
this._clearFurniturePreview();
|
||||
// A mode command is newer than the editor remembered by a same-route warm
|
||||
// remount. Clear it before the same-mode early return: while can_write is
|
||||
// pending, Lit may still be presenting the previous editor DOM even though
|
||||
@@ -2197,6 +2204,7 @@ public _commitPhysicalGeometry(
|
||||
}
|
||||
|
||||
public _clearGeometryGesture(): void {
|
||||
this._clearFurniturePreview();
|
||||
this.host._path = [];
|
||||
this._clearPlanSnapHover();
|
||||
this._clearOpeningPlacement(false);
|
||||
@@ -2226,6 +2234,12 @@ public _clearGeometryGesture(): void {
|
||||
public _stagePointerCancel(ev: PointerEvent): void {
|
||||
clearTimeout(this.host._kioskHoldTimer);
|
||||
if (this.host._swipeStart?.id === ev.pointerId) this.host._swipeStart = null;
|
||||
if (this.host._furnTouchPending?.pid === ev.pointerId) {
|
||||
this._clearFurniturePreview();
|
||||
this.host._pointers.delete(ev.pointerId);
|
||||
this.host.requestUpdate();
|
||||
return;
|
||||
}
|
||||
if (this.host._physicalDrag?.pid === ev.pointerId || this.host._physicalRotate?.pid === ev.pointerId) {
|
||||
this._cancelPhysicalGesture();
|
||||
return;
|
||||
@@ -4095,7 +4109,29 @@ public _decorPointerDown(ev: PointerEvent): boolean {
|
||||
// must not silently place whatever was chosen last week.
|
||||
if (!this.host._furnPalette) return false;
|
||||
ev.preventDefault();
|
||||
this._furnPlace(this._svgPoint(ev), ev.shiftKey);
|
||||
// A mouse has already shown the exact result and keeps the traditional
|
||||
// one-press stamp. Touch/pen has no hover, so commit only after a clean
|
||||
// pointerup; pointercancel, movement and a second contact stay fail-dark.
|
||||
const pointerType = ev.pointerType || 'mouse';
|
||||
if (pointerType === 'mouse') {
|
||||
this._furnPlace(this._svgPoint(ev), ev.shiftKey, pointerType);
|
||||
return true;
|
||||
}
|
||||
const pending = this.host._furnTouchPending;
|
||||
if (pending && pending.pid !== ev.pointerId) {
|
||||
pending.cancelled = true;
|
||||
this.host._furnPreviewInput = null;
|
||||
return true;
|
||||
}
|
||||
this.host._furnTouchPending = {
|
||||
pid: ev.pointerId,
|
||||
sx: ev.clientX,
|
||||
sy: ev.clientY,
|
||||
pointerType,
|
||||
cancelled: false,
|
||||
};
|
||||
this.host._furnPreviewInput = null;
|
||||
capturePointer(ev);
|
||||
return true;
|
||||
}
|
||||
// Empty-space selection belongs only to Select. Erase is an object
|
||||
@@ -4639,41 +4675,104 @@ public _furnPick(symbol: string): void {
|
||||
this.host._furnPalette = { symbol, w: d.w, h: d.h };
|
||||
}
|
||||
|
||||
public _furnPlace(raw: number[], free = false): void {
|
||||
public _resolveFurniturePlacement(
|
||||
raw: number[], free = false, pointerType = 'mouse',
|
||||
): FurniturePlacement | null {
|
||||
const pal = this.host._furnPalette;
|
||||
const sp = this.host._curSpaceCfg;
|
||||
if (!pal || !sp) return;
|
||||
if (!pal || !sp) return null;
|
||||
const W = NORM_W, H = this.host._decorH;
|
||||
const wN = clampFurnSize(cmToNorm(pal.w, this.host._cellCm, this.host._gridPitch, W));
|
||||
const hN = clampFurnSize(cmToNorm(pal.h, this.host._cellCm, this.host._gridPitch, W));
|
||||
const snapped = this._decorSnap(raw, pointerType);
|
||||
return resolveFurniturePlacement({
|
||||
symbol: pal.symbol,
|
||||
widthCm: pal.w,
|
||||
depthCm: pal.h,
|
||||
point: [snapped[0], snapped[1]],
|
||||
canvasW: W,
|
||||
canvasH: H,
|
||||
cellCm: this.host._cellCm,
|
||||
gridPitch: this.host._gridPitch,
|
||||
walls: this.host._furnWalls,
|
||||
wallReach: this.host._furnWallReach,
|
||||
free,
|
||||
});
|
||||
}
|
||||
|
||||
public _furnPlace(raw: number[], free = false, pointerType = 'mouse'): void {
|
||||
const placement = this._resolveFurniturePlacement(raw, free, pointerType);
|
||||
if (!placement) return;
|
||||
const sp = this.host._curSpaceCfg;
|
||||
if (!sp) return;
|
||||
const before = this._geometrySnapshot();
|
||||
const c = this._decorSnap(raw);
|
||||
let cx = c[0], cy = c[1];
|
||||
let angle = 0;
|
||||
const snap = free ? null : snapFurnitureToWall(cx, cy, hN * H, this.host._furnWalls,
|
||||
this.host._furnWallReach, this.host._gridPitch);
|
||||
if (snap) { cx = snap.cx; cy = snap.cy; angle = snap.angle; }
|
||||
const id = 'df' + Date.now().toString(36) + Math.random().toString(36).slice(2, 5);
|
||||
const shape: any = {
|
||||
id, kind: 'furniture', symbol: pal.symbol,
|
||||
x: clampCanvasN(cx / W - wN / 2), y: clampCanvasN(cy / H - hN / 2),
|
||||
w: wN, h: hN,
|
||||
id, kind: 'furniture', symbol: placement.symbol,
|
||||
x: placement.x, y: placement.y, w: placement.w, h: placement.h,
|
||||
...decorStylePatch(this.host._decorStyle, false),
|
||||
};
|
||||
// a straight piece stores no angle at all, exactly as a straight label
|
||||
// stores none (docs/LIVE-TEXT.md §3)
|
||||
if (angle) shape.angle = Number(angle.toFixed(2));
|
||||
if (placement.angle) shape.angle = placement.angle;
|
||||
sp.decor = [...this.host._decorList, shape];
|
||||
this.host._decorSel = id;
|
||||
// …and the editor goes back to the tool that can move what was just placed
|
||||
this.host._decorTool = 'select';
|
||||
this.host._furnPalette = null;
|
||||
this.host._furnCategory = null;
|
||||
this._clearFurniturePreview();
|
||||
this._recordGeometry(this.host._t('history.decor_add'), before);
|
||||
this._saveConfig();
|
||||
this.host.requestUpdate();
|
||||
}
|
||||
|
||||
public _clearFurniturePreview(): void {
|
||||
this.host._furnPreviewInput = null;
|
||||
this.host._furnTouchPending = null;
|
||||
}
|
||||
|
||||
/** Track only real fine-pointer hover. Touch/pen own a delayed one-tap
|
||||
* transaction below and must never resurrect a compatibility mouse ghost. */
|
||||
public _furnPointerMove(ev: PointerEvent, hoverAllowed: boolean): boolean {
|
||||
const pending = this.host._furnTouchPending;
|
||||
if (pending?.pid === ev.pointerId) {
|
||||
if (Math.abs(ev.clientX - pending.sx) + Math.abs(ev.clientY - pending.sy) > 8) {
|
||||
pending.cancelled = true;
|
||||
}
|
||||
this.host._furnPreviewInput = null;
|
||||
return true;
|
||||
}
|
||||
if (this.host._mode !== 'decor' || this.host._decorTool !== 'furniture'
|
||||
|| !this.host._furnPalette) {
|
||||
this._clearFurniturePreview();
|
||||
return false;
|
||||
}
|
||||
if (ev.pointerType !== 'mouse' || !hoverAllowed) {
|
||||
this.host._furnPreviewInput = null;
|
||||
return false;
|
||||
}
|
||||
const raw = this._svgPoint(ev);
|
||||
this.host._furnPreviewInput = { raw: [raw[0], raw[1]], free: ev.shiftKey };
|
||||
return true;
|
||||
}
|
||||
|
||||
public _furnPointerLeave(ev: PointerEvent): void {
|
||||
if (this.host._furnTouchPending?.pid === ev.pointerId) {
|
||||
this.host._furnTouchPending.cancelled = true;
|
||||
}
|
||||
this.host._furnPreviewInput = null;
|
||||
}
|
||||
|
||||
public _furnPointerUp(ev: PointerEvent): boolean {
|
||||
const pending = this.host._furnTouchPending;
|
||||
if (!pending || pending.pid !== ev.pointerId) return false;
|
||||
this.host._furnTouchPending = null;
|
||||
if (!pending.cancelled && this.host._mode === 'decor'
|
||||
&& this.host._decorTool === 'furniture' && this.host._furnPalette) {
|
||||
this._furnPlace(this._svgPoint(ev), ev.shiftKey, pending.pointerType);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public _furnMoveUpdate(ev: PointerEvent): void {
|
||||
const m = this.host._decorMove!;
|
||||
if (m.orig.kind !== 'furniture') return;
|
||||
@@ -4765,6 +4864,7 @@ public _renderFurnPalette(): TemplateResult {
|
||||
<span class="spacer"></span>
|
||||
<button class="btn furnclose" title=${this.host._t('btn.close')}
|
||||
@click=${() => {
|
||||
this._clearFurniturePreview();
|
||||
this.host._furnPalette = null;
|
||||
this.host._furnCategory = null;
|
||||
this.host._decorTool = 'select';
|
||||
@@ -4775,6 +4875,7 @@ public _renderFurnPalette(): TemplateResult {
|
||||
<div class="furnbody">
|
||||
${category ? html`
|
||||
<button class="btn ghost furnback" @click=${() => {
|
||||
this._clearFurniturePreview();
|
||||
this.host._furnPalette = null;
|
||||
this.host._furnCategory = null;
|
||||
}}>
|
||||
@@ -4798,6 +4899,7 @@ public _renderFurnPalette(): TemplateResult {
|
||||
class="furnitem furncategory" data-category=${item.id}
|
||||
title=${this.host._t(`furn.cat_${item.id}` as I18nKey)}
|
||||
@click=${() => {
|
||||
this._clearFurniturePreview();
|
||||
this.host._furnPalette = null;
|
||||
this.host._furnCategory = item.id;
|
||||
}}>
|
||||
@@ -5117,6 +5219,7 @@ public _renderDecorSecondary(): EditorSecondaryModel | null {
|
||||
dismissPolicy: this.host._furnPalette ? 'stay-open-on-canvas' : 'outside',
|
||||
dismiss: () => {
|
||||
if (this.host._decorTool !== 'furniture') return;
|
||||
this._clearFurniturePreview();
|
||||
this.host._furnPalette = null;
|
||||
this.host._furnCategory = null;
|
||||
this.host._decorTool = 'select';
|
||||
@@ -5239,6 +5342,7 @@ public _renderDecorBar(): TemplateResult {
|
||||
data-editor-palette=${t === 'furniture' ? 'furniture' : nothing}
|
||||
@click=${() => {
|
||||
if (t === 'furniture' && this.host._decorTool === 'furniture') {
|
||||
this._clearFurniturePreview();
|
||||
this.host._furnPalette = null;
|
||||
this.host._furnCategory = null;
|
||||
this.host._decorTool = 'select';
|
||||
@@ -5250,9 +5354,11 @@ public _renderDecorBar(): TemplateResult {
|
||||
// the palette belongs to its tool and to nothing else: leaving the
|
||||
// tool disarms whatever was chosen, so no later click can stamp it
|
||||
if (t !== 'furniture') {
|
||||
this._clearFurniturePreview();
|
||||
this.host._furnPalette = null;
|
||||
this.host._furnCategory = null;
|
||||
} else {
|
||||
this._clearFurniturePreview();
|
||||
this.host._furnCategory = null;
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -737,6 +737,10 @@ export const planStyles = css`
|
||||
calc(5px * var(--hp-cell-visual-scale, 1));
|
||||
pointer-events: none;
|
||||
}
|
||||
.decorlayer .furniture-placement-preview {
|
||||
opacity: 0.55;
|
||||
pointer-events: none;
|
||||
}
|
||||
.decorlayer text {
|
||||
font-weight: 600;
|
||||
user-select: none;
|
||||
|
||||
+34
-1
@@ -6,7 +6,8 @@ import assert from 'node:assert/strict';
|
||||
import {
|
||||
FURNITURE, FURNITURE_GROUPS, furnitureSymbol, furnitureOfGroup,
|
||||
furnitureDefaultCm, furniturePathD, furnitureGraphic, furnitureCorners, furnitureResize,
|
||||
snapFurnitureToWall, cmToNorm, normToCm, clampFurnSize, clampFurnCm,
|
||||
snapFurnitureToWall, resolveFurniturePlacement,
|
||||
cmToNorm, normToCm, clampFurnSize, clampFurnCm,
|
||||
FURN_MIN_N, FURN_MIN_CM, FURN_MAX_CM, FURN_WALL_CELLS,
|
||||
} from '../test-build/furniture.js';
|
||||
import { roomEdges } from '../test-build/logic.js';
|
||||
@@ -185,6 +186,38 @@ test('the default reach is six cells — thirty centimetres on a default plan',
|
||||
closeTo(GRID_PITCH * FURN_WALL_CELLS * (5 / GRID_PITCH), 30); // cells x cell_cm
|
||||
});
|
||||
|
||||
test('preview and commit share one deterministic furniture placement resolver', () => {
|
||||
const input = {
|
||||
symbol: 'sofa', widthCm: 180, depthCm: 90,
|
||||
point: [300, 112], canvasW: 1000, canvasH: 1000,
|
||||
cellCm: 5, gridPitch: GRID_PITCH, walls: EDGES,
|
||||
wallReach: 30,
|
||||
};
|
||||
const preview = resolveFurniturePlacement(input);
|
||||
const commit = resolveFurniturePlacement({ ...input });
|
||||
assert.deepEqual(preview, commit);
|
||||
assert.equal(preview.symbol, 'sofa');
|
||||
closeTo((preview.x + preview.w / 2) * 1000, 300, GRID_PITCH);
|
||||
closeTo((preview.y + preview.h / 2) * 1000, 100 + preview.h * 500);
|
||||
assert.equal(preview.angle, 0);
|
||||
});
|
||||
|
||||
test('the shared placement resolver supports Shift/free, canvas guards and unknown symbols', () => {
|
||||
const base = {
|
||||
symbol: 'sofa', widthCm: 180, depthCm: 90,
|
||||
point: [300, 112], canvasW: 1000, canvasH: 1000,
|
||||
cellCm: 5, gridPitch: GRID_PITCH, walls: EDGES,
|
||||
wallReach: 30,
|
||||
};
|
||||
const free = resolveFurniturePlacement({ ...base, free: true });
|
||||
closeTo((free.x + free.w / 2) * 1000, 300);
|
||||
closeTo((free.y + free.h / 2) * 1000, 112);
|
||||
const guarded = resolveFurniturePlacement({ ...base, point: [-1e9, -1e9], free: true });
|
||||
assert.equal(guarded.x, -5000);
|
||||
assert.equal(guarded.y, -5000);
|
||||
assert.equal(resolveFurniturePlacement({ ...base, symbol: 'future_unknown_symbol' }), null);
|
||||
});
|
||||
|
||||
// ------------------------------- the frame ----------------------------------
|
||||
|
||||
test('the corners of an unrotated box are the box; a rotated one turns about its centre', () => {
|
||||
|
||||
@@ -835,6 +835,17 @@ test('issue 159 goldens cover both palette levels and every plan-art compatibili
|
||||
new Set(['sofa', 'fridge', 'sofa_corner_right']));
|
||||
});
|
||||
|
||||
test('issue 359 has one deterministic light-theme furniture placement preview golden', () => {
|
||||
const scenario = GOLDEN_SCENARIOS.find((item) => item.id === 'furniture-placement-preview-light');
|
||||
assert.ok(scenario);
|
||||
assert.equal(scenario.mode, 'decor');
|
||||
assert.equal(scenario.theme, 'light');
|
||||
assert.equal(scenario.capture, 'stage');
|
||||
assert.deepEqual(scenario.furniturePlacementPreview, {
|
||||
symbol: 'sofa', widthCm: 180, depthCm: 90, pointer: [0.35, 0.90],
|
||||
});
|
||||
});
|
||||
|
||||
test('device dialog goldens expose the complete light-source controls at desktop and mobile widths', () => {
|
||||
const dialogs = GOLDEN_SCENARIOS.filter((scenario) => scenario.id.startsWith('device-dialog-'));
|
||||
assert.equal(dialogs.length, 3);
|
||||
|
||||
Reference in New Issue
Block a user