mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 09:16:38 +00:00
Merge pull request #11899 from home-assistant/dev
This commit is contained in:
commit
7f086c0900
@ -279,7 +279,7 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
can_play: true,
|
||||
can_expand: false,
|
||||
children_media_class: null,
|
||||
thumbnail: null,
|
||||
thumbnail: "https://brands.home-assistant.io/_/image/logo.png",
|
||||
},
|
||||
{
|
||||
title: "movie.mp4",
|
||||
|
@ -110,8 +110,6 @@ class HassioAddonStore extends LitElement {
|
||||
<div class="search">
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
no-label-float
|
||||
no-underline
|
||||
.filter=${this._filter}
|
||||
@value-changed=${this._filterChanged}
|
||||
></search-input>
|
||||
|
@ -80,8 +80,6 @@ class HassioHardwareDialog extends LitElement {
|
||||
></ha-icon-button>
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
dialogInitialFocus
|
||||
no-label-float
|
||||
.filter=${this._filter}
|
||||
@value-changed=${this._handleSearchChange}
|
||||
.label=${this._dialogParams.supervisor.localize(
|
||||
|
@ -106,6 +106,9 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
</paper-item-body>
|
||||
<div class="delete">
|
||||
<ha-icon-button
|
||||
.label=${this._dialogParams!.supervisor.localize(
|
||||
"dialog.repositories.remove"
|
||||
)}
|
||||
.disabled=${usedRepositories.includes(repo.slug)}
|
||||
.slug=${repo.slug}
|
||||
.path=${usedRepositories.includes(repo.slug)
|
||||
|
@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = home-assistant-frontend
|
||||
version = 20220226.0
|
||||
version = 20220301.0
|
||||
author = The Home Assistant Authors
|
||||
author_email = hello@home-assistant.io
|
||||
license = Apache-2.0
|
||||
|
@ -115,6 +115,9 @@ class DateRangePickerElement extends WrappedElement {
|
||||
color: var(--primary-text-color);
|
||||
min-width: initial !important;
|
||||
}
|
||||
.daterangepicker:after {
|
||||
display: none;
|
||||
}
|
||||
.daterangepicker:after {
|
||||
border-bottom: 6px solid var(--card-background-color);
|
||||
}
|
||||
|
@ -51,10 +51,11 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
|
||||
private _filterEntities = (entity: HassEntity): boolean => {
|
||||
if (this.selector.entity?.domain) {
|
||||
const filterDomain = this.selector.entity.domain;
|
||||
const filterDomainIsArray = Array.isArray(filterDomain);
|
||||
const entityDomain = computeStateDomain(entity);
|
||||
if (
|
||||
(Array.isArray(filterDomain) && !filterDomain.includes(entityDomain)) ||
|
||||
entityDomain !== filterDomain
|
||||
(filterDomainIsArray && !filterDomain.includes(entityDomain)) ||
|
||||
(!filterDomainIsArray && entityDomain !== filterDomain)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
} from "../../data/media-player";
|
||||
import type { MediaSelector, MediaSelectorValue } from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url";
|
||||
import "../ha-alert";
|
||||
import "../ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../ha-form/types";
|
||||
@ -50,6 +51,18 @@ export class HaMediaSelector extends LitElement {
|
||||
getSignedPath(this.hass, thumbnail).then((signedPath) => {
|
||||
this._thumbnailUrl = signedPath.path;
|
||||
});
|
||||
} else if (
|
||||
thumbnail &&
|
||||
thumbnail.startsWith("https://brands.home-assistant.io")
|
||||
) {
|
||||
// The backend is not aware of the theme used by the users,
|
||||
// so we rewrite the URL to show a proper icon
|
||||
this._thumbnailUrl = brandsUrl({
|
||||
domain: extractDomainFromBrandUrl(thumbnail),
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
});
|
||||
} else {
|
||||
this._thumbnailUrl = thumbnail;
|
||||
}
|
||||
|
@ -34,23 +34,24 @@ import {
|
||||
MediaPickedEvent,
|
||||
MediaPlayerBrowseAction,
|
||||
} from "../../data/media-player";
|
||||
import { browseLocalMediaPlayer } from "../../data/media_source";
|
||||
import { isTTSMediaSource } from "../../data/tts";
|
||||
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
||||
import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import "../entity/ha-entity-picker";
|
||||
import "../ha-button-menu";
|
||||
import "../ha-card";
|
||||
import type { HaCard } from "../ha-card";
|
||||
import "../ha-circular-progress";
|
||||
import "../ha-fab";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-svg-icon";
|
||||
import "../ha-fab";
|
||||
import { browseLocalMediaPlayer } from "../../data/media_source";
|
||||
import { isTTSMediaSource } from "../../data/tts";
|
||||
import type { TtsMediaPickedEvent } from "./ha-browse-media-tts";
|
||||
import "./ha-browse-media-tts";
|
||||
import type { TtsMediaPickedEvent } from "./ha-browse-media-tts";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@ -681,6 +682,17 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
// Thumbnails served by local API require authentication
|
||||
const signedPath = await getSignedPath(this.hass, thumbnailUrl);
|
||||
thumbnailUrl = signedPath.path;
|
||||
} else if (
|
||||
thumbnailUrl.startsWith("https://brands.home-assistant.io")
|
||||
) {
|
||||
// The backend is not aware of the theme used by the users,
|
||||
// so we rewrite the URL to show a proper icon
|
||||
thumbnailUrl = brandsUrl({
|
||||
domain: extractDomainFromBrandUrl(thumbnailUrl),
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
});
|
||||
}
|
||||
thumbnailCard.style.backgroundImage = `url(${thumbnailUrl})`;
|
||||
observer.unobserve(thumbnailCard); // loaded, so no need to observe anymore
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputDateTime {
|
||||
@ -17,6 +18,19 @@ export interface InputDateTimeMutableParams {
|
||||
has_date: boolean;
|
||||
}
|
||||
|
||||
export const stateToIsoDateString = (entityState: HassEntity) =>
|
||||
`${entityState.attributes.year || "1970"}-${String(
|
||||
entityState.attributes.month || "01"
|
||||
).padStart(2, "0")}-${String(entityState.attributes.day || "01").padStart(
|
||||
2,
|
||||
"0"
|
||||
)}T${String(entityState.attributes.hour || "00").padStart(2, "0")}:${String(
|
||||
entityState.attributes.minute || "00"
|
||||
).padStart(2, "0")}:${String(entityState.attributes.second || "00").padStart(
|
||||
2,
|
||||
"0"
|
||||
)}`;
|
||||
|
||||
export const setInputDateTimeValue = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
|
@ -4,7 +4,10 @@ import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-date-input";
|
||||
import "../../../components/ha-time-input";
|
||||
import { UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
|
||||
import { setInputDateTimeValue } from "../../../data/input_datetime";
|
||||
import {
|
||||
setInputDateTimeValue,
|
||||
stateToIsoDateString,
|
||||
} from "../../../data/input_datetime";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("more-info-input_datetime")
|
||||
@ -24,7 +27,7 @@ class MoreInfoInputDatetime extends LitElement {
|
||||
? html`
|
||||
<ha-date-input
|
||||
.locale=${this.hass.locale}
|
||||
.value=${`${this.stateObj.attributes.year}-${this.stateObj.attributes.month}-${this.stateObj.attributes.day}`}
|
||||
.value=${stateToIsoDateString(this.stateObj)}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj.state)}
|
||||
@value-changed=${this._dateChanged}
|
||||
>
|
||||
|
@ -13,6 +13,9 @@ export const SubscribeMixin = <T extends Constructor<ReactiveElement>>(
|
||||
class SubscribeClass extends superClass {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
// we wait with subscribing till these properties are set on the host element
|
||||
protected hassSubscribeRequiredHostProps?: string[];
|
||||
|
||||
private __unsubs?: Array<UnsubscribeFunc | Promise<UnsubscribeFunc>>;
|
||||
|
||||
public connectedCallback() {
|
||||
@ -39,6 +42,16 @@ export const SubscribeMixin = <T extends Constructor<ReactiveElement>>(
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("hass")) {
|
||||
this.__checkSubscribed();
|
||||
return;
|
||||
}
|
||||
if (!this.hassSubscribeRequiredHostProps) {
|
||||
return;
|
||||
}
|
||||
for (const key of changedProps.keys()) {
|
||||
if (this.hassSubscribeRequiredHostProps.includes(key as string)) {
|
||||
this.__checkSubscribed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +65,10 @@ export const SubscribeMixin = <T extends Constructor<ReactiveElement>>(
|
||||
if (
|
||||
this.__unsubs !== undefined ||
|
||||
!(this as unknown as Element).isConnected ||
|
||||
this.hass === undefined
|
||||
this.hass === undefined ||
|
||||
this.hassSubscribeRequiredHostProps?.some(
|
||||
(prop) => this[prop] === undefined
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
@ -133,6 +133,7 @@ class HaConfigDashboard extends LitElement {
|
||||
></ha-menu-button>
|
||||
<div main-title>${this.hass.localize("panel.config")}</div>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("ui.dialogs.quick-bar.title")}
|
||||
.path=${mdiMagnify}
|
||||
@click=${this._showQuickBar}
|
||||
></ha-icon-button>
|
||||
|
@ -86,6 +86,7 @@ export class EnergyDeviceSettings extends LitElement {
|
||||
: device.stat_consumption}</span
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("ui.common.delete")}
|
||||
@click=${this._deleteDevice}
|
||||
.device=${device}
|
||||
.path=${mdiDelete}
|
||||
|
@ -144,8 +144,6 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
<div slot="header">
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
no-label-float
|
||||
no-underline
|
||||
class="header"
|
||||
@value-changed=${this._handleSearchChange}
|
||||
.filter=${this._filter}
|
||||
@ -161,8 +159,6 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
${!this.narrow
|
||||
? html`<search-input
|
||||
.hass=${this.hass}
|
||||
no-label-float
|
||||
no-underline
|
||||
@value-changed=${this._handleSearchChange}
|
||||
.filter=${this._filter}
|
||||
.label=${this.hass.localize(
|
||||
|
@ -43,8 +43,6 @@ export class HaConfigLogs extends LitElement {
|
||||
<div slot="header">
|
||||
<search-input
|
||||
class="header"
|
||||
no-label-float
|
||||
no-underline
|
||||
@value-changed=${this._filterChanged}
|
||||
.hass=${this.hass}
|
||||
.filter=${this._filter}
|
||||
@ -55,9 +53,6 @@ export class HaConfigLogs extends LitElement {
|
||||
: html`
|
||||
<div class="search">
|
||||
<search-input
|
||||
autofocus
|
||||
no-label-float
|
||||
no-underline
|
||||
@value-changed=${this._filterChanged}
|
||||
.hass=${this.hass}
|
||||
.filter=${this._filter}
|
||||
|
@ -32,6 +32,8 @@ class HuiEnergyCarbonGaugeCard
|
||||
|
||||
@state() private _data?: EnergyData;
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public getCardSize(): number {
|
||||
return 4;
|
||||
}
|
||||
|
@ -49,6 +49,8 @@ export class HuiEnergyDevicesGraphCard
|
||||
|
||||
@query("ha-chart-base") private _chart?: HaChartBase;
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
getEnergyDataCollection(this.hass, {
|
||||
|
@ -43,6 +43,8 @@ class HuiEnergyDistrubutionCard
|
||||
|
||||
@state() private _data?: EnergyData;
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public setConfig(config: EnergyDistributionCardConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
@ -62,6 +62,8 @@ export class HuiEnergyGasGraphCard
|
||||
|
||||
@state() private _unit?: string;
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
getEnergyDataCollection(this.hass, {
|
||||
|
@ -35,6 +35,8 @@ class HuiEnergyGridGaugeCard
|
||||
|
||||
@state() private _data?: EnergyData;
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
getEnergyDataCollection(this.hass!, {
|
||||
|
@ -30,6 +30,8 @@ class HuiEnergySolarGaugeCard
|
||||
|
||||
@state() private _data?: EnergyData;
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
getEnergyDataCollection(this.hass!, {
|
||||
|
@ -61,6 +61,8 @@ export class HuiEnergySolarGraphCard
|
||||
|
||||
@state() private _end = endOfToday();
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
getEnergyDataCollection(this.hass, {
|
||||
|
@ -45,6 +45,8 @@ export class HuiEnergySourcesTableCard
|
||||
|
||||
@state() private _data?: EnergyData;
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
getEnergyDataCollection(this.hass, {
|
||||
|
@ -50,6 +50,8 @@ export class HuiEnergyUsageGraphCard
|
||||
|
||||
@state() private _end = endOfToday();
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
getEnergyDataCollection(this.hass, {
|
||||
|
@ -9,7 +9,10 @@ import {
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../components/ha-date-input";
|
||||
import { UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
|
||||
import { setInputDateTimeValue } from "../../../data/input_datetime";
|
||||
import {
|
||||
setInputDateTimeValue,
|
||||
stateToIsoDateString,
|
||||
} from "../../../data/input_datetime";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import "../components/hui-generic-entity-row";
|
||||
@ -65,7 +68,7 @@ class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow {
|
||||
.label=${stateObj.attributes.has_time ? name : undefined}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
||||
.value=${`${stateObj.attributes.year}-${stateObj.attributes.month}-${stateObj.attributes.day}`}
|
||||
.value=${stateToIsoDateString(stateObj)}
|
||||
@value-changed=${this._dateChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
|
@ -9,6 +9,8 @@ import {
|
||||
import { ResolvedMediaSource } from "../../data/media_source";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
export const ERR_UNSUPPORTED_MEDIA = "Unsupported Media";
|
||||
|
||||
export class BrowserMediaPlayer {
|
||||
private player: HTMLAudioElement;
|
||||
|
||||
@ -25,6 +27,9 @@ export class BrowserMediaPlayer {
|
||||
private onChange: () => void
|
||||
) {
|
||||
const player = new Audio(this.resolved.url);
|
||||
if (player.canPlayType(resolved.mime_type) === "") {
|
||||
throw new Error(ERR_UNSUPPORTED_MEDIA);
|
||||
}
|
||||
player.autoplay = true;
|
||||
player.volume = volume;
|
||||
player.addEventListener("play", this._handleChange);
|
||||
|
@ -49,9 +49,13 @@ import {
|
||||
SUPPORT_VOLUME_SET,
|
||||
} from "../../data/media-player";
|
||||
import { ResolvedMediaSource } from "../../data/media_source";
|
||||
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../lovelace/components/hui-marquee";
|
||||
import { BrowserMediaPlayer } from "./browser-media-player";
|
||||
import {
|
||||
BrowserMediaPlayer,
|
||||
ERR_UNSUPPORTED_MEDIA,
|
||||
} from "./browser-media-player";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@ -125,13 +129,25 @@ export class BarMediaPlayer extends LitElement {
|
||||
throw Error("Only browser supported");
|
||||
}
|
||||
this._tearDownBrowserPlayer();
|
||||
this._browserPlayer = new BrowserMediaPlayer(
|
||||
this.hass,
|
||||
item,
|
||||
resolved,
|
||||
this._browserPlayerVolume,
|
||||
() => this.requestUpdate("_browserPlayer")
|
||||
);
|
||||
try {
|
||||
this._browserPlayer = new BrowserMediaPlayer(
|
||||
this.hass,
|
||||
item,
|
||||
resolved,
|
||||
this._browserPlayerVolume,
|
||||
() => this.requestUpdate("_browserPlayer")
|
||||
);
|
||||
} catch (err: any) {
|
||||
if (err.message === ERR_UNSUPPORTED_MEDIA) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.components.media-browser.media_not_supported"
|
||||
),
|
||||
});
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
this._newMediaExpected = false;
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,10 @@ import {
|
||||
MediaPickedEvent,
|
||||
MediaPlayerItem,
|
||||
} from "../../data/media-player";
|
||||
import { resolveMediaSource } from "../../data/media_source";
|
||||
import {
|
||||
ResolvedMediaSource,
|
||||
resolveMediaSource,
|
||||
} from "../../data/media_source";
|
||||
import "../../layouts/ha-app-layout";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../types";
|
||||
@ -224,11 +227,19 @@ class PanelMediaBrowser extends LitElement {
|
||||
}
|
||||
|
||||
this._player.showResolvingNewMediaPicked();
|
||||
|
||||
const resolvedUrl = await resolveMediaSource(
|
||||
this.hass,
|
||||
item.media_content_id
|
||||
);
|
||||
let resolvedUrl: ResolvedMediaSource;
|
||||
try {
|
||||
resolvedUrl = await resolveMediaSource(this.hass, item.media_content_id);
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.components.media-browser.media_browsing_error"
|
||||
),
|
||||
text: err.message,
|
||||
});
|
||||
this._player.hideResolvingNewMediaPicked();
|
||||
return;
|
||||
}
|
||||
|
||||
if (resolvedUrl.mime_type.startsWith("audio/")) {
|
||||
this._player.playItem(item, resolvedUrl);
|
||||
|
@ -9,3 +9,5 @@ export const brandsUrl = (options: BrandsOptions): string =>
|
||||
`https://brands.home-assistant.io/${options.useFallback ? "_/" : ""}${
|
||||
options.domain
|
||||
}/${options.darkOptimized ? "dark_" : ""}${options.type}.png`;
|
||||
|
||||
export const extractDomainFromBrandUrl = (url: string) => url.split("/")[4];
|
||||
|
Loading…
x
Reference in New Issue
Block a user