Skip to content
1 change: 1 addition & 0 deletions cast/src/receiver/layout/hc-lovelace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class HcLovelace extends LitElement {
}
const lovelace: Lovelace = {
config: this.lovelaceConfig,
rawConfig: this.lovelaceConfig,
editMode: false,
urlPath: this.urlPath!,
enableFullEditMode: () => undefined,
Expand Down
12 changes: 9 additions & 3 deletions cast/src/receiver/layout/hc-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,17 @@ export class HcMain extends HassElement {
}

private async _generateLovelaceConfig() {
const { generateLovelaceConfigFromHass } = await import(
"../../../../src/panels/lovelace/common/generate-lovelace-config"
const { generateLovelaceDashboardStrategy } = await import(
"../../../../src/panels/lovelace/strategies/get-strategy"
);
this._handleNewLovelaceConfig(
await generateLovelaceConfigFromHass(this.hass!)
await generateLovelaceDashboardStrategy(
{
hass: this.hass!,
narrow: false,
},
"original-states"
)
);
}

Expand Down
9 changes: 9 additions & 0 deletions src/data/lovelace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export interface LovelacePanelConfig {

export interface LovelaceConfig {
title?: string;
strategy?: {
name: string;
options?: Record<string, unknown>;
};
views: LovelaceViewConfig[];
background?: string;
}
Expand Down Expand Up @@ -77,6 +81,10 @@ export interface LovelaceViewConfig {
index?: number;
title?: string;
type?: string;
strategy?: {
name: string;
options?: Record<string, unknown>;
};
badges?: Array<string | LovelaceBadgeConfig>;
cards?: LovelaceCardConfig[];
path?: string;
Expand All @@ -94,6 +102,7 @@ export interface LovelaceViewElement extends HTMLElement {
index?: number;
cards?: Array<LovelaceCard | HuiErrorCard>;
badges?: LovelaceBadge[];
isStrategy: boolean;
setConfig(config: LovelaceViewConfig): void;
}

Expand Down
15 changes: 11 additions & 4 deletions src/panels/lovelace/cards/hui-error-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,18 @@ export class HuiErrorCard extends LitElement implements LovelaceCard {
return html``;
}

let dumped: string | undefined;

if (this._config.origConfig) {
try {
dumped = safeDump(this._config.origConfig);
} catch (err) {
dumped = `[Error dumping ${this._config.origConfig}]`;
}
}

return html`
${this._config.error}
${this._config.origConfig
? html`<pre>${safeDump(this._config.origConfig)}</pre>`
: ""}
${this._config.error}${dumped ? html`<pre>${dumped}</pre>` : ""}
`;
}

Expand Down
177 changes: 6 additions & 171 deletions src/panels/lovelace/common/generate-lovelace-config.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,16 @@
import {
HassEntities,
HassEntity,
STATE_NOT_RUNNING,
} from "home-assistant-js-websocket";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { DEFAULT_VIEW_ENTITY_ID } from "../../../common/const";
import { HassEntities, HassEntity } from "home-assistant-js-websocket";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeObjectId } from "../../../common/entity/compute_object_id";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { extractViews } from "../../../common/entity/extract_views";
import { getViewEntities } from "../../../common/entity/get_view_entities";
import { splitByGroups } from "../../../common/entity/split_by_groups";
import { compare } from "../../../common/string/compare";
import { LocalizeFunc } from "../../../common/translations/localize";
import { subscribeOne } from "../../../common/util/subscribe-one";
import {
AreaRegistryEntry,
subscribeAreaRegistry,
} from "../../../data/area_registry";
import {
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../../data/entity_registry";
import { GroupEntity } from "../../../data/group";
import type { AreaRegistryEntry } from "../../../data/area_registry";
import type { DeviceRegistryEntry } from "../../../data/device_registry";
import type { EntityRegistryEntry } from "../../../data/entity_registry";
import { domainToName } from "../../../data/integration";
import {
LovelaceCardConfig,
LovelaceConfig,
LovelaceViewConfig,
} from "../../../data/lovelace";
import { LovelaceCardConfig, LovelaceViewConfig } from "../../../data/lovelace";
import { SENSOR_DEVICE_CLASS_BATTERY } from "../../../data/sensor";
import { HomeAssistant } from "../../../types";
import {
AlarmPanelCardConfig,
EntitiesCardConfig,
Expand All @@ -57,8 +32,6 @@ const HIDE_DOMAIN = new Set([

const HIDE_PLATFORM = new Set(["mobile_app"]);

let subscribedRegistries = false;

interface SplittedByAreas {
areasWithEntities: Array<[AreaRegistryEntry, HassEntity[]]>;
otherEntities: HassEntities;
Expand Down Expand Up @@ -239,7 +212,7 @@ const computeDefaultViewStates = (
return states;
};

const generateViewConfig = (
export const generateViewConfig = (
localize: LocalizeFunc,
path: string,
title: string | undefined,
Expand Down Expand Up @@ -373,141 +346,3 @@ export const generateDefaultViewConfig = (

return config;
};

export const generateLovelaceConfigFromData = async (
hass: HomeAssistant,
areaEntries: AreaRegistryEntry[],
deviceEntries: DeviceRegistryEntry[],
entityEntries: EntityRegistryEntry[],
entities: HassEntities,
localize: LocalizeFunc
): Promise<LovelaceConfig> => {
if (hass.config.safe_mode) {
return {
title: hass.config.location_name,
views: [
{
cards: [{ type: "safe-mode" }],
},
],
};
}

const viewEntities = extractViews(entities);

const views = viewEntities.map((viewEntity: GroupEntity) => {
const states = getViewEntities(entities, viewEntity);

// In the case of a normal view, we use group order as specified in view
const groupOrders = {};
Object.keys(states).forEach((entityId, idx) => {
groupOrders[entityId] = idx;
});

return generateViewConfig(
localize,
computeObjectId(viewEntity.entity_id),
computeStateName(viewEntity),
viewEntity.attributes.icon,
states,
groupOrders
);
});

let title = hass.config.location_name;

// User can override default view. If they didn't, we will add one
// that contains all entities.
if (
viewEntities.length === 0 ||
viewEntities[0].entity_id !== DEFAULT_VIEW_ENTITY_ID
) {
views.unshift(
generateDefaultViewConfig(
areaEntries,
deviceEntries,
entityEntries,
entities,
localize
)
);

// Add map of geo locations to default view if loaded
if (isComponentLoaded(hass, "geo_location")) {
if (views[0] && views[0].cards) {
views[0].cards.push({
type: "map",
geo_location_sources: ["all"],
});
}
}

// Make sure we don't have Home as title and first tab.
if (views.length > 1 && title === "Home") {
title = "Home Assistant";
}
}

// User has no entities
if (views.length === 1 && views[0].cards!.length === 0) {
views[0].cards!.push({
type: "empty-state",
});
}

return {
title,
views,
};
};

export const generateLovelaceConfigFromHass = async (
hass: HomeAssistant,
localize?: LocalizeFunc
): Promise<LovelaceConfig> => {
if (hass.config.state === STATE_NOT_RUNNING) {
return {
title: hass.config.location_name,
views: [
{
cards: [{ type: "starting" }],
},
],
};
}

if (hass.config.safe_mode) {
return {
title: hass.config.location_name,
views: [
{
cards: [{ type: "safe-mode" }],
},
],
};
}

// We want to keep the registry subscriptions alive after generating the UI
// so that we don't serve up stale data after changing areas.
if (!subscribedRegistries) {
subscribedRegistries = true;
subscribeAreaRegistry(hass.connection, () => undefined);
subscribeDeviceRegistry(hass.connection, () => undefined);
subscribeEntityRegistry(hass.connection, () => undefined);
}

const [areaEntries, deviceEntries, entityEntries] = await Promise.all([
subscribeOne(hass.connection, subscribeAreaRegistry),
subscribeOne(hass.connection, subscribeDeviceRegistry),
subscribeOne(hass.connection, subscribeEntityRegistry),
]);

return generateLovelaceConfigFromData(
hass,
areaEntries,
deviceEntries,
entityEntries,
hass.states,
localize || hass.localize
);
};
3 changes: 2 additions & 1 deletion src/panels/lovelace/create-element/create-view-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
LovelaceViewConfig,
LovelaceViewElement,
} from "../../../data/lovelace";
import { HuiErrorCard } from "../cards/hui-error-card";
import "../views/hui-masonry-view";
import { createLovelaceElement } from "./create-element-base";

Expand All @@ -13,7 +14,7 @@ const LAZY_LOAD_LAYOUTS = {

export const createViewElement = (
config: LovelaceViewConfig
): LovelaceViewElement => {
): LovelaceViewElement | HuiErrorCard => {
return createLovelaceElement(
"view",
config,
Expand Down
33 changes: 23 additions & 10 deletions src/panels/lovelace/editor/hui-dialog-save-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ import "../../../components/ha-formfield";
import "../../../components/ha-svg-icon";
import "../../../components/ha-switch";
import "../../../components/ha-yaml-editor";
import type { LovelaceConfig } from "../../../data/lovelace";
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { expandLovelaceConfigStrategies } from "../strategies/get-strategy";
import type { SaveDialogParams } from "./show-save-config-dialog";

const EMPTY_CONFIG = { views: [] };
const EMPTY_CONFIG: LovelaceConfig = { views: [{ title: "Home" }] };
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one has bothered me for so long. When it's emtpy, it's not clear how you can add a card, because we have no view. Just adding a stub view to get creation of cards going.


@customElement("hui-dialog-save-config")
export class HuiSaveConfig extends LitElement implements HassDialog {
Expand Down Expand Up @@ -125,14 +127,17 @@ export class HuiSaveConfig extends LitElement implements HassDialog {
</div>
${this._params.mode === "storage"
? html`
<mwc-button slot="primaryAction" @click=${this.closeDialog}
>${this.hass!.localize(
"ui.common.cancel"
)}
</mwc-button>
<mwc-button
slot="primaryAction"
.label=${this.hass!.localize("ui.common.cancel")}
@click=${this.closeDialog}
></mwc-button>
<mwc-button
slot="primaryAction"
?disabled=${this._saving}
aria-label=${this.hass!.localize(
"ui.panel.lovelace.editor.save_config.save"
)}
@click=${this._saveConfig}
>
${this._saving
Expand All @@ -148,11 +153,13 @@ export class HuiSaveConfig extends LitElement implements HassDialog {
</mwc-button>
`
: html`
<mwc-button slot="primaryAction" @click=${this.closeDialog}
>${this.hass!.localize(
<mwc-button
slot="primaryAction"
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.save_config.close"
)}
</mwc-button>
@click=${this.closeDialog}
></mwc-button>
`}
</ha-dialog>
`;
Expand All @@ -177,7 +184,13 @@ export class HuiSaveConfig extends LitElement implements HassDialog {
try {
const lovelace = this._params!.lovelace;
await lovelace.saveConfig(
this._emptyConfig ? EMPTY_CONFIG : lovelace.config
this._emptyConfig
? EMPTY_CONFIG
: await expandLovelaceConfigStrategies({
config: lovelace.config,
hass: this.hass!,
narrow: this._params!.narrow,
})
);
lovelace.setEditMode(true);
this._saving = false;
Expand Down
1 change: 1 addition & 0 deletions src/panels/lovelace/editor/show-save-config-dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const dialogTag = "hui-dialog-save-config";
export interface SaveDialogParams {
lovelace: Lovelace;
mode: "yaml" | "storage";
narrow: boolean;
}

let registeredDialog = false;
Expand Down
Loading