mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-19 19:07:23 +00:00
Compare commits
25 Commits
dev
...
20250328.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fe7e8e17ae | ||
![]() |
2161357226 | ||
![]() |
e8e65a4293 | ||
![]() |
724adab2d6 | ||
![]() |
345ad6c9c5 | ||
![]() |
a88d066d7e | ||
![]() |
a8e5c8482b | ||
![]() |
d5ff8ab1e1 | ||
![]() |
e765cc10fb | ||
![]() |
916dec101f | ||
![]() |
909fc119b7 | ||
![]() |
8751dc46f4 | ||
![]() |
118c25d25f | ||
![]() |
ae5427a75e | ||
![]() |
3b6e267fb5 | ||
![]() |
1770a51303 | ||
![]() |
534df3d378 | ||
![]() |
23229b3e3b | ||
![]() |
94ee99160b | ||
![]() |
b009d71e8f | ||
![]() |
2ab8209622 | ||
![]() |
ed2940edc3 | ||
![]() |
e2b9a06242 | ||
![]() |
a7acee0438 | ||
![]() |
1208af510c |
@ -309,7 +309,7 @@ export class HcMain extends HassElement {
|
||||
"../../../../src/panels/lovelace/strategies/get-strategy"
|
||||
);
|
||||
const config = await generateLovelaceDashboardStrategy(
|
||||
rawConfig.strategy,
|
||||
rawConfig,
|
||||
this.hass!
|
||||
);
|
||||
this._handleNewLovelaceConfig(config);
|
||||
@ -351,10 +351,7 @@ export class HcMain extends HassElement {
|
||||
"../../../../src/panels/lovelace/strategies/get-strategy"
|
||||
);
|
||||
this._handleNewLovelaceConfig(
|
||||
await generateLovelaceDashboardStrategy(
|
||||
DEFAULT_CONFIG.strategy,
|
||||
this.hass!
|
||||
)
|
||||
await generateLovelaceDashboardStrategy(DEFAULT_CONFIG, this.hass!)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20250326.0"
|
||||
version = "20250328.0"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*"]
|
||||
description = "The Home Assistant frontend"
|
||||
|
@ -33,7 +33,14 @@ export const computeEntityEntryName = (
|
||||
const device = entry.device_id ? hass.devices[entry.device_id] : undefined;
|
||||
|
||||
if (!device) {
|
||||
return name;
|
||||
if (name) {
|
||||
return name;
|
||||
}
|
||||
const stateObj = hass.states[entry.entity_id] as HassEntity | undefined;
|
||||
if (stateObj) {
|
||||
return computeStateName(stateObj);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const deviceName = computeDeviceName(device);
|
||||
|
@ -1,7 +1,11 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { AreaRegistryEntry } from "../../data/area_registry";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
ExtEntityRegistryEntry,
|
||||
} from "../../data/entity_registry";
|
||||
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
@ -19,6 +23,23 @@ export const getEntityContext = (
|
||||
| EntityRegistryDisplayEntry
|
||||
| undefined;
|
||||
|
||||
if (!entry) {
|
||||
return {
|
||||
device: null,
|
||||
area: null,
|
||||
floor: null,
|
||||
};
|
||||
}
|
||||
return getEntityEntryContext(entry, hass);
|
||||
};
|
||||
|
||||
export const getEntityEntryContext = (
|
||||
entry:
|
||||
| EntityRegistryDisplayEntry
|
||||
| EntityRegistryEntry
|
||||
| ExtEntityRegistryEntry,
|
||||
hass: HomeAssistant
|
||||
): EntityContext => {
|
||||
const deviceId = entry?.device_id;
|
||||
const device = deviceId ? hass.devices[deviceId] : null;
|
||||
const areaId = entry?.area_id || device?.area_id;
|
||||
|
@ -211,36 +211,12 @@ export class HaRelatedItems extends LitElement {
|
||||
)}
|
||||
</mwc-list>`
|
||||
: nothing}
|
||||
${this._related.device
|
||||
? html`<h3>
|
||||
${this.hass.localize("ui.components.related-items.device")}
|
||||
</h3>
|
||||
${this._related.device.map((relatedDeviceId) => {
|
||||
const device = this.hass.devices[relatedDeviceId];
|
||||
if (!device) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<a href="/config/devices/device/${relatedDeviceId}">
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon
|
||||
.path=${mdiDevices}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${device.name_by_user || device.name}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`;
|
||||
})} </mwc-list>
|
||||
`
|
||||
: nothing}
|
||||
${this._related.area
|
||||
? html`<h3>
|
||||
${this.hass.localize("ui.components.related-items.area")}
|
||||
</h3>
|
||||
<mwc-list
|
||||
>${this._related.area.map((relatedAreaId) => {
|
||||
<mwc-list>
|
||||
${this._related.area.map((relatedAreaId) => {
|
||||
const area = this.hass.areas[relatedAreaId];
|
||||
if (!area) {
|
||||
return nothing;
|
||||
@ -268,8 +244,33 @@ export class HaRelatedItems extends LitElement {
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`;
|
||||
})}</mwc-list
|
||||
>`
|
||||
})}
|
||||
</mwc-list>`
|
||||
: nothing}
|
||||
${this._related.device
|
||||
? html`<h3>
|
||||
${this.hass.localize("ui.components.related-items.device")}
|
||||
</h3>
|
||||
<mwc-list>
|
||||
${this._related.device.map((relatedDeviceId) => {
|
||||
const device = this.hass.devices[relatedDeviceId];
|
||||
if (!device) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<a href="/config/devices/device/${relatedDeviceId}">
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon
|
||||
.path=${mdiDevices}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${device.name_by_user || device.name}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`;
|
||||
})}
|
||||
</mwc-list>`
|
||||
: nothing}
|
||||
${this._related.entity
|
||||
? html`
|
||||
|
@ -69,11 +69,14 @@ export class HaTemplateSelector extends LitElement {
|
||||
}
|
||||
|
||||
private _handleChange(ev) {
|
||||
const value = ev.target.value;
|
||||
let value = ev.target.value;
|
||||
if (this.value === value) {
|
||||
return;
|
||||
}
|
||||
this.warn = WARNING_STRINGS.find((str) => value.includes(str));
|
||||
if (value === "" && !this.required) {
|
||||
value = undefined;
|
||||
}
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,6 @@ export class HaTileInfo extends LitElement {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
height: 36px;
|
||||
}
|
||||
span {
|
||||
text-overflow: ellipsis;
|
||||
|
@ -49,9 +49,12 @@ export const testAssistSatelliteConnection = (
|
||||
export const assistSatelliteAnnounce = (
|
||||
hass: HomeAssistant,
|
||||
entity_id: string,
|
||||
message: string
|
||||
) =>
|
||||
hass.callService("assist_satellite", "announce", { message }, { entity_id });
|
||||
args: {
|
||||
message?: string;
|
||||
media_id?: string;
|
||||
preannounce_media_id?: string | null;
|
||||
}
|
||||
) => hass.callService("assist_satellite", "announce", args, { entity_id });
|
||||
|
||||
export const fetchAssistSatelliteConfiguration = (
|
||||
hass: HomeAssistant,
|
||||
|
@ -38,7 +38,7 @@ export interface Statistic {
|
||||
|
||||
export enum StatisticMeanType {
|
||||
NONE = 0,
|
||||
ARIMETHIC = 1,
|
||||
ARITHMETIC = 1,
|
||||
CIRCULAR = 2,
|
||||
}
|
||||
|
||||
|
@ -21,8 +21,10 @@ import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
import { computeAreaName } from "../../common/entity/compute_area_name";
|
||||
import { computeDeviceName } from "../../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeEntityName } from "../../common/entity/compute_entity_name";
|
||||
import { getEntityContext } from "../../common/entity/get_entity_context";
|
||||
import {
|
||||
computeEntityEntryName,
|
||||
computeEntityName,
|
||||
} from "../../common/entity/compute_entity_name";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../common/mwc/handle-request-selected-event";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import "../../components/ha-button-menu";
|
||||
@ -56,6 +58,10 @@ import "./ha-more-info-history-and-logbook";
|
||||
import "./ha-more-info-info";
|
||||
import "./ha-more-info-settings";
|
||||
import "./more-info-content";
|
||||
import {
|
||||
getEntityContext,
|
||||
getEntityEntryContext,
|
||||
} from "../../common/entity/get_entity_context";
|
||||
|
||||
export interface MoreInfoDialogParams {
|
||||
entityId: string | null;
|
||||
@ -270,6 +276,11 @@ export class MoreInfoDialog extends LitElement {
|
||||
this._setView("related");
|
||||
}
|
||||
|
||||
private _breadcrumbClick(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
this._setView("related");
|
||||
}
|
||||
|
||||
private async _loadNumericDeviceClasses() {
|
||||
const deviceClasses = await getSensorNumericDeviceClasses(this.hass);
|
||||
this._sensorNumericDeviceClasses = deviceClasses.numeric_device_classes;
|
||||
@ -293,11 +304,18 @@ export class MoreInfoDialog extends LitElement {
|
||||
this._initialView !== DEFAULT_VIEW && !this._childView;
|
||||
const showCloseIcon = isDefaultView || isSpecificInitialView;
|
||||
|
||||
const context = stateObj ? getEntityContext(stateObj, this.hass) : null;
|
||||
const context = stateObj
|
||||
? getEntityContext(stateObj, this.hass)
|
||||
: this._entry
|
||||
? getEntityEntryContext(this._entry, this.hass)
|
||||
: undefined;
|
||||
|
||||
const entityName = stateObj
|
||||
? computeEntityName(stateObj, this.hass)
|
||||
: undefined;
|
||||
: this._entry
|
||||
? computeEntityEntryName(this._entry, this.hass)
|
||||
: entityId;
|
||||
|
||||
const deviceName = context?.device
|
||||
? computeDeviceName(context.device)
|
||||
: undefined;
|
||||
@ -306,7 +324,7 @@ export class MoreInfoDialog extends LitElement {
|
||||
const breadcrumb = [areaName, deviceName, entityName].filter(
|
||||
(v): v is string => Boolean(v)
|
||||
);
|
||||
const title = this._childView?.viewTitle || breadcrumb.pop();
|
||||
const title = this._childView?.viewTitle || breadcrumb.pop() || entityId;
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
@ -337,17 +355,16 @@ export class MoreInfoDialog extends LitElement {
|
||||
)}
|
||||
></ha-icon-button-prev>
|
||||
`}
|
||||
<span
|
||||
slot="title"
|
||||
.title=${title}
|
||||
@click=${this._enlarge}
|
||||
class="title"
|
||||
>
|
||||
<span slot="title" @click=${this._enlarge} class="title">
|
||||
${breadcrumb.length > 0
|
||||
? html`
|
||||
<p class="breadcrumb">
|
||||
<button
|
||||
class="breadcrumb"
|
||||
@click=${this._breadcrumbClick}
|
||||
aria-label=${breadcrumb.join(" > ")}
|
||||
>
|
||||
${join(breadcrumb, html`<ha-icon-next></ha-icon-next>`)}
|
||||
</p>
|
||||
</button>
|
||||
`
|
||||
: nothing}
|
||||
<p class="main">${title}</p>
|
||||
@ -643,6 +660,7 @@ export class MoreInfoDialog extends LitElement {
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.title p {
|
||||
@ -663,11 +681,22 @@ export class MoreInfoDialog extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
margin-top: -6px;
|
||||
--mdc-icon-size: 16px;
|
||||
padding: 4px;
|
||||
margin: -4px;
|
||||
margin-top: -10px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
display: inline-flex;
|
||||
border-radius: 6px;
|
||||
transition: background-color 180ms ease-in-out;
|
||||
}
|
||||
|
||||
.title .breadcrumb {
|
||||
--mdc-icon-size: 16px;
|
||||
.title .breadcrumb:focus-visible,
|
||||
.title .breadcrumb:hover {
|
||||
background-color: rgba(var(--rgb-secondary-text-color), 0.08);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -243,7 +243,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
|
||||
|
||||
private readonly _ttsHostName = "core-piper";
|
||||
|
||||
private readonly _ttsPort = "10200";
|
||||
private readonly _ttsPort = 10200;
|
||||
|
||||
private get _sttProviderName() {
|
||||
return this.localOption === "focused_local"
|
||||
@ -263,7 +263,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
|
||||
: "core-whisper";
|
||||
}
|
||||
|
||||
private readonly _sttPort = "10300";
|
||||
private readonly _sttPort = 10300;
|
||||
|
||||
private async _findLocalEntities() {
|
||||
const wyomingEntities = Object.values(this.hass.entities).filter(
|
||||
@ -325,14 +325,16 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
|
||||
(flow) =>
|
||||
flow.handler === "wyoming" &&
|
||||
flow.context.source === "hassio" &&
|
||||
(flow.context.configuration_url.includes(
|
||||
type === "tts" ? this._ttsHostName : this._sttHostName
|
||||
) ||
|
||||
flow.context.title_placeholders.title
|
||||
.toLowerCase()
|
||||
.includes(
|
||||
type === "tts" ? this._ttsProviderName : this._sttProviderName
|
||||
))
|
||||
((flow.context.configuration_url &&
|
||||
flow.context.configuration_url.includes(
|
||||
type === "tts" ? this._ttsAddonName : this._sttAddonName
|
||||
)) ||
|
||||
(flow.context.title_placeholders.name &&
|
||||
flow.context.title_placeholders.name
|
||||
.toLowerCase()
|
||||
.includes(
|
||||
type === "tts" ? this._ttsProviderName : this._sttProviderName
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
} from "../../data/assist_pipeline";
|
||||
import type { AssistSatelliteConfiguration } from "../../data/assist_satellite";
|
||||
import { fetchCloudStatus } from "../../data/cloud";
|
||||
import type { LanguageScores } from "../../data/conversation";
|
||||
import type { LanguageScore, LanguageScores } from "../../data/conversation";
|
||||
import { getLanguageScores, listAgents } from "../../data/conversation";
|
||||
import { listSTTEngines } from "../../data/stt";
|
||||
import { listTTSEngines, listTTSVoices } from "../../data/tts";
|
||||
@ -26,6 +26,12 @@ import { documentationUrl } from "../../util/documentation-url";
|
||||
|
||||
const OPTIONS = ["cloud", "focused_local", "full_local"] as const;
|
||||
|
||||
const EMPTY_SCORE: LanguageScore = {
|
||||
cloud: 0,
|
||||
focused_local: 0,
|
||||
full_local: 0,
|
||||
};
|
||||
|
||||
@customElement("ha-voice-assistant-setup-step-pipeline")
|
||||
export class HaVoiceAssistantSetupStepPipeline extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@ -61,12 +67,12 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement {
|
||||
this._languageScores
|
||||
) {
|
||||
const lang = this.language;
|
||||
if (this._value && this._languageScores[lang][this._value] === 0) {
|
||||
if (this._value && this._languageScores[lang]?.[this._value] === 0) {
|
||||
this._value = undefined;
|
||||
}
|
||||
if (!this._value) {
|
||||
this._value = this._getOptions(
|
||||
this._languageScores[lang],
|
||||
this._languageScores[lang] || EMPTY_SCORE,
|
||||
this.hass.localize
|
||||
).supportedOptions[0]?.value as
|
||||
| "cloud"
|
||||
@ -147,12 +153,9 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
const score = this._languageScores[this.language];
|
||||
const score = this._languageScores[this.language] || EMPTY_SCORE;
|
||||
|
||||
const options = this._getOptions(
|
||||
score || { cloud: 3, focused_local: 0, full_local: 0 },
|
||||
this.hass.localize
|
||||
);
|
||||
const options = this._getOptions(score, this.hass.localize);
|
||||
|
||||
const performance = !this._value
|
||||
? ""
|
||||
@ -162,11 +165,11 @@ export class HaVoiceAssistantSetupStepPipeline extends LitElement {
|
||||
|
||||
const commands = !this._value
|
||||
? ""
|
||||
: score?.[this._value] > 2
|
||||
: score[this._value] > 2
|
||||
? "high"
|
||||
: score?.[this._value] > 1
|
||||
: score[this._value] > 1
|
||||
? "ready"
|
||||
: score?.[this._value] > 0
|
||||
: score[this._value] > 0
|
||||
? "low"
|
||||
: "";
|
||||
|
||||
|
@ -246,7 +246,10 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
|
||||
if (!this.assistEntityId) {
|
||||
return;
|
||||
}
|
||||
await assistSatelliteAnnounce(this.hass, this.assistEntityId, message);
|
||||
await assistSatelliteAnnounce(this.hass, this.assistEntityId, {
|
||||
message,
|
||||
preannounce_media_id: null,
|
||||
});
|
||||
}
|
||||
|
||||
private _testWakeWord() {
|
||||
|
@ -152,12 +152,14 @@ export class EnergyDeviceSettings extends LitElement {
|
||||
device_consumptions: this.preferences
|
||||
.device_consumption as DeviceConsumptionEnergyPreference[],
|
||||
saveCallback: async (newDevice) => {
|
||||
await this._savePreferences({
|
||||
const newPrefs = {
|
||||
...this.preferences,
|
||||
device_consumption: this.preferences.device_consumption.map((d) =>
|
||||
d === origDevice ? newDevice : d
|
||||
),
|
||||
});
|
||||
};
|
||||
this._sanitizeParents(newPrefs);
|
||||
await this._savePreferences(newPrefs);
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -177,6 +179,15 @@ export class EnergyDeviceSettings extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _sanitizeParents(prefs: EnergyPreferences) {
|
||||
const statIds = prefs.device_consumption.map((d) => d.stat_consumption);
|
||||
prefs.device_consumption.forEach((d) => {
|
||||
if (d.included_in_stat && !statIds.includes(d.included_in_stat)) {
|
||||
delete d.included_in_stat;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async _deleteDevice(ev) {
|
||||
const deviceToDelete: DeviceConsumptionEnergyPreference =
|
||||
ev.currentTarget.device;
|
||||
@ -196,14 +207,7 @@ export class EnergyDeviceSettings extends LitElement {
|
||||
(device) => device !== deviceToDelete
|
||||
),
|
||||
};
|
||||
newPrefs.device_consumption.forEach((d, idx) => {
|
||||
if (d.included_in_stat === deviceToDelete.stat_consumption) {
|
||||
newPrefs.device_consumption[idx] = {
|
||||
...newPrefs.device_consumption[idx],
|
||||
};
|
||||
delete newPrefs.device_consumption[idx].included_in_stat;
|
||||
}
|
||||
});
|
||||
this._sanitizeParents(newPrefs);
|
||||
await this._savePreferences(newPrefs);
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, { title: `Failed to save config: ${err.message}` });
|
||||
|
@ -74,6 +74,7 @@ export class DialogEnergyDeviceSettings
|
||||
this._possibleParents = this._params.device_consumptions.filter(
|
||||
(d) =>
|
||||
d.stat_consumption !== this._device!.stat_consumption &&
|
||||
d.stat_consumption !== this._params?.device?.stat_consumption &&
|
||||
!children.includes(d.stat_consumption)
|
||||
);
|
||||
}
|
||||
@ -160,18 +161,26 @@ export class DialogEnergyDeviceSettings
|
||||
naturalMenuWidth
|
||||
clearable
|
||||
>
|
||||
${this._possibleParents.map(
|
||||
(stat) => html`
|
||||
<mwc-list-item .value=${stat.stat_consumption}
|
||||
>${stat.name ||
|
||||
getStatisticLabel(
|
||||
this.hass,
|
||||
stat.stat_consumption,
|
||||
this._params?.statsMetadata?.[stat.stat_consumption]
|
||||
)}</mwc-list-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
${!this._possibleParents.length
|
||||
? html`
|
||||
<mwc-list-item disabled value="-"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.energy.device_consumption.dialog.no_upstream_devices"
|
||||
)}</mwc-list-item
|
||||
>
|
||||
`
|
||||
: this._possibleParents.map(
|
||||
(stat) => html`
|
||||
<mwc-list-item .value=${stat.stat_consumption}
|
||||
>${stat.name ||
|
||||
getStatisticLabel(
|
||||
this.hass,
|
||||
stat.stat_consumption,
|
||||
this._params?.statsMetadata?.[stat.stat_consumption]
|
||||
)}</mwc-list-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
|
||||
<mwc-button @click=${this.closeDialog} slot="secondaryAction">
|
||||
|
@ -120,7 +120,7 @@ class HaConfigIntegrations extends SubscribeMixin(HassRouterPage) {
|
||||
const existingEntries = fullUpdate ? [] : this._configEntries;
|
||||
this._configEntries = [...existingEntries!, ...newEntries];
|
||||
},
|
||||
{ type: ["device", "hub", "service"] }
|
||||
{ type: ["device", "hub", "service", "hardware"] }
|
||||
),
|
||||
subscribeConfigFlowInProgress(this.hass, async (flowsInProgress) => {
|
||||
const integrations = new Set<string>();
|
||||
|
@ -78,9 +78,6 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
|
||||
return {
|
||||
type: "button",
|
||||
tap_action: {
|
||||
action: "toggle",
|
||||
},
|
||||
entity: foundEntities[0] || "",
|
||||
};
|
||||
}
|
||||
@ -164,6 +161,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
action: getEntityDefaultButtonAction(config.entity),
|
||||
},
|
||||
hold_action: { action: "more-info" },
|
||||
double_tap_action: { action: "none" },
|
||||
show_icon: true,
|
||||
show_name: true,
|
||||
state_color: true,
|
||||
|
@ -65,7 +65,9 @@ export class HuiClockCard extends LitElement implements LovelaceCard {
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hourCycle: useAmPm(locale) ? "h12" : "h23",
|
||||
timeZone: resolveTimeZone(locale.time_zone, this.hass.config?.time_zone),
|
||||
timeZone:
|
||||
this._config?.time_zone ||
|
||||
resolveTimeZone(locale.time_zone, this.hass.config?.time_zone),
|
||||
});
|
||||
|
||||
this._tick();
|
||||
@ -79,7 +81,7 @@ export class HuiClockCard extends LitElement implements LovelaceCard {
|
||||
public getGridOptions(): LovelaceGridOptions {
|
||||
if (this._config?.clock_size === "medium") {
|
||||
return {
|
||||
min_rows: 1,
|
||||
min_rows: this._config?.title ? 2 : 1,
|
||||
rows: 2,
|
||||
max_rows: 4,
|
||||
min_columns: 4,
|
||||
@ -101,7 +103,7 @@ export class HuiClockCard extends LitElement implements LovelaceCard {
|
||||
min_rows: 1,
|
||||
rows: 1,
|
||||
max_rows: 4,
|
||||
min_columns: 4,
|
||||
min_columns: 3,
|
||||
columns: 6,
|
||||
};
|
||||
}
|
||||
@ -160,6 +162,9 @@ export class HuiClockCard extends LitElement implements LovelaceCard {
|
||||
? `size-${this._config.clock_size}`
|
||||
: ""}"
|
||||
>
|
||||
${this._config.title !== undefined
|
||||
? html`<div class="time-title">${this._config.title}</div>`
|
||||
: nothing}
|
||||
<div class="time-parts">
|
||||
<div class="time-part hour">${this._timeHour}</div>
|
||||
<div class="time-part minute">${this._timeMinute}</div>
|
||||
@ -182,9 +187,41 @@ export class HuiClockCard extends LitElement implements LovelaceCard {
|
||||
|
||||
.time-wrapper {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
height: calc(100% - 12px);
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 6px 8px;
|
||||
row-gap: 6px;
|
||||
}
|
||||
|
||||
.time-wrapper.size-medium,
|
||||
.time-wrapper.size-large {
|
||||
height: calc(100% - 32px);
|
||||
padding: 16px;
|
||||
row-gap: 12px;
|
||||
}
|
||||
|
||||
.time-title {
|
||||
color: var(--primary-text-color);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.time-wrapper.size-medium .time-title {
|
||||
font-size: 18px;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
.time-wrapper.size-large .time-title {
|
||||
font-size: 24px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.time-parts {
|
||||
@ -197,7 +234,10 @@ export class HuiClockCard extends LitElement implements LovelaceCard {
|
||||
font-size: 2rem;
|
||||
font-weight: 500;
|
||||
line-height: 0.8;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.time-title + .time-parts {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.time-wrapper.size-medium .time-parts {
|
||||
@ -242,8 +282,7 @@ export class HuiClockCard extends LitElement implements LovelaceCard {
|
||||
|
||||
.time-parts .time-part.second,
|
||||
.time-parts .time-part.am-pm {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-size: 10px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,10 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
|
||||
throw new Error("Image required");
|
||||
}
|
||||
|
||||
this._config = config;
|
||||
this._config = {
|
||||
tap_action: { action: "more-info" },
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
|
@ -6,9 +6,11 @@ import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_elemen
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import "../../../components/ha-card";
|
||||
import type { CameraEntity } from "../../../data/camera";
|
||||
import type { ImageEntity } from "../../../data/image";
|
||||
import { computeImageUrl } from "../../../data/image";
|
||||
import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
||||
import type { PersonEntity } from "../../../data/person";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
@ -19,8 +21,6 @@ import "../components/hui-image";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import type { PictureEntityCardConfig } from "./types";
|
||||
import type { CameraEntity } from "../../../data/camera";
|
||||
import type { PersonEntity } from "../../../data/person";
|
||||
|
||||
export const STUB_IMAGE =
|
||||
"https://demo.home-assistant.io/stub_config/bedroom.png";
|
||||
@ -75,7 +75,12 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
|
||||
throw new Error("No image source configured");
|
||||
}
|
||||
|
||||
this._config = { show_name: true, show_state: true, ...config };
|
||||
this._config = {
|
||||
show_name: true,
|
||||
show_state: true,
|
||||
tap_action: { action: "more-info" },
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
|
@ -105,7 +105,7 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
|
||||
});
|
||||
|
||||
this._config = {
|
||||
hold_action: { action: "more-info" },
|
||||
tap_action: { action: "more-info" },
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
@ -349,9 +349,11 @@ export interface MarkdownCardConfig extends LovelaceCardConfig {
|
||||
|
||||
export interface ClockCardConfig extends LovelaceCardConfig {
|
||||
type: "clock";
|
||||
title?: string;
|
||||
clock_size?: "small" | "medium" | "large";
|
||||
show_seconds?: boolean | undefined;
|
||||
time_format?: TimeFormat;
|
||||
time_zone?: string;
|
||||
}
|
||||
|
||||
export interface MediaControlCardConfig extends LovelaceCardConfig {
|
||||
|
@ -50,7 +50,7 @@ const HIDE_DOMAIN = new Set([
|
||||
...ASSIST_ENTITIES,
|
||||
]);
|
||||
|
||||
const HIDE_PLATFORM = new Set(["mobile_app"]);
|
||||
const HIDE_PLATFORM = new Set(["backup", "mobile_app"]);
|
||||
|
||||
interface SplittedByAreaDevice {
|
||||
areasWithEntities: Record<string, HassEntity[]>;
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { mdiGestureTap } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { any, assert, literal, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import type { LovelacePictureElementEditor } from "../../../types";
|
||||
import type { IconElementConfig } from "../../../elements/types";
|
||||
import type { LovelacePictureElementEditor } from "../../../types";
|
||||
import { actionConfigStruct } from "../../structs/action-struct";
|
||||
|
||||
const iconElementConfigStruct = object({
|
||||
@ -25,16 +26,35 @@ const SCHEMA = [
|
||||
{ name: "title", selector: { text: {} } },
|
||||
{ name: "entity", selector: { entity: {} } },
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hold_action",
|
||||
selector: {
|
||||
ui_action: {},
|
||||
},
|
||||
name: "interactions",
|
||||
type: "expandable",
|
||||
flatten: true,
|
||||
iconPath: mdiGestureTap,
|
||||
schema: [
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "more-info",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "optional_actions",
|
||||
flatten: true,
|
||||
schema: (["hold_action", "double_tap_action"] as const).map(
|
||||
(action) => ({
|
||||
name: action,
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "none" as const,
|
||||
},
|
||||
},
|
||||
})
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{ name: "style", selector: { object: {} } },
|
||||
] as const;
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { mdiGestureTap } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { any, assert, literal, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import type { LovelacePictureElementEditor } from "../../../types";
|
||||
import type { ImageElementConfig } from "../../../elements/types";
|
||||
import type { LovelacePictureElementEditor } from "../../../types";
|
||||
import { actionConfigStruct } from "../../structs/action-struct";
|
||||
|
||||
const imageElementConfigStruct = object({
|
||||
@ -30,16 +31,35 @@ const SCHEMA = [
|
||||
{ name: "entity", selector: { entity: {} } },
|
||||
{ name: "title", selector: { text: {} } },
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hold_action",
|
||||
selector: {
|
||||
ui_action: {},
|
||||
},
|
||||
name: "interactions",
|
||||
type: "expandable",
|
||||
flatten: true,
|
||||
iconPath: mdiGestureTap,
|
||||
schema: [
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "more-info",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "optional_actions",
|
||||
flatten: true,
|
||||
schema: (["hold_action", "double_tap_action"] as const).map(
|
||||
(action) => ({
|
||||
name: action,
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "none" as const,
|
||||
},
|
||||
},
|
||||
})
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{ name: "image", selector: { image: {} } },
|
||||
{ name: "camera_image", selector: { entity: { domain: "camera" } } },
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { any, assert, literal, object, optional, string } from "superstruct";
|
||||
import { mdiGestureTap } from "@mdi/js";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import type { SchemaUnion } from "../../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
@ -23,16 +24,35 @@ const SCHEMA = [
|
||||
{ name: "entity", required: true, selector: { entity: {} } },
|
||||
{ name: "title", selector: { text: {} } },
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hold_action",
|
||||
selector: {
|
||||
ui_action: {},
|
||||
},
|
||||
name: "interactions",
|
||||
type: "expandable",
|
||||
flatten: true,
|
||||
iconPath: mdiGestureTap,
|
||||
schema: [
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "more-info",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "optional_actions",
|
||||
flatten: true,
|
||||
schema: (["hold_action", "double_tap_action"] as const).map(
|
||||
(action) => ({
|
||||
name: action,
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "none" as const,
|
||||
},
|
||||
},
|
||||
})
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{ name: "style", selector: { object: {} } },
|
||||
] as const;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { mdiGestureTap } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import {
|
||||
@ -10,11 +11,11 @@ import {
|
||||
string,
|
||||
} from "superstruct";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import type { LovelacePictureElementEditor } from "../../../types";
|
||||
import type { StateIconElementConfig } from "../../../elements/types";
|
||||
import type { LovelacePictureElementEditor } from "../../../types";
|
||||
import { actionConfigStruct } from "../../structs/action-struct";
|
||||
|
||||
const stateIconElementConfigStruct = object({
|
||||
@ -35,16 +36,35 @@ const SCHEMA = [
|
||||
{ name: "title", selector: { text: {} } },
|
||||
{ name: "state_color", default: true, selector: { boolean: {} } },
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hold_action",
|
||||
selector: {
|
||||
ui_action: {},
|
||||
},
|
||||
name: "interactions",
|
||||
type: "expandable",
|
||||
flatten: true,
|
||||
iconPath: mdiGestureTap,
|
||||
schema: [
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "more-info",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "optional_actions",
|
||||
flatten: true,
|
||||
schema: (["hold_action", "double_tap_action"] as const).map(
|
||||
(action) => ({
|
||||
name: action,
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "none" as const,
|
||||
},
|
||||
},
|
||||
})
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{ name: "style", selector: { object: {} } },
|
||||
] as const;
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { mdiGestureTap } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { any, assert, literal, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import type { LovelacePictureElementEditor } from "../../../types";
|
||||
import type { StateLabelElementConfig } from "../../../elements/types";
|
||||
import type { LovelacePictureElementEditor } from "../../../types";
|
||||
import { actionConfigStruct } from "../../structs/action-struct";
|
||||
|
||||
const stateLabelElementConfigStruct = object({
|
||||
@ -35,16 +36,35 @@ const SCHEMA = [
|
||||
{ name: "suffix", selector: { text: {} } },
|
||||
{ name: "title", selector: { text: {} } },
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hold_action",
|
||||
selector: {
|
||||
ui_action: {},
|
||||
},
|
||||
name: "interactions",
|
||||
type: "expandable",
|
||||
flatten: true,
|
||||
iconPath: mdiGestureTap,
|
||||
schema: [
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "more-info",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "optional_actions",
|
||||
flatten: true,
|
||||
schema: (["hold_action", "double_tap_action"] as const).map(
|
||||
(action) => ({
|
||||
name: action,
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "none" as const,
|
||||
},
|
||||
},
|
||||
})
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{ name: "style", selector: { object: {} } },
|
||||
] as const;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { mdiGestureTap } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@ -28,6 +29,7 @@ const cardConfigStruct = assign(
|
||||
icon_height: optional(string()),
|
||||
tap_action: optional(actionConfigStruct),
|
||||
hold_action: optional(actionConfigStruct),
|
||||
double_tap_action: optional(actionConfigStruct),
|
||||
theme: optional(string()),
|
||||
show_state: optional(boolean()),
|
||||
})
|
||||
@ -86,20 +88,43 @@ export class HuiButtonCardEditor
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: getEntityDefaultButtonAction(entityId),
|
||||
name: "interactions",
|
||||
type: "expandable",
|
||||
flatten: true,
|
||||
iconPath: mdiGestureTap,
|
||||
schema: [
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: getEntityDefaultButtonAction(entityId),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hold_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "more-info",
|
||||
{
|
||||
name: "hold_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "more-info",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "optional_actions",
|
||||
flatten: true,
|
||||
schema: [
|
||||
{
|
||||
name: "double_tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const satisfies readonly HaFormSchema[]
|
||||
);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import timezones from "google-timezones-json";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@ -9,6 +10,7 @@ import {
|
||||
literal,
|
||||
object,
|
||||
optional,
|
||||
string,
|
||||
union,
|
||||
} from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
@ -27,10 +29,12 @@ import { TimeFormat } from "../../../../data/translation";
|
||||
const cardConfigStruct = assign(
|
||||
baseLovelaceCardConfig,
|
||||
object({
|
||||
title: optional(string()),
|
||||
clock_size: optional(
|
||||
union([literal("small"), literal("medium"), literal("large")])
|
||||
),
|
||||
time_format: optional(enums(Object.values(TimeFormat))),
|
||||
time_zone: optional(enums(Object.keys(timezones))),
|
||||
show_seconds: optional(boolean()),
|
||||
})
|
||||
);
|
||||
@ -47,6 +51,7 @@ export class HuiClockCardEditor
|
||||
private _schema = memoizeOne(
|
||||
(localize: LocalizeFunc) =>
|
||||
[
|
||||
{ name: "title", selector: { text: {} } },
|
||||
{
|
||||
name: "clock_size",
|
||||
selector: {
|
||||
@ -61,18 +66,13 @@ export class HuiClockCardEditor
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "show_seconds",
|
||||
selector: {
|
||||
boolean: {},
|
||||
},
|
||||
},
|
||||
{ name: "show_seconds", selector: { boolean: {} } },
|
||||
{
|
||||
name: "time_format",
|
||||
selector: {
|
||||
select: {
|
||||
mode: "dropdown",
|
||||
options: Object.values(TimeFormat).map((value) => ({
|
||||
options: ["auto", ...Object.values(TimeFormat)].map((value) => ({
|
||||
value,
|
||||
label: localize(
|
||||
`ui.panel.lovelace.editor.card.clock.time_formats.${value}`
|
||||
@ -81,12 +81,33 @@ export class HuiClockCardEditor
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "time_zone",
|
||||
selector: {
|
||||
select: {
|
||||
mode: "dropdown",
|
||||
options: [
|
||||
[
|
||||
"auto",
|
||||
localize(
|
||||
`ui.panel.lovelace.editor.card.clock.time_zones.auto`
|
||||
),
|
||||
],
|
||||
...Object.entries(timezones as Record<string, string>),
|
||||
].map(([key, value]) => ({
|
||||
value: key,
|
||||
label: value,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const satisfies readonly HaFormSchema[]
|
||||
);
|
||||
|
||||
private _data = memoizeOne((config) => ({
|
||||
clock_size: "small",
|
||||
time_format: TimeFormat.language,
|
||||
time_zone: "auto",
|
||||
time_format: "auto",
|
||||
show_seconds: false,
|
||||
...config,
|
||||
}));
|
||||
@ -113,6 +134,13 @@ export class HuiClockCardEditor
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
if (ev.detail.value.time_zone === "auto") {
|
||||
delete ev.detail.value.time_zone;
|
||||
}
|
||||
if (ev.detail.value.time_format === "auto") {
|
||||
delete ev.detail.value.time_format;
|
||||
}
|
||||
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
}
|
||||
|
||||
@ -120,6 +148,10 @@ export class HuiClockCardEditor
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
case "title":
|
||||
return this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.title"
|
||||
);
|
||||
case "clock_size":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.clock.clock_size`
|
||||
@ -128,6 +160,10 @@ export class HuiClockCardEditor
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.clock.time_format`
|
||||
);
|
||||
case "time_zone":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.clock.time_zone`
|
||||
);
|
||||
case "show_seconds":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.clock.show_seconds`
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { mdiGestureTap } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@ -16,14 +17,21 @@ import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { DEFAULT_MAX, DEFAULT_MIN } from "../../cards/hui-gauge-card";
|
||||
import type { GaugeCardConfig } from "../../cards/types";
|
||||
import type { UiAction } from "../../components/hui-action-editor";
|
||||
import type { LovelaceCardEditor } from "../../types";
|
||||
import { actionConfigStruct } from "../structs/action-struct";
|
||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
import { DEFAULT_MIN, DEFAULT_MAX } from "../../cards/hui-gauge-card";
|
||||
import type { UiAction } from "../../components/hui-action-editor";
|
||||
|
||||
const TAP_ACTIONS: UiAction[] = ["navigate", "url", "perform-action", "none"];
|
||||
const TAP_ACTIONS: UiAction[] = [
|
||||
"more-info",
|
||||
"navigate",
|
||||
"url",
|
||||
"perform-action",
|
||||
"assist",
|
||||
"none",
|
||||
];
|
||||
|
||||
const gaugeSegmentStruct = object({
|
||||
from: number(),
|
||||
@ -134,13 +142,37 @@ export class HuiGaugeCardEditor
|
||||
] as const)
|
||||
: []),
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
actions: TAP_ACTIONS,
|
||||
default_action: "more-info",
|
||||
name: "interactions",
|
||||
type: "expandable",
|
||||
flatten: true,
|
||||
iconPath: mdiGestureTap,
|
||||
schema: [
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
actions: TAP_ACTIONS,
|
||||
default_action: "more-info",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "optional_actions",
|
||||
flatten: true,
|
||||
schema: (["hold_action", "double_tap_action"] as const).map(
|
||||
(action) => ({
|
||||
name: action,
|
||||
selector: {
|
||||
ui_action: {
|
||||
actions: TAP_ACTIONS,
|
||||
default_action: "none" as const,
|
||||
},
|
||||
},
|
||||
})
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const
|
||||
);
|
||||
@ -231,7 +263,13 @@ export class HuiGaugeCardEditor
|
||||
return this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.unit"
|
||||
);
|
||||
case "interactions":
|
||||
return this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.interactions"
|
||||
);
|
||||
case "tap_action":
|
||||
case "hold_action":
|
||||
case "double_tap_action":
|
||||
return `${this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.generic.${schema.name}`
|
||||
)} (${this.hass!.localize(
|
||||
|
@ -2,6 +2,7 @@ import type { CSSResultGroup } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { assert, assign, object, optional, string } from "superstruct";
|
||||
import { mdiGestureTap } from "@mdi/js";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
@ -19,6 +20,7 @@ const cardConfigStruct = assign(
|
||||
entity: optional(string()),
|
||||
theme: optional(string()),
|
||||
icon: optional(string()),
|
||||
tap_action: optional(actionConfigStruct),
|
||||
hold_action: optional(actionConfigStruct),
|
||||
double_tap_action: optional(actionConfigStruct),
|
||||
})
|
||||
@ -48,12 +50,43 @@ const SCHEMA = [
|
||||
},
|
||||
{ name: "theme", selector: { theme: {} } },
|
||||
{
|
||||
name: "hold_action",
|
||||
selector: { ui_action: {} },
|
||||
},
|
||||
{
|
||||
name: "double_tap_action",
|
||||
selector: { ui_action: {} },
|
||||
name: "interactions",
|
||||
type: "expandable",
|
||||
flatten: true,
|
||||
iconPath: mdiGestureTap,
|
||||
schema: [
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "toggle",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hold_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "more-info",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "optional_actions",
|
||||
flatten: true,
|
||||
schema: [
|
||||
{
|
||||
name: "double_tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const;
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { mdiGestureTap } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { assert, assign, object, optional, string } from "superstruct";
|
||||
@ -18,6 +19,7 @@ const cardConfigStruct = assign(
|
||||
image_entity: optional(string()),
|
||||
tap_action: optional(actionConfigStruct),
|
||||
hold_action: optional(actionConfigStruct),
|
||||
double_tap_action: optional(actionConfigStruct),
|
||||
theme: optional(string()),
|
||||
alt_text: optional(string()),
|
||||
})
|
||||
@ -32,12 +34,35 @@ const SCHEMA = [
|
||||
{ name: "alt_text", selector: { text: {} } },
|
||||
{ name: "theme", selector: { theme: {} } },
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: { ui_action: {} },
|
||||
},
|
||||
{
|
||||
name: "hold_action",
|
||||
selector: { ui_action: {} },
|
||||
name: "interactions",
|
||||
type: "expandable",
|
||||
flatten: true,
|
||||
iconPath: mdiGestureTap,
|
||||
schema: [
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "more-info",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "optional_actions",
|
||||
flatten: true,
|
||||
schema: (["hold_action", "double_tap_action"] as const).map(
|
||||
(action) => ({
|
||||
name: action,
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "none" as const,
|
||||
},
|
||||
},
|
||||
})
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const;
|
||||
|
||||
|
@ -1,18 +1,19 @@
|
||||
import { mdiGestureTap } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { assert, assign, boolean, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { STUB_IMAGE } from "../../cards/hui-picture-entity-card";
|
||||
import type { PictureEntityCardConfig } from "../../cards/types";
|
||||
import type { LovelaceCardEditor } from "../../types";
|
||||
import { actionConfigStruct } from "../structs/action-struct";
|
||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import { STUB_IMAGE } from "../../cards/hui-picture-entity-card";
|
||||
|
||||
const cardConfigStruct = assign(
|
||||
baseLovelaceCardConfig,
|
||||
@ -25,6 +26,7 @@ const cardConfigStruct = assign(
|
||||
aspect_ratio: optional(string()),
|
||||
tap_action: optional(actionConfigStruct),
|
||||
hold_action: optional(actionConfigStruct),
|
||||
double_tap_action: optional(actionConfigStruct),
|
||||
show_name: optional(boolean()),
|
||||
show_state: optional(boolean()),
|
||||
theme: optional(string()),
|
||||
@ -64,12 +66,35 @@ const SCHEMA = [
|
||||
},
|
||||
{ name: "theme", selector: { theme: {} } },
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: { ui_action: {} },
|
||||
},
|
||||
{
|
||||
name: "hold_action",
|
||||
selector: { ui_action: {} },
|
||||
name: "interactions",
|
||||
type: "expandable",
|
||||
flatten: true,
|
||||
iconPath: mdiGestureTap,
|
||||
schema: [
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "more-info",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "optional_actions",
|
||||
flatten: true,
|
||||
schema: (["hold_action", "double_tap_action"] as const).map(
|
||||
(action) => ({
|
||||
name: action,
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "none" as const,
|
||||
},
|
||||
},
|
||||
})
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const;
|
||||
|
||||
@ -132,6 +157,7 @@ export class HuiPictureEntityCardEditor
|
||||
case "theme":
|
||||
case "tap_action":
|
||||
case "hold_action":
|
||||
case "double_tap_action":
|
||||
return `${this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.generic.${schema.name}`
|
||||
)} (${this.hass!.localize(
|
||||
|
@ -2,6 +2,7 @@ import type { CSSResultGroup } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { array, assert, assign, object, optional, string } from "superstruct";
|
||||
import { mdiGestureTap } from "@mdi/js";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
@ -29,6 +30,7 @@ const cardConfigStruct = assign(
|
||||
aspect_ratio: optional(string()),
|
||||
tap_action: optional(actionConfigStruct),
|
||||
hold_action: optional(actionConfigStruct),
|
||||
double_tap_action: optional(actionConfigStruct),
|
||||
entities: array(entitiesConfigStruct),
|
||||
theme: optional(string()),
|
||||
})
|
||||
@ -56,12 +58,35 @@ const SCHEMA = [
|
||||
{ name: "entity", selector: { entity: {} } },
|
||||
{ name: "theme", selector: { theme: {} } },
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: { ui_action: {} },
|
||||
},
|
||||
{
|
||||
name: "hold_action",
|
||||
selector: { ui_action: {} },
|
||||
name: "interactions",
|
||||
type: "expandable",
|
||||
flatten: true,
|
||||
iconPath: mdiGestureTap,
|
||||
schema: [
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "more-info",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "optional_actions",
|
||||
flatten: true,
|
||||
schema: (["hold_action", "double_tap_action"] as const).map(
|
||||
(action) => ({
|
||||
name: action,
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "none" as const,
|
||||
},
|
||||
},
|
||||
})
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const;
|
||||
|
||||
@ -136,6 +161,7 @@ export class HuiPictureGlanceCardEditor
|
||||
case "theme":
|
||||
case "tap_action":
|
||||
case "hold_action":
|
||||
case "double_tap_action":
|
||||
return `${this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.generic.${schema.name}`
|
||||
)} (${this.hass!.localize(
|
||||
|
@ -31,7 +31,7 @@ export class HuiIconElement extends LitElement implements LovelaceElement {
|
||||
throw Error("Icon required");
|
||||
}
|
||||
|
||||
this._config = { hold_action: { action: "more-info" }, ...config };
|
||||
this._config = { tap_action: { action: "more-info" }, ...config };
|
||||
}
|
||||
|
||||
protected render() {
|
||||
|
@ -29,7 +29,7 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
|
||||
throw Error("Invalid configuration");
|
||||
}
|
||||
|
||||
this._config = { hold_action: { action: "more-info" }, ...config };
|
||||
this._config = { tap_action: { action: "more-info" }, ...config };
|
||||
|
||||
this.classList.toggle(
|
||||
"clickable",
|
||||
|
@ -60,7 +60,7 @@ export class HuiStateBadgeElement
|
||||
throw Error("Entity required");
|
||||
}
|
||||
|
||||
this._config = { hold_action: { action: "more-info" }, ...config };
|
||||
this._config = { tap_action: { action: "more-info" }, ...config };
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
|
@ -59,7 +59,7 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement {
|
||||
|
||||
this._config = {
|
||||
state_color: true,
|
||||
hold_action: { action: "more-info" },
|
||||
tap_action: { action: "more-info" },
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ class HuiStateLabelElement extends LitElement implements LovelaceElement {
|
||||
throw Error("Entity required");
|
||||
}
|
||||
|
||||
this._config = { hold_action: { action: "more-info" }, ...config };
|
||||
this._config = { tap_action: { action: "more-info" }, ...config };
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
|
@ -187,7 +187,7 @@ export class LovelacePanel extends LitElement {
|
||||
|
||||
private async _regenerateConfig() {
|
||||
const conf = await generateLovelaceDashboardStrategy(
|
||||
DEFAULT_CONFIG.strategy,
|
||||
DEFAULT_CONFIG,
|
||||
this.hass!
|
||||
);
|
||||
this._setLovelaceConfig(conf, DEFAULT_CONFIG, "generated");
|
||||
@ -281,10 +281,7 @@ export class LovelacePanel extends LitElement {
|
||||
// We need these to generate a dashboard, wait for them
|
||||
return;
|
||||
}
|
||||
conf = await generateLovelaceDashboardStrategy(
|
||||
rawConf.strategy,
|
||||
this.hass!
|
||||
);
|
||||
conf = await generateLovelaceDashboardStrategy(rawConf, this.hass!);
|
||||
} else {
|
||||
conf = rawConf;
|
||||
}
|
||||
@ -301,7 +298,7 @@ export class LovelacePanel extends LitElement {
|
||||
return;
|
||||
}
|
||||
conf = await generateLovelaceDashboardStrategy(
|
||||
DEFAULT_CONFIG.strategy,
|
||||
DEFAULT_CONFIG,
|
||||
this.hass!
|
||||
);
|
||||
rawConf = DEFAULT_CONFIG;
|
||||
@ -378,10 +375,7 @@ export class LovelacePanel extends LitElement {
|
||||
let conf: LovelaceConfig;
|
||||
// If strategy defined, apply it here.
|
||||
if (isStrategyDashboard(newConfig)) {
|
||||
conf = await generateLovelaceDashboardStrategy(
|
||||
newConfig.strategy,
|
||||
this.hass!
|
||||
);
|
||||
conf = await generateLovelaceDashboardStrategy(newConfig, this.hass!);
|
||||
} else {
|
||||
conf = newConfig;
|
||||
}
|
||||
@ -415,7 +409,7 @@ export class LovelacePanel extends LitElement {
|
||||
try {
|
||||
// Optimistic update
|
||||
const generatedConf = await generateLovelaceDashboardStrategy(
|
||||
DEFAULT_CONFIG.strategy,
|
||||
DEFAULT_CONFIG,
|
||||
this.hass!
|
||||
);
|
||||
this._updateLovelace({
|
||||
|
@ -76,9 +76,9 @@ import { getLovelaceStrategy } from "./strategies/get-strategy";
|
||||
import { isLegacyStrategyConfig } from "./strategies/legacy-strategy";
|
||||
import type { Lovelace } from "./types";
|
||||
import "./views/hui-view";
|
||||
import "./views/hui-view-container";
|
||||
import type { HUIView } from "./views/hui-view";
|
||||
import "./views/hui-view-background";
|
||||
import "./views/hui-view-container";
|
||||
|
||||
@customElement("hui-root")
|
||||
class HUIRoot extends LitElement {
|
||||
@ -101,6 +101,8 @@ class HUIRoot extends LitElement {
|
||||
|
||||
private _viewScrollPositions: Record<string, number> = {};
|
||||
|
||||
private _restoreScroll = false;
|
||||
|
||||
private _debouncedConfigChanged: () => void;
|
||||
|
||||
private _conversation = memoizeOne((_components) =>
|
||||
@ -112,7 +114,7 @@ class HUIRoot extends LitElement {
|
||||
// The view can trigger a re-render when it knows that certain
|
||||
// web components have been loaded.
|
||||
this._debouncedConfigChanged = debounce(
|
||||
() => this._selectView(this._curView, true, false),
|
||||
() => this._selectView(this._curView, true),
|
||||
100,
|
||||
false
|
||||
);
|
||||
@ -487,6 +489,10 @@ class HUIRoot extends LitElement {
|
||||
this.toggleAttribute("scrolled", window.scrollY !== 0);
|
||||
};
|
||||
|
||||
private _handlePopState = () => {
|
||||
this._restoreScroll = true;
|
||||
};
|
||||
|
||||
private _isVisible = (view: LovelaceViewConfig) =>
|
||||
Boolean(
|
||||
this._editMode ||
|
||||
@ -528,21 +534,19 @@ class HUIRoot extends LitElement {
|
||||
passive: true,
|
||||
});
|
||||
window.addEventListener("popstate", this._handlePopState);
|
||||
// Disable history scroll restoration because it is managed manually here
|
||||
window.history.scrollRestoration = "manual";
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener("scroll", this._handleWindowScroll);
|
||||
window.removeEventListener("popstate", this._handlePopState);
|
||||
this.toggleAttribute("scrolled", window.scrollY !== 0);
|
||||
// Re-enable history scroll restoration when leaving the page
|
||||
window.history.scrollRestoration = "auto";
|
||||
}
|
||||
|
||||
private _restoreScroll = false;
|
||||
|
||||
private _handlePopState = () => {
|
||||
// If we navigated back, we want to restore the scroll position.
|
||||
this._restoreScroll = true;
|
||||
};
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
|
||||
@ -622,8 +626,16 @@ class HUIRoot extends LitElement {
|
||||
}
|
||||
// Will allow for ripples to start rendering
|
||||
afterNextRender(() => {
|
||||
this._selectView(newSelectView, force, this._restoreScroll);
|
||||
this._restoreScroll = false;
|
||||
if (changedProperties.has("route")) {
|
||||
const position =
|
||||
(this._restoreScroll && this._viewScrollPositions[newSelectView]) ||
|
||||
0;
|
||||
this._restoreScroll = false;
|
||||
requestAnimationFrame(() =>
|
||||
scrollTo({ behavior: "auto", top: position })
|
||||
);
|
||||
}
|
||||
this._selectView(newSelectView, force);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -932,15 +944,12 @@ class HUIRoot extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _selectView(
|
||||
viewIndex: HUIRoot["_curView"],
|
||||
force: boolean,
|
||||
restoreScroll: boolean
|
||||
): void {
|
||||
private _selectView(viewIndex: HUIRoot["_curView"], force: boolean): void {
|
||||
if (!force && this._curView === viewIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save scroll position of current view
|
||||
if (this._curView != null) {
|
||||
this._viewScrollPositions[this._curView] = window.scrollY;
|
||||
}
|
||||
@ -983,15 +992,10 @@ class HUIRoot extends LitElement {
|
||||
|
||||
if (!force && this._viewCache![viewIndex]) {
|
||||
view = this._viewCache![viewIndex];
|
||||
const position = restoreScroll
|
||||
? this._viewScrollPositions[viewIndex] || 0
|
||||
: 0;
|
||||
setTimeout(() => scrollTo({ behavior: "auto", top: position }), 0);
|
||||
} else {
|
||||
view = document.createElement("hui-view");
|
||||
view.index = viewIndex;
|
||||
this._viewCache![viewIndex] = view;
|
||||
setTimeout(() => scrollTo({ behavior: "auto", top: 0 }), 0);
|
||||
}
|
||||
|
||||
view.lovelace = this.lovelace;
|
||||
|
@ -185,7 +185,7 @@ export class HuiSection extends ReactiveElement {
|
||||
if (isStrategySection(sectionConfig)) {
|
||||
isStrategy = true;
|
||||
sectionConfig = await generateLovelaceSectionStrategy(
|
||||
sectionConfig.strategy,
|
||||
sectionConfig,
|
||||
this.hass!
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { clamp } from "../../../../common/number/clamp";
|
||||
import type { LovelaceBadgeConfig } from "../../../../data/lovelace/config/badge";
|
||||
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
||||
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
|
||||
@ -144,7 +145,10 @@ export class AreaViewStrategy extends ReactiveElement {
|
||||
});
|
||||
}
|
||||
|
||||
// Take the full width if there is only one section to avoid misalignment between cards and header
|
||||
// Allow between 2 and 3 columns (the max should be set to define the width of the header)
|
||||
const maxColumns = clamp(sections.length, 2, 3);
|
||||
|
||||
// Take the full width if there is only one section to avoid narrow header on desktop
|
||||
if (sections.length === 1) {
|
||||
sections[0].column_span = 2;
|
||||
}
|
||||
@ -160,7 +164,7 @@ export class AreaViewStrategy extends ReactiveElement {
|
||||
content: `## ${area.name}`,
|
||||
},
|
||||
},
|
||||
max_columns: 2,
|
||||
max_columns: maxColumns,
|
||||
sections: sections,
|
||||
badges: badges,
|
||||
};
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { STATE_NOT_RUNNING } from "home-assistant-js-websocket";
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
|
||||
import type { LovelaceViewRawConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { LovelaceStrategyEditor } from "../types";
|
||||
import type {
|
||||
AreaViewStrategyConfig,
|
||||
EntitiesDisplay,
|
||||
} from "./area-view-strategy";
|
||||
import type { LovelaceStrategyEditor } from "../types";
|
||||
import type { AreasViewStrategyConfig } from "./areas-overview-view-strategy";
|
||||
import { computeAreaPath, getAreas } from "./helpers/areas-strategy-helper";
|
||||
|
||||
@ -30,6 +31,28 @@ export class AreasDashboardStrategy extends ReactiveElement {
|
||||
config: AreasDashboardStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceConfig> {
|
||||
if (hass.config.state === STATE_NOT_RUNNING) {
|
||||
return {
|
||||
views: [
|
||||
{
|
||||
type: "sections",
|
||||
sections: [{ cards: [{ type: "starting" }] }],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (hass.config.recovery_mode) {
|
||||
return {
|
||||
views: [
|
||||
{
|
||||
type: "sections",
|
||||
sections: [{ cards: [{ type: "recovery-mode" }] }],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const areas = getAreas(
|
||||
hass.areas,
|
||||
config.areas_display?.hidden,
|
||||
|
@ -95,7 +95,7 @@ export class AreasOverviewViewStrategy extends ReactiveElement {
|
||||
|
||||
return {
|
||||
type: "sections",
|
||||
max_columns: 2,
|
||||
max_columns: 3,
|
||||
sections: areaSections,
|
||||
};
|
||||
}
|
||||
|
@ -1,10 +1,18 @@
|
||||
import type {
|
||||
LovelaceSectionConfig,
|
||||
LovelaceStrategySectionConfig,
|
||||
} from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
import type {
|
||||
LovelaceConfig,
|
||||
LovelaceDashboardStrategyConfig,
|
||||
LovelaceRawConfig,
|
||||
} from "../../../data/lovelace/config/types";
|
||||
import { isStrategyDashboard } from "../../../data/lovelace/config/types";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type {
|
||||
LovelaceStrategyViewConfig,
|
||||
LovelaceViewConfig,
|
||||
} from "../../../data/lovelace/config/view";
|
||||
import { isStrategyView } from "../../../data/lovelace/config/view";
|
||||
import type { AsyncReturnType, HomeAssistant } from "../../../types";
|
||||
import { cleanLegacyStrategyConfig, isLegacyStrategy } from "./legacy-strategy";
|
||||
@ -133,10 +141,11 @@ const generateStrategy = async <T extends LovelaceStrategyConfigType>(
|
||||
};
|
||||
|
||||
export const generateLovelaceDashboardStrategy = async (
|
||||
strategyConfig: LovelaceStrategyConfig,
|
||||
config: LovelaceDashboardStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceConfig> =>
|
||||
generateStrategy(
|
||||
): Promise<LovelaceConfig> => {
|
||||
const { strategy, ...base } = config;
|
||||
const generated = await generateStrategy(
|
||||
"dashboard",
|
||||
(err) => ({
|
||||
views: [
|
||||
@ -151,15 +160,21 @@ export const generateLovelaceDashboardStrategy = async (
|
||||
},
|
||||
],
|
||||
}),
|
||||
strategyConfig,
|
||||
strategy,
|
||||
hass
|
||||
);
|
||||
return {
|
||||
...base,
|
||||
...generated,
|
||||
};
|
||||
};
|
||||
|
||||
export const generateLovelaceViewStrategy = async (
|
||||
strategyConfig: LovelaceStrategyConfig,
|
||||
config: LovelaceStrategyViewConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceViewConfig> =>
|
||||
generateStrategy(
|
||||
): Promise<LovelaceViewConfig> => {
|
||||
const { strategy, ...base } = config;
|
||||
const generated = await generateStrategy(
|
||||
"view",
|
||||
(err) => ({
|
||||
cards: [
|
||||
@ -169,15 +184,21 @@ export const generateLovelaceViewStrategy = async (
|
||||
},
|
||||
],
|
||||
}),
|
||||
strategyConfig,
|
||||
strategy,
|
||||
hass
|
||||
);
|
||||
return {
|
||||
...base,
|
||||
...generated,
|
||||
};
|
||||
};
|
||||
|
||||
export const generateLovelaceSectionStrategy = async (
|
||||
strategyConfig: LovelaceStrategyConfig,
|
||||
config: LovelaceStrategySectionConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceViewConfig> =>
|
||||
generateStrategy(
|
||||
): Promise<LovelaceSectionConfig> => {
|
||||
const { strategy, ...base } = config;
|
||||
const generated = await generateStrategy(
|
||||
"section",
|
||||
(err) => ({
|
||||
cards: [
|
||||
@ -187,9 +208,14 @@ export const generateLovelaceSectionStrategy = async (
|
||||
},
|
||||
],
|
||||
}),
|
||||
strategyConfig,
|
||||
strategy,
|
||||
hass
|
||||
);
|
||||
return {
|
||||
...base,
|
||||
...generated,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Find all references to strategies and replaces them with the generated output
|
||||
@ -199,20 +225,20 @@ export const expandLovelaceConfigStrategies = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceConfig> => {
|
||||
const newConfig = isStrategyDashboard(config)
|
||||
? await generateLovelaceDashboardStrategy(config.strategy, hass)
|
||||
? await generateLovelaceDashboardStrategy(config, hass)
|
||||
: { ...config };
|
||||
|
||||
newConfig.views = await Promise.all(
|
||||
newConfig.views.map(async (view) => {
|
||||
const newView = isStrategyView(view)
|
||||
? await generateLovelaceViewStrategy(view.strategy, hass)
|
||||
? await generateLovelaceViewStrategy(view, hass)
|
||||
: { ...view };
|
||||
|
||||
if (newView.sections) {
|
||||
newView.sections = await Promise.all(
|
||||
newView.sections.map(async (section) => {
|
||||
const newSection = isStrategyView(section)
|
||||
? await generateLovelaceSectionStrategy(section.strategy, hass)
|
||||
? await generateLovelaceSectionStrategy(section, hass)
|
||||
: { ...section };
|
||||
return newSection;
|
||||
})
|
||||
|
@ -233,10 +233,7 @@ export class HUIView extends ReactiveElement {
|
||||
|
||||
if (isStrategyView(viewConfig)) {
|
||||
isStrategy = true;
|
||||
viewConfig = await generateLovelaceViewStrategy(
|
||||
viewConfig.strategy,
|
||||
this.hass!
|
||||
);
|
||||
viewConfig = await generateLovelaceViewStrategy(viewConfig, this.hass!);
|
||||
}
|
||||
|
||||
viewConfig = {
|
||||
|
@ -2902,7 +2902,8 @@
|
||||
"device_consumption_energy": "Device energy consumption",
|
||||
"selected_stat_intro": "Select the energy sensor that measures the device's energy usage in either of {unit}.",
|
||||
"included_in_device": "Upstream device",
|
||||
"included_in_device_helper": "If this device is already counted by another device (such as a smart switch measured by a smart breaker), selecting the upstream device prevents duplicate energy tracking."
|
||||
"included_in_device_helper": "If this device is already counted by another device (such as a smart switch measured by a smart breaker), selecting the upstream device prevents duplicate energy tracking.",
|
||||
"no_upstream_devices": "No eligible upstream devices"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -3430,9 +3431,9 @@
|
||||
},
|
||||
"local": {
|
||||
"title": "Installing add-ons",
|
||||
"secondary": "The Whisper and Piper add-ons are being installed and configured based on your hardware.",
|
||||
"secondary": "We are preparing your system for local voice processing.",
|
||||
"failed_title": "Failed to install add-ons",
|
||||
"failed_secondary": "We were unable to install the Whisper and Piper add-ons automatically for you. Read the documentation to learn how to install them.",
|
||||
"failed_secondary": "We were unable to install the add-ons for speech-to-text and text-to-speech automatically for you. Read the documentation to learn how to install them.",
|
||||
"not_supported_title": "Installation of add-ons is not supported on your system",
|
||||
"not_supported_secondary": "Your system is not supported to automatically install a local TTS and STT provider. Learn how to set up local TTS and STT providers in the documentation.",
|
||||
"local_pipeline": "Local Assistant",
|
||||
@ -7160,12 +7161,17 @@
|
||||
"large": "Large"
|
||||
},
|
||||
"show_seconds": "Display seconds",
|
||||
"time_format": "Time format",
|
||||
"time_format": "[%key:ui::panel::profile::time_format::dropdown_label%]",
|
||||
"time_formats": {
|
||||
"auto": "Use user settings",
|
||||
"language": "[%key:ui::panel::profile::time_format::formats::language%]",
|
||||
"system": "[%key:ui::panel::profile::time_format::formats::system%]",
|
||||
"24": "[%key:ui::panel::profile::time_format::formats::24%]",
|
||||
"12": "[%key:ui::panel::profile::time_format::formats::12%]"
|
||||
},
|
||||
"time_zone": "[%key:ui::panel::profile::time_zone::dropdown_label%]",
|
||||
"time_zones": {
|
||||
"auto": "Use user settings"
|
||||
}
|
||||
},
|
||||
"media-control": {
|
||||
@ -8129,7 +8135,7 @@
|
||||
},
|
||||
"mean_type": {
|
||||
"0": "None",
|
||||
"1": "Arimethic",
|
||||
"1": "Arithmetic",
|
||||
"2": "Circular"
|
||||
},
|
||||
"fix_issue": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user