Skip to content

Commit 3d82e7b

Browse files
authored
Avoid flickering when moving element in the dashboard (#1661)
* Add cache manager * Use cache for all templates * Rollback plugin serve
1 parent fa4e496 commit 3d82e7b

File tree

7 files changed

+204
-40
lines changed

7 files changed

+204
-40
lines changed

package-lock.json

Lines changed: 32 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"home-assistant-js-websocket": "^9.4.0",
2626
"lit": "^3.2.1",
2727
"memoize-one": "^6.0.0",
28+
"object-hash": "^3.0.0",
2829
"sortablejs": "^1.15.6",
2930
"superstruct": "^2.0.2"
3031
},
@@ -44,7 +45,7 @@
4445
"eslint": "^9.21.0",
4546
"prettier": "^3.5.2",
4647
"rollup": "^4.34.8",
47-
"rollup-plugin-serve": "^3.0.0",
48+
"rollup-plugin-serve": "^1.1.1",
4849
"typescript": "^5.7.3"
4950
}
5051
}

src/badges/template/template-badge.ts

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { customElement, property, state } from "lit/decorators.js";
1111
import { classMap } from "lit/directives/class-map.js";
1212
import { ifDefined } from "lit/directives/if-defined.js";
1313
import { styleMap } from "lit/directives/style-map.js";
14+
import hash from "object-hash/dist/object_hash";
1415
import {
1516
actionHandler,
1617
ActionHandlerEvent,
@@ -24,10 +25,17 @@ import {
2425
} from "../../ha";
2526
import { computeCssColor } from "../../ha/common/color/compute-color";
2627
import { registerCustomBadge } from "../../utils/custom-badges";
27-
import { TEMPLATE_BADGE_EDITOR_NAME, TEMPLATE_BADGE_NAME } from "./const";
28-
import { TemplateBadgeConfig } from "./template-badge-config";
2928
import { getWeatherSvgIcon } from "../../utils/icons/weather-icon";
3029
import { weatherSVGStyles } from "../../utils/weather";
30+
import { TEMPLATE_BADGE_EDITOR_NAME, TEMPLATE_BADGE_NAME } from "./const";
31+
import { TemplateBadgeConfig } from "./template-badge-config";
32+
import { CacheManager } from "../../utils/cache-manager";
33+
34+
const templateCache = new CacheManager<TemplateResults>(1000);
35+
36+
type TemplateResults = Partial<
37+
Record<TemplateKey, RenderTemplateResult | undefined>
38+
>;
3139

3240
registerCustomBadge({
3341
type: TEMPLATE_BADGE_NAME,
@@ -62,9 +70,7 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
6270

6371
@state() protected _config?: TemplateBadgeConfig;
6472

65-
@state() private _templateResults: Partial<
66-
Record<TemplateKey, RenderTemplateResult | undefined>
67-
> = {};
73+
@state() private _templateResults?: TemplateResults;
6874

6975
@state() private _unsubRenderTemplates: Map<
7076
TemplateKey,
@@ -77,9 +83,34 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
7783
}
7884

7985
public disconnectedCallback() {
86+
super.disconnectedCallback();
8087
this._tryDisconnect();
88+
89+
if (this._config && this._templateResults) {
90+
const key = this._computeCacheKey();
91+
templateCache.set(key, this._templateResults);
92+
}
8193
}
8294

95+
private _computeCacheKey() {
96+
return hash(this._config);
97+
}
98+
99+
protected willUpdate(_changedProperties: PropertyValues): void {
100+
super.willUpdate(_changedProperties);
101+
if (!this._config) {
102+
return;
103+
}
104+
105+
if (!this._templateResults) {
106+
const key = this._computeCacheKey();
107+
if (templateCache.has(key)) {
108+
this._templateResults = templateCache.get(key)!;
109+
} else {
110+
this._templateResults = {};
111+
}
112+
}
113+
}
83114
protected updated(changedProps: PropertyValues): void {
84115
super.updated(changedProps);
85116
if (!this._config || !this.hass) {
@@ -267,7 +298,7 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
267298

268299
private getValue(key: TemplateKey) {
269300
return this.isTemplate(key)
270-
? this._templateResults[key]?.result?.toString()
301+
? this._templateResults?.[key]?.result?.toString()
271302
: this._config?.[key];
272303
}
273304

src/cards/chips-card/chips/template-chip.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "lit";
1111
import { customElement, property, state } from "lit/decorators.js";
1212
import { styleMap } from "lit/directives/style-map.js";
13+
import hash from "object-hash/dist/object_hash";
1314
import {
1415
actionHandler,
1516
ActionHandlerEvent,
@@ -20,6 +21,7 @@ import {
2021
RenderTemplateResult,
2122
subscribeRenderTemplate,
2223
} from "../../../ha";
24+
import { CacheManager } from "../../../utils/cache-manager";
2325
import { computeRgbColor } from "../../../utils/colors";
2426
import { getWeatherSvgIcon } from "../../../utils/icons/weather-icon";
2527
import {
@@ -33,6 +35,12 @@ import {
3335
import { LovelaceChipEditor } from "../../../utils/lovelace/types";
3436
import { weatherSVGStyles } from "../../../utils/weather";
3537

38+
const templateCache = new CacheManager<TemplateResults>(1000);
39+
40+
type TemplateResults = Partial<
41+
Record<TemplateKey, RenderTemplateResult | undefined>
42+
>;
43+
3644
const TEMPLATE_KEYS = ["content", "icon", "icon_color", "picture"] as const;
3745
type TemplateKey = (typeof TEMPLATE_KEYS)[number];
3846

@@ -57,9 +65,7 @@ export class TemplateChip extends LitElement implements LovelaceChip {
5765

5866
@state() private _config?: TemplateChipConfig;
5967

60-
@state() private _templateResults: Partial<
61-
Record<TemplateKey, RenderTemplateResult | undefined>
62-
> = {};
68+
@state() private _templateResults?: TemplateResults;
6369

6470
@state() private _unsubRenderTemplates: Map<
6571
TemplateKey,
@@ -92,7 +98,33 @@ export class TemplateChip extends LitElement implements LovelaceChip {
9298
}
9399

94100
public disconnectedCallback() {
101+
super.disconnectedCallback();
95102
this._tryDisconnect();
103+
104+
if (this._config && this._templateResults) {
105+
const key = this._computeCacheKey();
106+
templateCache.set(key, this._templateResults);
107+
}
108+
}
109+
110+
private _computeCacheKey() {
111+
return hash(this._config);
112+
}
113+
114+
protected willUpdate(_changedProperties: PropertyValues): void {
115+
super.willUpdate(_changedProperties);
116+
if (!this._config) {
117+
return;
118+
}
119+
120+
if (!this._templateResults) {
121+
const key = this._computeCacheKey();
122+
if (templateCache.has(key)) {
123+
this._templateResults = templateCache.get(key)!;
124+
} else {
125+
this._templateResults = {};
126+
}
127+
}
96128
}
97129

98130
private _handleAction(ev: ActionHandlerEvent) {
@@ -106,7 +138,7 @@ export class TemplateChip extends LitElement implements LovelaceChip {
106138

107139
private getValue(key: TemplateKey) {
108140
return this.isTemplate(key)
109-
? this._templateResults[key]?.result?.toString()
141+
? this._templateResults?.[key]?.result?.toString()
110142
: this._config?.[key];
111143
}
112144

@@ -132,7 +164,7 @@ export class TemplateChip extends LitElement implements LovelaceChip {
132164
hasDoubleClick: hasAction(this._config.double_tap_action),
133165
})}
134166
.avatar=${picture ? (this.hass as any).hassUrl(picture) : undefined}
135-
.avatarOnly=${picture && !content}
167+
.avatarOnly=${(picture && !content) || false}
136168
>
137169
${!picture
138170
? weatherSvg

src/cards/template-card/template-card.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { customElement, property, state } from "lit/decorators.js";
1111
import { classMap } from "lit/directives/class-map.js";
1212
import { styleMap } from "lit/directives/style-map.js";
13+
import hash from "object-hash/dist/object_hash";
1314
import {
1415
actionHandler,
1516
ActionHandlerEvent,
@@ -29,6 +30,7 @@ import "../../shared/state-info";
2930
import "../../shared/state-item";
3031
import { computeAppearance } from "../../utils/appearance";
3132
import { MushroomBaseElement } from "../../utils/base-element";
33+
import { CacheManager } from "../../utils/cache-manager";
3234
import { cardStyle } from "../../utils/card-styles";
3335
import { computeRgbColor } from "../../utils/colors";
3436
import { registerCustomCard } from "../../utils/custom-cards";
@@ -37,6 +39,12 @@ import { weatherSVGStyles } from "../../utils/weather";
3739
import { TEMPLATE_CARD_EDITOR_NAME, TEMPLATE_CARD_NAME } from "./const";
3840
import { TemplateCardConfig } from "./template-card-config";
3941

42+
const templateCache = new CacheManager<TemplateResults>(1000);
43+
44+
type TemplateResults = Partial<
45+
Record<TemplateKey, RenderTemplateResult | undefined>
46+
>;
47+
4048
registerCustomCard({
4149
type: TEMPLATE_CARD_NAME,
4250
name: "Mushroom Template",
@@ -76,9 +84,7 @@ export class TemplateCard extends MushroomBaseElement implements LovelaceCard {
7684

7785
@state() private _config?: TemplateCardConfig;
7886

79-
@state() private _templateResults: Partial<
80-
Record<TemplateKey, RenderTemplateResult | undefined>
81-
> = {};
87+
@state() private _templateResults?: TemplateResults;
8288

8389
@state() private _unsubRenderTemplates: Map<
8490
TemplateKey,
@@ -164,7 +170,33 @@ export class TemplateCard extends MushroomBaseElement implements LovelaceCard {
164170
}
165171

166172
public disconnectedCallback() {
173+
super.disconnectedCallback();
167174
this._tryDisconnect();
175+
176+
if (this._config && this._templateResults) {
177+
const key = this._computeCacheKey();
178+
templateCache.set(key, this._templateResults);
179+
}
180+
}
181+
182+
private _computeCacheKey() {
183+
return hash(this._config);
184+
}
185+
186+
protected willUpdate(_changedProperties: PropertyValues): void {
187+
super.willUpdate(_changedProperties);
188+
if (!this._config) {
189+
return;
190+
}
191+
192+
if (!this._templateResults) {
193+
const key = this._computeCacheKey();
194+
if (templateCache.has(key)) {
195+
this._templateResults = templateCache.get(key)!;
196+
} else {
197+
this._templateResults = {};
198+
}
199+
}
168200
}
169201

170202
private _handleAction(ev: ActionHandlerEvent) {
@@ -178,7 +210,7 @@ export class TemplateCard extends MushroomBaseElement implements LovelaceCard {
178210

179211
private getValue(key: TemplateKey) {
180212
return this.isTemplate(key)
181-
? this._templateResults[key]?.result?.toString()
213+
? this._templateResults?.[key]?.result?.toString()
182214
: this._config?.[key];
183215
}
184216

0 commit comments

Comments
 (0)