mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 11:16:35 +00:00
parent
7cd6619a79
commit
edef4ba2f5
3
src/common/util/render-status.ts
Normal file
3
src/common/util/render-status.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const afterNextRender = (cb: () => void): void => {
|
||||||
|
requestAnimationFrame(() => setTimeout(cb, 0));
|
||||||
|
};
|
@ -84,7 +84,9 @@ export class HaStateLabelBadge extends hassLocalizeLitMixin(LitElement) {
|
|||||||
super.firstUpdated(changedProperties);
|
super.firstUpdated(changedProperties);
|
||||||
this.addEventListener("click", (ev) => {
|
this.addEventListener("click", (ev) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
fireEvent(this, "hass-more-info", { entityId: this.state!.entity_id });
|
if (this.state) {
|
||||||
|
fireEvent(this, "hass-more-info", { entityId: this.state.entity_id });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,13 +37,15 @@ export const hassLocalizeLitMixin = <T extends LitElement>(
|
|||||||
public connectedCallback(): void {
|
public connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
|
||||||
let language;
|
if (this.localize === empty) {
|
||||||
let resources;
|
let language;
|
||||||
if (this.hass) {
|
let resources;
|
||||||
language = this.hass.language;
|
if (this.hass) {
|
||||||
resources = this.hass.resources;
|
language = this.hass.language;
|
||||||
|
resources = this.hass.resources;
|
||||||
|
}
|
||||||
|
this.localize = this.__computeLocalize(language, resources);
|
||||||
}
|
}
|
||||||
this.localize = this.__computeLocalize(language, resources);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public updated(changedProperties: PropertyValues) {
|
public updated(changedProperties: PropertyValues) {
|
||||||
|
@ -19,6 +19,7 @@ import { LovelaceCardConfig } from "../../../data/lovelace";
|
|||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon";
|
import "../../../components/ha-icon";
|
||||||
import { loadRoundslider } from "../../../resources/jquery.roundslider.ondemand";
|
import { loadRoundslider } from "../../../resources/jquery.roundslider.ondemand";
|
||||||
|
import { afterNextRender } from "../../../common/util/render-status";
|
||||||
|
|
||||||
const thermostatConfig = {
|
const thermostatConfig = {
|
||||||
radius: 150,
|
radius: 150,
|
||||||
@ -68,6 +69,7 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
|
|||||||
private _config?: Config;
|
private _config?: Config;
|
||||||
private _roundSliderStyle?: TemplateResult;
|
private _roundSliderStyle?: TemplateResult;
|
||||||
private _jQuery?: any;
|
private _jQuery?: any;
|
||||||
|
private _broadCard?: boolean;
|
||||||
|
|
||||||
static get properties(): PropertyDeclarations {
|
static get properties(): PropertyDeclarations {
|
||||||
return {
|
return {
|
||||||
@ -95,7 +97,6 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
const stateObj = this.hass.states[this._config.entity] as ClimateEntity;
|
const stateObj = this.hass.states[this._config.entity] as ClimateEntity;
|
||||||
const broadCard = this.clientWidth > 390;
|
|
||||||
const mode = modeIcons[stateObj.attributes.operation_mode || ""]
|
const mode = modeIcons[stateObj.attributes.operation_mode || ""]
|
||||||
? stateObj.attributes.operation_mode!
|
? stateObj.attributes.operation_mode!
|
||||||
: "unknown-mode";
|
: "unknown-mode";
|
||||||
@ -104,8 +105,8 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
|
|||||||
<ha-card
|
<ha-card
|
||||||
class="${classMap({
|
class="${classMap({
|
||||||
[mode]: true,
|
[mode]: true,
|
||||||
large: broadCard,
|
large: this._broadCard!,
|
||||||
small: !broadCard,
|
small: !this._broadCard,
|
||||||
})}">
|
})}">
|
||||||
<div id="root">
|
<div id="root">
|
||||||
<div id="thermostat"></div>
|
<div id="thermostat"></div>
|
||||||
@ -146,8 +147,41 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
|
|||||||
return hasConfigOrEntityChanged(this, changedProps);
|
return hasConfigOrEntityChanged(this, changedProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async firstUpdated(): Promise<void> {
|
protected firstUpdated(): void {
|
||||||
|
this._initialLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues): void {
|
||||||
|
if (!this._config || !this.hass || !changedProps.has("hass")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
|
|
||||||
|
if (!oldHass || oldHass.themes !== this.hass.themes) {
|
||||||
|
applyThemesOnElement(this, this.hass.themes, this._config.theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stateObj = this.hass.states[this._config.entity] as ClimateEntity;
|
||||||
|
|
||||||
|
if (
|
||||||
|
this._jQuery &&
|
||||||
|
// If jQuery changed, we just rendered in firstUpdated
|
||||||
|
!changedProps.has("_jQuery") &&
|
||||||
|
(!oldHass || oldHass.states[this._config.entity] !== stateObj)
|
||||||
|
) {
|
||||||
|
const [sliderValue, uiValue] = this._genSliderValue(stateObj);
|
||||||
|
|
||||||
|
this._jQuery("#thermostat", this.shadowRoot).roundSlider({
|
||||||
|
value: sliderValue,
|
||||||
|
});
|
||||||
|
this._updateSetTemp(uiValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _initialLoad(): Promise<void> {
|
||||||
const loaded = await loadRoundslider();
|
const loaded = await loadRoundslider();
|
||||||
|
await new Promise((resolve) => afterNextRender(resolve));
|
||||||
|
|
||||||
this._roundSliderStyle = loaded.roundSliderStyle;
|
this._roundSliderStyle = loaded.roundSliderStyle;
|
||||||
this._jQuery = loaded.jQuery;
|
this._jQuery = loaded.jQuery;
|
||||||
@ -160,6 +194,8 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
|
|||||||
? "range"
|
? "range"
|
||||||
: "min-range";
|
: "min-range";
|
||||||
|
|
||||||
|
const [sliderValue, uiValue] = this._genSliderValue(stateObj);
|
||||||
|
this._broadCard = this.clientWidth > 390;
|
||||||
this._jQuery("#thermostat", this.shadowRoot).roundSlider({
|
this._jQuery("#thermostat", this.shadowRoot).roundSlider({
|
||||||
...thermostatConfig,
|
...thermostatConfig,
|
||||||
radius: this.clientWidth / 3,
|
radius: this.clientWidth / 3,
|
||||||
@ -168,18 +204,14 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
|
|||||||
sliderType: _sliderType,
|
sliderType: _sliderType,
|
||||||
change: (value) => this._setTemperature(value),
|
change: (value) => this._setTemperature(value),
|
||||||
drag: (value) => this._dragEvent(value),
|
drag: (value) => this._dragEvent(value),
|
||||||
|
value: sliderValue,
|
||||||
});
|
});
|
||||||
|
this._updateSetTemp(uiValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues): void {
|
private _genSliderValue(stateObj: ClimateEntity): [string | number, string] {
|
||||||
if (!this._config || !this.hass || !this._jQuery) {
|
let sliderValue: string | number;
|
||||||
return;
|
let uiValue: string;
|
||||||
}
|
|
||||||
|
|
||||||
const stateObj = this.hass.states[this._config.entity] as ClimateEntity;
|
|
||||||
|
|
||||||
let sliderValue;
|
|
||||||
let uiValue;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
stateObj.attributes.target_temp_low &&
|
stateObj.attributes.target_temp_low &&
|
||||||
@ -193,18 +225,11 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
|
|||||||
String(stateObj.attributes.target_temp_high),
|
String(stateObj.attributes.target_temp_high),
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
sliderValue = uiValue = stateObj.attributes.temperature;
|
sliderValue = stateObj.attributes.temperature;
|
||||||
|
uiValue = "" + stateObj.attributes.temperature;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._jQuery("#thermostat", this.shadowRoot).roundSlider({
|
return [sliderValue, uiValue];
|
||||||
value: sliderValue,
|
|
||||||
});
|
|
||||||
this.shadowRoot!.querySelector("#set-temperature")!.innerHTML = uiValue;
|
|
||||||
|
|
||||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
|
||||||
if (!oldHass || oldHass.themes !== this.hass.themes) {
|
|
||||||
applyThemesOnElement(this, this.hass.themes, this._config.theme);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStyle(): TemplateResult {
|
private renderStyle(): TemplateResult {
|
||||||
@ -383,10 +408,12 @@ export class HuiThermostatCard extends hassLocalizeLitMixin(LitElement)
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _updateSetTemp(value: string): void {
|
||||||
|
this.shadowRoot!.querySelector("#set-temperature")!.innerHTML = value;
|
||||||
|
}
|
||||||
|
|
||||||
private _dragEvent(e): void {
|
private _dragEvent(e): void {
|
||||||
this.shadowRoot!.querySelector("#set-temperature")!.innerHTML = formatTemp(
|
this._updateSetTemp(formatTemp(String(e.value).split(",")));
|
||||||
String(e.value).split(",")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setTemperature(e): void {
|
private _setTemperature(e): void {
|
||||||
|
@ -44,6 +44,7 @@ import { HUIView } from "./hui-view";
|
|||||||
import createCardElement from "./common/create-card-element";
|
import createCardElement from "./common/create-card-element";
|
||||||
import { showEditViewDialog } from "./editor/view-editor/show-edit-view-dialog";
|
import { showEditViewDialog } from "./editor/view-editor/show-edit-view-dialog";
|
||||||
import { Lovelace } from "./types";
|
import { Lovelace } from "./types";
|
||||||
|
import { afterNextRender } from "../../common/util/render-status";
|
||||||
|
|
||||||
// CSS and JS should only be imported once. Modules and HTML are safe.
|
// CSS and JS should only be imported once. Modules and HTML are safe.
|
||||||
const CSS_CACHE = {};
|
const CSS_CACHE = {};
|
||||||
@ -57,10 +58,11 @@ class HUIRoot extends hassLocalizeLitMixin(LitElement) {
|
|||||||
public columns?: number;
|
public columns?: number;
|
||||||
public route?: { path: string; prefix: string };
|
public route?: { path: string; prefix: string };
|
||||||
private _routeData?: { view: string };
|
private _routeData?: { view: string };
|
||||||
private _curView: number | "unused";
|
private _curView?: number | "unused";
|
||||||
private notificationsOpen?: boolean;
|
private notificationsOpen?: boolean;
|
||||||
private _persistentNotifications?: Notification[];
|
private _persistentNotifications?: Notification[];
|
||||||
private _haStyle?: DocumentFragment;
|
private _haStyle?: DocumentFragment;
|
||||||
|
private _viewCache?: { [viewId: string]: HUIView };
|
||||||
|
|
||||||
private _debouncedConfigChanged: () => void;
|
private _debouncedConfigChanged: () => void;
|
||||||
private _unsubNotifications?: () => void;
|
private _unsubNotifications?: () => void;
|
||||||
@ -82,9 +84,8 @@ class HUIRoot extends hassLocalizeLitMixin(LitElement) {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this._curView = 0;
|
|
||||||
this._debouncedConfigChanged = debounce(
|
this._debouncedConfigChanged = debounce(
|
||||||
() => this._selectView(this._curView),
|
() => this._selectView(this._curView, true),
|
||||||
100
|
100
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -349,6 +350,9 @@ class HUIRoot extends hassLocalizeLitMixin(LitElement) {
|
|||||||
huiView.hass = this.hass;
|
huiView.hass = this.hass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let newSelectView;
|
||||||
|
let force = false;
|
||||||
|
|
||||||
if (changedProperties.has("route")) {
|
if (changedProperties.has("route")) {
|
||||||
const views = this.config && this.config.views;
|
const views = this.config && this.config.views;
|
||||||
if (
|
if (
|
||||||
@ -367,9 +371,7 @@ class HUIRoot extends hassLocalizeLitMixin(LitElement) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (index !== this._curView) {
|
newSelectView = index;
|
||||||
this._selectView(index);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,15 +381,20 @@ class HUIRoot extends hassLocalizeLitMixin(LitElement) {
|
|||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
if (!oldLovelace || oldLovelace.config !== this.lovelace!.config) {
|
if (!oldLovelace || oldLovelace.config !== this.lovelace!.config) {
|
||||||
|
this._viewCache = {};
|
||||||
this._loadResources(this.lovelace!.config.resources || []);
|
this._loadResources(this.lovelace!.config.resources || []);
|
||||||
// On config change, recreate the view from scratch.
|
// On config change, recreate the view from scratch.
|
||||||
this._selectView(this._curView);
|
force = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!oldLovelace || oldLovelace.editMode !== this.lovelace!.editMode) {
|
if (!oldLovelace || oldLovelace.editMode !== this.lovelace!.editMode) {
|
||||||
this._editModeChanged();
|
force = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newSelectView !== undefined || force) {
|
||||||
|
this._selectView(newSelectView, force);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _notifications() {
|
private get _notifications() {
|
||||||
@ -442,7 +449,7 @@ class HUIRoot extends hassLocalizeLitMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleUnusedEntities(): void {
|
private _handleUnusedEntities(): void {
|
||||||
this._selectView("unused");
|
this._selectView("unused", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _deselect(ev): void {
|
private _deselect(ev): void {
|
||||||
@ -471,10 +478,6 @@ class HUIRoot extends hassLocalizeLitMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _editModeChanged(): void {
|
|
||||||
this._selectView(this._curView);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _editView() {
|
private _editView() {
|
||||||
showEditViewDialog(this, {
|
showEditViewDialog(this, {
|
||||||
lovelace: this.lovelace!,
|
lovelace: this.lovelace!,
|
||||||
@ -501,45 +504,61 @@ class HUIRoot extends hassLocalizeLitMixin(LitElement) {
|
|||||||
scrollToTarget(this, this._layout.header.scrollTarget);
|
scrollToTarget(this, this._layout.header.scrollTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _selectView(viewIndex: HUIRoot["_curView"]): void {
|
private async _selectView(
|
||||||
|
viewIndex: HUIRoot["_curView"],
|
||||||
|
force: boolean
|
||||||
|
): Promise<void> {
|
||||||
|
if (!force && this._curView === viewIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
viewIndex = viewIndex === undefined ? 0 : viewIndex;
|
||||||
|
|
||||||
this._curView = viewIndex;
|
this._curView = viewIndex;
|
||||||
|
|
||||||
// Recreate a new element to clear the applied themes.
|
// Recreate a new element to clear the applied themes.
|
||||||
const root = this._view;
|
const root = this._view;
|
||||||
|
|
||||||
if (root.lastChild) {
|
if (root.lastChild) {
|
||||||
root.removeChild(root.lastChild);
|
root.removeChild(root.lastChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
let view;
|
|
||||||
let background = this.config.background || "";
|
|
||||||
|
|
||||||
if (viewIndex === "unused") {
|
if (viewIndex === "unused") {
|
||||||
view = document.createElement("hui-unused-entities");
|
const unusedEntities = document.createElement("hui-unused-entities");
|
||||||
view.setConfig(this.config);
|
unusedEntities.setConfig(this.config);
|
||||||
|
root.style.background = this.config.background || "";
|
||||||
|
root.appendChild(unusedEntities);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let view;
|
||||||
|
const viewConfig = this.config.views[viewIndex];
|
||||||
|
|
||||||
|
if (!viewConfig) {
|
||||||
|
this._editModeEnable();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!force && this._viewCache![viewIndex]) {
|
||||||
|
view = this._viewCache![viewIndex];
|
||||||
} else {
|
} else {
|
||||||
const viewConfig = this.config.views[this._curView];
|
await new Promise((resolve) => afterNextRender(resolve));
|
||||||
if (!viewConfig) {
|
|
||||||
this._editModeEnable();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (viewConfig.panel && viewConfig.cards && viewConfig.cards.length > 0) {
|
if (viewConfig.panel && viewConfig.cards && viewConfig.cards.length > 0) {
|
||||||
view = createCardElement(viewConfig.cards[0]);
|
view = createCardElement(viewConfig.cards[0]);
|
||||||
view.isPanel = true;
|
view.isPanel = true;
|
||||||
} else {
|
} else {
|
||||||
view = document.createElement("hui-view");
|
view = document.createElement("hui-view");
|
||||||
view.lovelace = this.lovelace;
|
view.lovelace = this.lovelace;
|
||||||
view.config = viewConfig;
|
|
||||||
view.columns = this.columns;
|
view.columns = this.columns;
|
||||||
view.index = viewIndex;
|
view.index = viewIndex;
|
||||||
}
|
}
|
||||||
if (viewConfig.background) {
|
this._viewCache![viewIndex] = view;
|
||||||
background = viewConfig.background;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._view.style.background = background;
|
|
||||||
|
|
||||||
view.hass = this.hass;
|
view.hass = this.hass;
|
||||||
|
root.style.background =
|
||||||
|
viewConfig.background || this.config.background || "";
|
||||||
root.appendChild(view);
|
root.appendChild(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,24 @@ import { showEditCardDialog } from "./editor/card-editor/show-edit-card-dialog";
|
|||||||
|
|
||||||
let editCodeLoaded = false;
|
let editCodeLoaded = false;
|
||||||
|
|
||||||
|
// Find column with < 5 entities, else column with lowest count
|
||||||
|
const getColumnIndex = (columnEntityCount: number[], size: number) => {
|
||||||
|
let minIndex = 0;
|
||||||
|
for (let i = 0; i < columnEntityCount.length; i++) {
|
||||||
|
if (columnEntityCount[i] < 5) {
|
||||||
|
minIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (columnEntityCount[i] < columnEntityCount[minIndex]) {
|
||||||
|
minIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
columnEntityCount[minIndex] += size;
|
||||||
|
|
||||||
|
return minIndex;
|
||||||
|
};
|
||||||
|
|
||||||
export class HUIView extends hassLocalizeLitMixin(LitElement) {
|
export class HUIView extends hassLocalizeLitMixin(LitElement) {
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
public lovelace?: Lovelace;
|
public lovelace?: Lovelace;
|
||||||
@ -245,28 +263,12 @@ export class HUIView extends hassLocalizeLitMixin(LitElement) {
|
|||||||
columnEntityCount.push(0);
|
columnEntityCount.push(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find column with < 5 entities, else column with lowest count
|
|
||||||
function getColumnIndex(size) {
|
|
||||||
let minIndex = 0;
|
|
||||||
for (let i = 0; i < columnEntityCount.length; i++) {
|
|
||||||
if (columnEntityCount[i] < 5) {
|
|
||||||
minIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (columnEntityCount[i] < columnEntityCount[minIndex]) {
|
|
||||||
minIndex = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
columnEntityCount[minIndex] += size;
|
|
||||||
|
|
||||||
return minIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
elements.forEach((el, index) => {
|
elements.forEach((el, index) => {
|
||||||
const cardSize = computeCardSize(el);
|
const cardSize = computeCardSize(el);
|
||||||
// Element to append might be the wrapped card when we're editing.
|
// Element to append might be the wrapped card when we're editing.
|
||||||
columns[getColumnIndex(cardSize)].push(elementsToAppend[index]);
|
columns[getColumnIndex(columnEntityCount, cardSize)].push(
|
||||||
|
elementsToAppend[index]
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove empty columns
|
// Remove empty columns
|
||||||
|
Loading…
x
Reference in New Issue
Block a user