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