mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
20190904.0 (#3613)
* Alarm codes (#3566) * Handle alarm codes from keyboard input Closes https://github.com/home-assistant/home-assistant-polymer/issues/2602 * remove friendly_name changes * remove unnecessary TS check * Update azure-pipelines-release.yml for Azure Pipelines * Don't remove `hvac_action` from history attributes (#3570) So it can be used to plot a fill when active in the graph. * Update the map when making config changes (#3568) * Add haptic feedback to handle click (#3569) * Filter camera service entities (#3583) Closes https://github.com/home-assistant/home-assistant-polymer/issues/3582 * Notification drawer RTL support (#3580) * add exceptional icon (#3572) * Add options to badges (#3552) * Add options to badges name icon entity_picture * lint * lint * rename entityPicture to image * Align styling cast buttons (#3579) * Align styling cast buttons * Split dev constants * Ignore dev_const * Update README.md * Move lovelace background settings to theme (#3561) * Move lovelace background settings to theme While being backwards compatible * Also update cast * Don't allow overwrite of english lang (#3590) * Update hui-card-options.ts (#3591) * Fix display of no triggers text if no device is selected or device has no triggers (#3592) * Fix timing issue in external auth (#3587) * Fix timing issue in external auth * add await 0 * Show toast on successfull save (#3576) * Show toast on successfull save We need to make a list of places where this could benefit the user experience. * Helper method * Rename * handle unavailable lights (#3549) * handle unavailable lights * unavailable overlay * extract unavailable overlay * Option to display last changed in glance-card (#3584) * Option to display last changed in glance-card Closes https://github.com/home-assistant/ui-schema/issues/110 * move show_last_changed to entity-level * address review comments * Filter alerts in services (#3598) Closes https://github.com/home-assistant/home-assistant-polymer/issues/3597 * Add exceptional in weather to translations (#3599) * Add MQTT subscribe to dev tools (#3589) * Add mqtt subscribe to dev tools * Update mqtt-subscribe-card.ts * Comments * type * Wrap long attributes in more-info-default (#3601) Can likely be applied in many other places Closes https://github.com/home-assistant/home-assistant-polymer/issues/2811 * Bumped version to 20190904.0 (#3612)
This commit is contained in:
parent
b022128031
commit
abb9190c98
3
.gitignore
vendored
3
.gitignore
vendored
@ -25,6 +25,9 @@ dist
|
|||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# Cast dev settings
|
||||||
|
src/cast/dev_const.ts
|
||||||
|
|
||||||
# Secrets
|
# Secrets
|
||||||
.lokalise_token
|
.lokalise_token
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
|
@ -8,7 +8,7 @@ trigger:
|
|||||||
pr: none
|
pr: none
|
||||||
variables:
|
variables:
|
||||||
- name: versionWheels
|
- name: versionWheels
|
||||||
value: '1.1-3.7-alpine3.10'
|
value: '1.3-3.7-alpine3.10'
|
||||||
- name: versionNode
|
- name: versionNode
|
||||||
value: '12.1'
|
value: '12.1'
|
||||||
- group: twine
|
- group: twine
|
||||||
|
@ -214,6 +214,8 @@ gulp.task(
|
|||||||
const lang = subtags.slice(0, i).join("-");
|
const lang = subtags.slice(0, i).join("-");
|
||||||
if (lang === "test") {
|
if (lang === "test") {
|
||||||
src.push(workDir + "/test.json");
|
src.push(workDir + "/test.json");
|
||||||
|
} else if (lang === "en") {
|
||||||
|
src.push("src/translations/en.json");
|
||||||
} else {
|
} else {
|
||||||
src.push(inDir + "/" + lang + ".json");
|
src.push(inDir + "/" + lang + ".json");
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ Home Assistant Cast is made up of two separate applications:
|
|||||||
|
|
||||||
### Setting dev variables
|
### Setting dev variables
|
||||||
|
|
||||||
Open `src/cast/const.ts` and change `CAST_DEV` to `true` and `CAST_DEV_APP_ID` to the ID of the app you just created.
|
Open `src/cast/dev_const.ts` and change `CAST_DEV_APP_ID` to the ID of the app you just created. And set the `CAST_DEV_HASS_URL` to the url of you development machine.
|
||||||
|
|
||||||
### Changing configuration
|
### Changing configuration
|
||||||
|
|
||||||
|
@ -57,10 +57,16 @@ class HcLovelace extends LitElement {
|
|||||||
const index = this._viewIndex;
|
const index = this._viewIndex;
|
||||||
|
|
||||||
if (index !== undefined) {
|
if (index !== undefined) {
|
||||||
this.shadowRoot!.querySelector("hui-view")!.style.background =
|
const configBackground =
|
||||||
this.lovelaceConfig.views[index].background ||
|
this.lovelaceConfig.views[index].background ||
|
||||||
this.lovelaceConfig.background ||
|
this.lovelaceConfig.background;
|
||||||
"";
|
|
||||||
|
if (configBackground) {
|
||||||
|
this.shadowRoot!.querySelector("hui-view")!.style.setProperty(
|
||||||
|
"--lovelace-background",
|
||||||
|
configBackground
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20190901.0",
|
version="20190904.0",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
@ -281,6 +281,7 @@ class HaWeatherCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
this.weatherIcons = {
|
this.weatherIcons = {
|
||||||
"clear-night": "hass:weather-night",
|
"clear-night": "hass:weather-night",
|
||||||
cloudy: "hass:weather-cloudy",
|
cloudy: "hass:weather-cloudy",
|
||||||
|
exceptional: "hass:alert-circle-outline",
|
||||||
fog: "hass:weather-fog",
|
fog: "hass:weather-fog",
|
||||||
hail: "hass:weather-hail",
|
hail: "hass:weather-hail",
|
||||||
lightning: "hass:weather-lightning",
|
lightning: "hass:weather-lightning",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { castApiAvailable } from "./cast_framework";
|
import { castApiAvailable } from "./cast_framework";
|
||||||
import { CAST_APP_ID, CAST_NS, CAST_DEV_HASS_URL, CAST_DEV } from "./const";
|
import { CAST_APP_ID, CAST_NS, CAST_DEV } from "./const";
|
||||||
|
import { CAST_DEV_HASS_URL } from "./dev_const";
|
||||||
import {
|
import {
|
||||||
castSendAuth,
|
castSendAuth,
|
||||||
HassMessage as ReceiverMessage,
|
HassMessage as ReceiverMessage,
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
|
import { CAST_DEV_APP_ID } from "./dev_const";
|
||||||
|
|
||||||
// Guard dev mode with `__dev__` so it can only ever be enabled in dev mode.
|
// Guard dev mode with `__dev__` so it can only ever be enabled in dev mode.
|
||||||
export const CAST_DEV = __DEV__ && true;
|
export const CAST_DEV = __DEV__ && true;
|
||||||
// Replace this with your own unpublished cast app that points at your local dev
|
|
||||||
const CAST_DEV_APP_ID = "5FE44367";
|
|
||||||
export const CAST_APP_ID = CAST_DEV ? CAST_DEV_APP_ID : "B12CE3CA";
|
export const CAST_APP_ID = CAST_DEV ? CAST_DEV_APP_ID : "B12CE3CA";
|
||||||
export const CAST_NS = "urn:x-cast:com.nabucasa.hast";
|
export const CAST_NS = "urn:x-cast:com.nabucasa.hast";
|
||||||
|
|
||||||
// Chromecast SDK will only load on localhost and HTTPS
|
|
||||||
// So during local development we have to send our dev IP address,
|
|
||||||
// but then run the UI on localhost.
|
|
||||||
export const CAST_DEV_HASS_URL = "http://192.168.1.234:8123";
|
|
||||||
|
7
src/cast/dev_const.ts
Normal file
7
src/cast/dev_const.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Replace this with your own unpublished cast app that points at your local dev
|
||||||
|
export const CAST_DEV_APP_ID = "5FE44367";
|
||||||
|
|
||||||
|
// Chromecast SDK will only load on localhost and HTTPS
|
||||||
|
// So during local development we have to send our dev IP address,
|
||||||
|
// but then run the UI on localhost.
|
||||||
|
export const CAST_DEV_HASS_URL = "http://192.168.1.234:8123";
|
@ -4,7 +4,8 @@ import { Auth } from "home-assistant-js-websocket";
|
|||||||
import { CastManager } from "./cast_manager";
|
import { CastManager } from "./cast_manager";
|
||||||
|
|
||||||
import { BaseCastMessage } from "./types";
|
import { BaseCastMessage } from "./types";
|
||||||
import { CAST_DEV_HASS_URL, CAST_DEV } from "./const";
|
import { CAST_DEV } from "./const";
|
||||||
|
import { CAST_DEV_HASS_URL } from "./dev_const";
|
||||||
|
|
||||||
export interface GetStatusMessage extends BaseCastMessage {
|
export interface GetStatusMessage extends BaseCastMessage {
|
||||||
type: "get_status";
|
type: "get_status";
|
||||||
|
@ -20,10 +20,19 @@ export const setupLeafletMap = async (
|
|||||||
style.setAttribute("rel", "stylesheet");
|
style.setAttribute("rel", "stylesheet");
|
||||||
mapElement.parentNode.appendChild(style);
|
mapElement.parentNode.appendChild(style);
|
||||||
map.setView([52.3731339, 4.8903147], 13);
|
map.setView([52.3731339, 4.8903147], 13);
|
||||||
Leaflet.tileLayer(
|
createTileLayer(Leaflet, darkMode).addTo(map);
|
||||||
|
|
||||||
|
return [map, Leaflet];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createTileLayer = (
|
||||||
|
leaflet: LeafletModuleType,
|
||||||
|
darkMode: boolean
|
||||||
|
) => {
|
||||||
|
return leaflet.tileLayer(
|
||||||
`https://{s}.basemaps.cartocdn.com/${
|
`https://{s}.basemaps.cartocdn.com/${
|
||||||
darkMode ? "dark_all" : "light_all"
|
darkMode ? "dark_all" : "light_all"
|
||||||
}/{z}/{x}/{y}${Leaflet.Browser.retina ? "@2x.png" : ".png"}`,
|
}/{z}/{x}/{y}${leaflet.Browser.retina ? "@2x.png" : ".png"}`,
|
||||||
{
|
{
|
||||||
attribution:
|
attribution:
|
||||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, © <a href="https://carto.com/attributions">CARTO</a>',
|
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, © <a href="https://carto.com/attributions">CARTO</a>',
|
||||||
@ -31,7 +40,5 @@ export const setupLeafletMap = async (
|
|||||||
minZoom: 0,
|
minZoom: 0,
|
||||||
maxZoom: 20,
|
maxZoom: 20,
|
||||||
}
|
}
|
||||||
).addTo(map);
|
);
|
||||||
|
|
||||||
return [map, Leaflet];
|
|
||||||
};
|
};
|
||||||
|
@ -37,7 +37,10 @@ class HaDeviceTriggerPicker extends LitElement {
|
|||||||
@property() private _renderEmpty = false;
|
@property() private _renderEmpty = false;
|
||||||
|
|
||||||
private get _key() {
|
private get _key() {
|
||||||
if (!this.value) {
|
if (
|
||||||
|
!this.value ||
|
||||||
|
deviceAutomationTriggersEqual(this._noTrigger, this.value)
|
||||||
|
) {
|
||||||
return NO_TRIGGER_KEY;
|
return NO_TRIGGER_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,12 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
|
|
||||||
@property() public state?: HassEntity;
|
@property() public state?: HassEntity;
|
||||||
|
|
||||||
|
@property() public name?: string;
|
||||||
|
|
||||||
|
@property() public icon?: string;
|
||||||
|
|
||||||
|
@property() public image?: string;
|
||||||
|
|
||||||
@property() private _timerTimeRemaining?: number;
|
@property() private _timerTimeRemaining?: number;
|
||||||
|
|
||||||
private _connected?: boolean;
|
private _connected?: boolean;
|
||||||
@ -72,10 +78,14 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
"has-unit_of_measurement": "unit_of_measurement" in state.attributes,
|
"has-unit_of_measurement": "unit_of_measurement" in state.attributes,
|
||||||
})}"
|
})}"
|
||||||
.value="${this._computeValue(domain, state)}"
|
.value="${this._computeValue(domain, state)}"
|
||||||
.icon="${this._computeIcon(domain, state)}"
|
.icon="${this.icon ? this.icon : this._computeIcon(domain, state)}"
|
||||||
.image="${state.attributes.entity_picture}"
|
.image="${this.icon
|
||||||
|
? ""
|
||||||
|
: this.image
|
||||||
|
? this.image
|
||||||
|
: state.attributes.entity_picture}"
|
||||||
.label="${this._computeLabel(domain, state, this._timerTimeRemaining)}"
|
.label="${this._computeLabel(domain, state, this._timerTimeRemaining)}"
|
||||||
.description="${computeStateName(state)}"
|
.description="${this.name ? this.name : computeStateName(state)}"
|
||||||
></ha-label-badge>
|
></ha-label-badge>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ class HaAttributes extends PolymerElement {
|
|||||||
<style>
|
<style>
|
||||||
.data-entry .value {
|
.data-entry .value {
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
.attribution {
|
.attribution {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
|
@ -3,9 +3,11 @@ export const UNAVAILABLE = "unavailable";
|
|||||||
export const ENTITY_COMPONENT_DOMAINS = [
|
export const ENTITY_COMPONENT_DOMAINS = [
|
||||||
"air_quality",
|
"air_quality",
|
||||||
"alarm_control_panel",
|
"alarm_control_panel",
|
||||||
|
"alert",
|
||||||
"automation",
|
"automation",
|
||||||
"binary_sensor",
|
"binary_sensor",
|
||||||
"calendar",
|
"calendar",
|
||||||
|
"camera",
|
||||||
"counter",
|
"counter",
|
||||||
"cover",
|
"cover",
|
||||||
"dominos",
|
"dominos",
|
||||||
|
@ -11,6 +11,7 @@ const LINE_ATTRIBUTES_TO_KEEP = [
|
|||||||
"current_temperature",
|
"current_temperature",
|
||||||
"target_temp_low",
|
"target_temp_low",
|
||||||
"target_temp_high",
|
"target_temp_high",
|
||||||
|
"hvac_action",
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface LineChartState {
|
export interface LineChartState {
|
||||||
|
19
src/data/mqtt.ts
Normal file
19
src/data/mqtt.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export interface MQTTMessage {
|
||||||
|
topic: string;
|
||||||
|
payload: string;
|
||||||
|
qos: number;
|
||||||
|
retain: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const subscribeMQTTTopic = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
topic: string,
|
||||||
|
callback: (message: MQTTMessage) => void
|
||||||
|
) => {
|
||||||
|
return hass.connection.subscribeMessage<MQTTMessage>(callback, {
|
||||||
|
type: "mqtt/subscribe",
|
||||||
|
topic,
|
||||||
|
});
|
||||||
|
};
|
@ -158,6 +158,7 @@ class MoreInfoWeather extends LocalizeMixin(PolymerElement) {
|
|||||||
this.weatherIcons = {
|
this.weatherIcons = {
|
||||||
"clear-night": "hass:weather-night",
|
"clear-night": "hass:weather-night",
|
||||||
cloudy: "hass:weather-cloudy",
|
cloudy: "hass:weather-cloudy",
|
||||||
|
exceptional: "hass:alert-circle-outline",
|
||||||
fog: "hass:weather-fog",
|
fog: "hass:weather-fog",
|
||||||
hail: "hass:weather-hail",
|
hail: "hass:weather-hail",
|
||||||
lightning: "hass:weather-lightning",
|
lightning: "hass:weather-lightning",
|
||||||
|
@ -49,7 +49,7 @@ export class HuiNotificationDrawer extends EventsMixin(
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<app-drawer id='drawer' opened="{{open}}" disable-swipe>
|
<app-drawer id='drawer' opened="{{open}}" disable-swipe align="start">
|
||||||
<app-toolbar>
|
<app-toolbar>
|
||||||
<div main-title>[[localize('ui.notification_drawer.title')]]</div>
|
<div main-title>[[localize('ui.notification_drawer.title')]]</div>
|
||||||
<ha-paper-icon-button-prev on-click="_closeDrawer"></paper-icon-button>
|
<ha-paper-icon-button-prev on-click="_closeDrawer"></paper-icon-button>
|
||||||
|
@ -63,6 +63,15 @@ class ExternalAuth extends Auth {
|
|||||||
public async refreshAccessToken() {
|
public async refreshAccessToken() {
|
||||||
const callbackPayload = { callback: CALLBACK_SET_TOKEN };
|
const callbackPayload = { callback: CALLBACK_SET_TOKEN };
|
||||||
|
|
||||||
|
const callbackPromise = new Promise<RefreshTokenResponse>(
|
||||||
|
(resolve, reject) => {
|
||||||
|
window[CALLBACK_SET_TOKEN] = (success, data) =>
|
||||||
|
success ? resolve(data) : reject(data);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await 0;
|
||||||
|
|
||||||
if (window.externalApp) {
|
if (window.externalApp) {
|
||||||
window.externalApp.getExternalAuth(JSON.stringify(callbackPayload));
|
window.externalApp.getExternalAuth(JSON.stringify(callbackPayload));
|
||||||
} else {
|
} else {
|
||||||
@ -71,12 +80,7 @@ class ExternalAuth extends Auth {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokens = await new Promise<RefreshTokenResponse>(
|
const tokens = await callbackPromise;
|
||||||
(resolve, reject) => {
|
|
||||||
window[CALLBACK_SET_TOKEN] = (success, data) =>
|
|
||||||
success ? resolve(data) : reject(data);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.data.access_token = tokens.access_token;
|
this.data.access_token = tokens.access_token;
|
||||||
this.data.expires = tokens.expires_in * 1000 + Date.now();
|
this.data.expires = tokens.expires_in * 1000 + Date.now();
|
||||||
@ -85,6 +89,13 @@ class ExternalAuth extends Auth {
|
|||||||
public async revoke() {
|
public async revoke() {
|
||||||
const callbackPayload = { callback: CALLBACK_REVOKE_TOKEN };
|
const callbackPayload = { callback: CALLBACK_REVOKE_TOKEN };
|
||||||
|
|
||||||
|
const callbackPromise = new Promise((resolve, reject) => {
|
||||||
|
window[CALLBACK_REVOKE_TOKEN] = (success, data) =>
|
||||||
|
success ? resolve(data) : reject(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
await 0;
|
||||||
|
|
||||||
if (window.externalApp) {
|
if (window.externalApp) {
|
||||||
window.externalApp.revokeExternalAuth(JSON.stringify(callbackPayload));
|
window.externalApp.revokeExternalAuth(JSON.stringify(callbackPayload));
|
||||||
} else {
|
} else {
|
||||||
@ -93,10 +104,7 @@ class ExternalAuth extends Auth {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await callbackPromise;
|
||||||
window[CALLBACK_REVOKE_TOKEN] = (success, data) =>
|
|
||||||
success ? resolve(data) : reject(data);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,4 +49,6 @@ export default class DeviceTrigger extends Component {
|
|||||||
|
|
||||||
DeviceTrigger.defaultConfig = {
|
DeviceTrigger.defaultConfig = {
|
||||||
device_id: "",
|
device_id: "",
|
||||||
|
domain: "",
|
||||||
|
entity_id: "",
|
||||||
};
|
};
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
SYSTEM_GROUP_ID_USER,
|
SYSTEM_GROUP_ID_USER,
|
||||||
SYSTEM_GROUP_ID_ADMIN,
|
SYSTEM_GROUP_ID_ADMIN,
|
||||||
} from "../../../data/user";
|
} from "../../../data/user";
|
||||||
|
import { showSaveSuccessToast } from "../../../util/toast-saved-success";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
@ -150,6 +151,7 @@ class HaUserEditor extends LitElement {
|
|||||||
await updateUser(this.hass!, this.user!.id, {
|
await updateUser(this.hass!, this.user!.id, {
|
||||||
group_ids: [newGroup],
|
group_ids: [newGroup],
|
||||||
});
|
});
|
||||||
|
showSaveSuccessToast(this, this.hass!);
|
||||||
fireEvent(this, "reload-users");
|
fireEvent(this, "reload-users");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert(`Group update failed: ${err.message}`);
|
alert(`Group update failed: ${err.message}`);
|
||||||
|
@ -18,9 +18,13 @@ import format_time from "../../../common/datetime/format_time";
|
|||||||
@customElement("event-subscribe-card")
|
@customElement("event-subscribe-card")
|
||||||
class EventSubscribeCard extends LitElement {
|
class EventSubscribeCard extends LitElement {
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() private _eventType = "";
|
@property() private _eventType = "";
|
||||||
|
|
||||||
@property() private _subscribed?: () => void;
|
@property() private _subscribed?: () => void;
|
||||||
|
|
||||||
@property() private _events: Array<{ id: number; event: HassEvent }> = [];
|
@property() private _events: Array<{ id: number; event: HassEvent }> = [];
|
||||||
|
|
||||||
private _eventCount = 0;
|
private _eventCount = 0;
|
||||||
|
|
||||||
public disconnectedCallback() {
|
public disconnectedCallback() {
|
||||||
@ -33,7 +37,7 @@ class EventSubscribeCard extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<ha-card heading="Listen to events">
|
<ha-card header="Listen to events">
|
||||||
<form>
|
<form>
|
||||||
<paper-input
|
<paper-input
|
||||||
.label=${this._subscribed
|
.label=${this._subscribed
|
||||||
|
@ -15,7 +15,6 @@ import "@polymer/paper-tabs/paper-tab";
|
|||||||
import "@polymer/paper-tabs/paper-tabs";
|
import "@polymer/paper-tabs/paper-tabs";
|
||||||
|
|
||||||
import "../../components/ha-menu-button";
|
import "../../components/ha-menu-button";
|
||||||
import "../../resources/ha-style";
|
|
||||||
import "./developer-tools-router";
|
import "./developer-tools-router";
|
||||||
|
|
||||||
import scrollToTarget from "../../common/dom/scroll-to-target";
|
import scrollToTarget from "../../common/dom/scroll-to-target";
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-input/paper-textarea";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import "../../../components/ha-card";
|
|
||||||
import "../../../resources/ha-style";
|
|
||||||
import "../../../util/app-localstorage-document";
|
|
||||||
|
|
||||||
class HaPanelDevMqtt extends PolymerElement {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="ha-style">
|
|
||||||
:host {
|
|
||||||
-ms-user-select: initial;
|
|
||||||
-webkit-user-select: initial;
|
|
||||||
-moz-user-select: initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: 24px 0 32px;
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
|
|
||||||
mwc-button {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<app-localstorage-document key="panel-dev-mqtt-topic" data="{{topic}}">
|
|
||||||
</app-localstorage-document>
|
|
||||||
<app-localstorage-document
|
|
||||||
key="panel-dev-mqtt-payload"
|
|
||||||
data="{{payload}}"
|
|
||||||
>
|
|
||||||
</app-localstorage-document>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
<ha-card header="Publish a packet">
|
|
||||||
<div class="card-content">
|
|
||||||
<paper-input label="topic" value="{{topic}}"></paper-input>
|
|
||||||
|
|
||||||
<paper-textarea
|
|
||||||
always-float-label
|
|
||||||
label="Payload (template allowed)"
|
|
||||||
value="{{payload}}"
|
|
||||||
></paper-textarea>
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<mwc-button on-click="_publish">Publish</mwc-button>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
topic: String,
|
|
||||||
payload: String,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_publish() {
|
|
||||||
this.hass.callService("mqtt", "publish", {
|
|
||||||
topic: this.topic,
|
|
||||||
payload_template: this.payload,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("developer-tools-mqtt", HaPanelDevMqtt);
|
|
126
src/panels/developer-tools/mqtt/developer-tools-mqtt.ts
Normal file
126
src/panels/developer-tools/mqtt/developer-tools-mqtt.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
customElement,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
property,
|
||||||
|
CSSResultArray,
|
||||||
|
css,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
import "@polymer/paper-input/paper-textarea";
|
||||||
|
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
|
import { haStyle } from "../../../resources/styles";
|
||||||
|
import "../../../components/ha-card";
|
||||||
|
import "./mqtt-subscribe-card";
|
||||||
|
|
||||||
|
@customElement("developer-tools-mqtt")
|
||||||
|
class HaPanelDevMqtt extends LitElement {
|
||||||
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
|
@property() private topic = "";
|
||||||
|
|
||||||
|
@property() private payload = "";
|
||||||
|
|
||||||
|
private inited: boolean = false;
|
||||||
|
|
||||||
|
protected firstUpdated() {
|
||||||
|
if (localStorage && localStorage["panel-dev-mqtt-topic"]) {
|
||||||
|
this.topic = localStorage["panel-dev-mqtt-topic"];
|
||||||
|
}
|
||||||
|
if (localStorage && localStorage["panel-dev-mqtt-payload"]) {
|
||||||
|
this.payload = localStorage["panel-dev-mqtt-payload"];
|
||||||
|
}
|
||||||
|
this.inited = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<div class="content">
|
||||||
|
<ha-card header="Publish a packet">
|
||||||
|
<div class="card-content">
|
||||||
|
<paper-input
|
||||||
|
label="topic"
|
||||||
|
.value=${this.topic}
|
||||||
|
@value-changed=${this._handleTopic}
|
||||||
|
></paper-input>
|
||||||
|
|
||||||
|
<paper-textarea
|
||||||
|
always-float-label
|
||||||
|
label="Payload (template allowed)"
|
||||||
|
.value="${this.payload}"
|
||||||
|
@value-changed=${this._handlePayload}
|
||||||
|
></paper-textarea>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<mwc-button @click=${this._publish}>Publish</mwc-button>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
|
||||||
|
<mqtt-subscribe-card .hass=${this.hass}></mqtt-subscribe-card>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleTopic(ev: CustomEvent) {
|
||||||
|
this.topic = ev.detail.value;
|
||||||
|
if (localStorage && this.inited) {
|
||||||
|
localStorage["panel-dev-mqtt-topic"] = this.topic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handlePayload(ev: CustomEvent) {
|
||||||
|
this.payload = ev.detail.value;
|
||||||
|
if (localStorage && this.inited) {
|
||||||
|
localStorage["panel-dev-mqtt-payload"] = this.payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _publish(): void {
|
||||||
|
if (!this.hass) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.hass.callService("mqtt", "publish", {
|
||||||
|
topic: this.topic,
|
||||||
|
payload_template: this.payload,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultArray {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
-ms-user-select: initial;
|
||||||
|
-webkit-user-select: initial;
|
||||||
|
-moz-user-select: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 24px 0 32px;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
mwc-button {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
mqtt-subscribe-card {
|
||||||
|
display: block;
|
||||||
|
margin: 16px auto;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"developer-tools-mqtt": HaPanelDevMqtt;
|
||||||
|
}
|
||||||
|
}
|
153
src/panels/developer-tools/mqtt/mqtt-subscribe-card.ts
Normal file
153
src/panels/developer-tools/mqtt/mqtt-subscribe-card.ts
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
customElement,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
property,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
} from "lit-element";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import "../../../components/ha-card";
|
||||||
|
import format_time from "../../../common/datetime/format_time";
|
||||||
|
|
||||||
|
import { subscribeMQTTTopic, MQTTMessage } from "../../../data/mqtt";
|
||||||
|
|
||||||
|
@customElement("mqtt-subscribe-card")
|
||||||
|
class MqttSubscribeCard extends LitElement {
|
||||||
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
|
@property() private _topic = "";
|
||||||
|
|
||||||
|
@property() private _subscribed?: () => void;
|
||||||
|
|
||||||
|
@property() private _messages: Array<{
|
||||||
|
id: number;
|
||||||
|
message: MQTTMessage;
|
||||||
|
payload: string;
|
||||||
|
time: Date;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
private _messageCount = 0;
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
if (this._subscribed) {
|
||||||
|
this._subscribed();
|
||||||
|
this._subscribed = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<ha-card header="Listen to a topic">
|
||||||
|
<form>
|
||||||
|
<paper-input
|
||||||
|
.label=${this._subscribed
|
||||||
|
? "Listening to"
|
||||||
|
: "Topic to subscribe to"}
|
||||||
|
.disabled=${this._subscribed !== undefined}
|
||||||
|
.value=${this._topic}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></paper-input>
|
||||||
|
<mwc-button
|
||||||
|
.disabled=${this._topic === ""}
|
||||||
|
@click=${this._handleSubmit}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
${this._subscribed ? "Stop listening" : "Start listening"}
|
||||||
|
</mwc-button>
|
||||||
|
</form>
|
||||||
|
<div class="events">
|
||||||
|
${this._messages.map(
|
||||||
|
(msg) => html`
|
||||||
|
<div class="event">
|
||||||
|
Message ${msg.id} received on <b>${msg.message.topic}</b> at
|
||||||
|
${format_time(msg.time, this.hass!.language)}:
|
||||||
|
<pre>${msg.payload}</pre>
|
||||||
|
<div class="bottom">
|
||||||
|
QoS: ${msg.message.qos} - Retain:
|
||||||
|
${Boolean(msg.message.retain)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
this._topic = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleSubmit(): Promise<void> {
|
||||||
|
if (this._subscribed) {
|
||||||
|
this._subscribed();
|
||||||
|
this._subscribed = undefined;
|
||||||
|
} else {
|
||||||
|
this._subscribed = await subscribeMQTTTopic(
|
||||||
|
this.hass!,
|
||||||
|
this._topic,
|
||||||
|
(message) => this._handleMessage(message)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleMessage(message: MQTTMessage) {
|
||||||
|
const tail =
|
||||||
|
this._messages.length > 30 ? this._messages.slice(0, 29) : this._messages;
|
||||||
|
let payload: string;
|
||||||
|
try {
|
||||||
|
payload = JSON.stringify(JSON.parse(message.payload), null, 4);
|
||||||
|
} catch (e) {
|
||||||
|
payload = message.payload;
|
||||||
|
}
|
||||||
|
this._messages = [
|
||||||
|
{
|
||||||
|
payload,
|
||||||
|
message,
|
||||||
|
time: new Date(),
|
||||||
|
id: this._messageCount++,
|
||||||
|
},
|
||||||
|
...tail,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
form {
|
||||||
|
display: block;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
paper-input {
|
||||||
|
display: inline-block;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
.events {
|
||||||
|
margin: -16px 0;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
.event {
|
||||||
|
border-bottom: 1px solid var(--divider-color);
|
||||||
|
padding-bottom: 16px;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
.event:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
.bottom {
|
||||||
|
font-size: 80%;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"mqtt-subscribe-card": MqttSubscribeCard;
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ import {
|
|||||||
FORMAT_NUMBER,
|
FORMAT_NUMBER,
|
||||||
} from "../../../data/alarm_control_panel";
|
} from "../../../data/alarm_control_panel";
|
||||||
import { AlarmPanelCardConfig } from "./types";
|
import { AlarmPanelCardConfig } from "./types";
|
||||||
|
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
|
|
||||||
const ICONS = {
|
const ICONS = {
|
||||||
armed_away: "hass:shield-lock",
|
armed_away: "hass:shield-lock",
|
||||||
@ -144,6 +145,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
|||||||
? html``
|
? html``
|
||||||
: html`
|
: html`
|
||||||
<paper-input
|
<paper-input
|
||||||
|
id="alarmCode"
|
||||||
label="Alarm Code"
|
label="Alarm Code"
|
||||||
type="password"
|
type="password"
|
||||||
.value="${this._code}"
|
.value="${this._code}"
|
||||||
@ -198,11 +200,17 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleActionClick(e: MouseEvent): void {
|
private _handleActionClick(e: MouseEvent): void {
|
||||||
|
const input = this.shadowRoot!.querySelector(
|
||||||
|
"#alarmCode"
|
||||||
|
) as PaperInputElement;
|
||||||
|
const code =
|
||||||
|
this._code ||
|
||||||
|
(input && input.value && input.value.length > 0 ? input.value : "");
|
||||||
callAlarmAction(
|
callAlarmAction(
|
||||||
this.hass!,
|
this.hass!,
|
||||||
this._config!.entity,
|
this._config!.entity,
|
||||||
(e.currentTarget! as any).action,
|
(e.currentTarget! as any).action,
|
||||||
this._code!
|
code
|
||||||
);
|
);
|
||||||
this._code = "";
|
this._code = "";
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import { classMap } from "lit-html/directives/class-map";
|
|||||||
import computeStateDisplay from "../../../common/entity/compute_state_display";
|
import computeStateDisplay from "../../../common/entity/compute_state_display";
|
||||||
import computeStateName from "../../../common/entity/compute_state_name";
|
import computeStateName from "../../../common/entity/compute_state_name";
|
||||||
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
|
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
|
||||||
|
import relativeTime from "../../../common/datetime/relative_time";
|
||||||
|
|
||||||
import "../../../components/entity/state-badge";
|
import "../../../components/entity/state-badge";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
@ -24,7 +25,7 @@ import { LovelaceCard, LovelaceCardEditor } from "../types";
|
|||||||
import { longPress } from "../common/directives/long-press-directive";
|
import { longPress } from "../common/directives/long-press-directive";
|
||||||
import { processConfigEntities } from "../common/process-config-entities";
|
import { processConfigEntities } from "../common/process-config-entities";
|
||||||
import { handleClick } from "../common/handle-click";
|
import { handleClick } from "../common/handle-click";
|
||||||
import { GlanceCardConfig, ConfigEntity } from "./types";
|
import { GlanceCardConfig, GlanceConfigEntity } from "./types";
|
||||||
|
|
||||||
@customElement("hui-glance-card")
|
@customElement("hui-glance-card")
|
||||||
export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
||||||
@ -41,7 +42,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
@property() private _config?: GlanceCardConfig;
|
@property() private _config?: GlanceCardConfig;
|
||||||
|
|
||||||
private _configEntities?: ConfigEntity[];
|
private _configEntities?: GlanceConfigEntity[];
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return (
|
return (
|
||||||
@ -52,7 +53,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
public setConfig(config: GlanceCardConfig): void {
|
public setConfig(config: GlanceCardConfig): void {
|
||||||
this._config = { theme: "default", ...config };
|
this._config = { theme: "default", ...config };
|
||||||
const entities = processConfigEntities<ConfigEntity>(config.entities);
|
const entities = processConfigEntities<GlanceConfigEntity>(config.entities);
|
||||||
|
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
if (
|
if (
|
||||||
@ -207,11 +208,16 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
${this._config!.show_state !== false
|
${this._config!.show_state !== false
|
||||||
? html`
|
? html`
|
||||||
<div>
|
<div>
|
||||||
${computeStateDisplay(
|
${entityConf.show_last_changed
|
||||||
this.hass!.localize,
|
? relativeTime(
|
||||||
stateObj,
|
new Date(stateObj.last_changed),
|
||||||
this.hass!.language
|
this.hass!.localize
|
||||||
)}
|
)
|
||||||
|
: computeStateDisplay(
|
||||||
|
this.hass!.localize,
|
||||||
|
stateObj,
|
||||||
|
this.hass!.language
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
@ -220,12 +226,12 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleTap(ev: MouseEvent): void {
|
private _handleTap(ev: MouseEvent): void {
|
||||||
const config = (ev.currentTarget as any).entityConf as ConfigEntity;
|
const config = (ev.currentTarget as any).entityConf as GlanceConfigEntity;
|
||||||
handleClick(this, this.hass!, config, false);
|
handleClick(this, this.hass!, config, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleHold(ev: MouseEvent): void {
|
private _handleHold(ev: MouseEvent): void {
|
||||||
const config = (ev.currentTarget as any).entityConf as ConfigEntity;
|
const config = (ev.currentTarget as any).entityConf as GlanceConfigEntity;
|
||||||
handleClick(this, this.hass!, config, true);
|
handleClick(this, this.hass!, config, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
|
|||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon";
|
import "../../../components/ha-icon";
|
||||||
import "../components/hui-warning";
|
import "../components/hui-warning";
|
||||||
|
import "../components/hui-unavailable";
|
||||||
|
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { styleMap } from "lit-html/directives/style-map";
|
import { styleMap } from "lit-html/directives/style-map";
|
||||||
@ -94,6 +95,13 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
|||||||
return html`
|
return html`
|
||||||
${this.renderStyle()}
|
${this.renderStyle()}
|
||||||
<ha-card>
|
<ha-card>
|
||||||
|
${stateObj.state === "unavailable"
|
||||||
|
? html`
|
||||||
|
<hui-unavailable
|
||||||
|
.text="${this.hass.localize("state.default.unavailable")}"
|
||||||
|
></hui-unavailable>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
icon="hass:dots-vertical"
|
icon="hass:dots-vertical"
|
||||||
class="more-info"
|
class="more-info"
|
||||||
|
@ -15,6 +15,7 @@ import "../../map/ha-entity-marker";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
setupLeafletMap,
|
setupLeafletMap,
|
||||||
|
createTileLayer,
|
||||||
LeafletModuleType,
|
LeafletModuleType,
|
||||||
} from "../../../common/dom/setup-leaflet-map";
|
} from "../../../common/dom/setup-leaflet-map";
|
||||||
import computeStateDomain from "../../../common/entity/compute_state_domain";
|
import computeStateDomain from "../../../common/entity/compute_state_domain";
|
||||||
@ -194,6 +195,12 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
if (changedProps.has("hass")) {
|
if (changedProps.has("hass")) {
|
||||||
this._drawEntities();
|
this._drawEntities();
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
changedProps.has("_config") &&
|
||||||
|
changedProps.get("_config") !== undefined
|
||||||
|
) {
|
||||||
|
this.updateMap(changedProps.get("_config") as MapCardConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _mapEl(): HTMLDivElement {
|
private get _mapEl(): HTMLDivElement {
|
||||||
@ -210,6 +217,26 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
this._fitMap();
|
this._fitMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateMap(oldConfig: MapCardConfig): void {
|
||||||
|
const map = this._leafletMap;
|
||||||
|
const config = this._config;
|
||||||
|
const Leaflet = this.Leaflet;
|
||||||
|
if (!map || !config || !Leaflet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (config.dark_mode !== oldConfig.dark_mode) {
|
||||||
|
createTileLayer(Leaflet, config.dark_mode === true).addTo(map);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
config.entities !== oldConfig.entities ||
|
||||||
|
config.geo_location_sources !== oldConfig.geo_location_sources
|
||||||
|
) {
|
||||||
|
this._drawEntities();
|
||||||
|
}
|
||||||
|
map.invalidateSize();
|
||||||
|
this._fitMap();
|
||||||
|
}
|
||||||
|
|
||||||
private _fitMap(): void {
|
private _fitMap(): void {
|
||||||
if (!this._leafletMap || !this.Leaflet || !this._config || !this.hass) {
|
if (!this._leafletMap || !this.Leaflet || !this._config || !this.hass) {
|
||||||
return;
|
return;
|
||||||
|
@ -82,6 +82,10 @@ export interface ConfigEntity extends EntityConfig {
|
|||||||
hold_action?: ActionConfig;
|
hold_action?: ActionConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GlanceConfigEntity extends ConfigEntity {
|
||||||
|
show_last_changed?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GlanceCardConfig extends LovelaceCardConfig {
|
export interface GlanceCardConfig extends LovelaceCardConfig {
|
||||||
show_name?: boolean;
|
show_name?: boolean;
|
||||||
show_state?: boolean;
|
show_state?: boolean;
|
||||||
|
@ -3,6 +3,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
|||||||
import { navigate } from "../../../common/navigate";
|
import { navigate } from "../../../common/navigate";
|
||||||
import { toggleEntity } from "../../../../src/panels/lovelace/common/entity/toggle-entity";
|
import { toggleEntity } from "../../../../src/panels/lovelace/common/entity/toggle-entity";
|
||||||
import { ActionConfig } from "../../../data/lovelace";
|
import { ActionConfig } from "../../../data/lovelace";
|
||||||
|
import { forwardHaptic } from "../../../data/haptics";
|
||||||
|
|
||||||
export const handleClick = (
|
export const handleClick = (
|
||||||
node: HTMLElement,
|
node: HTMLElement,
|
||||||
@ -49,10 +50,12 @@ export const handleClick = (
|
|||||||
break;
|
break;
|
||||||
case "call-service": {
|
case "call-service": {
|
||||||
if (!actionConfig.service) {
|
if (!actionConfig.service) {
|
||||||
|
forwardHaptic("failure");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const [domain, service] = actionConfig.service.split(".", 2);
|
const [domain, service] = actionConfig.service.split(".", 2);
|
||||||
hass.callService(domain, service, actionConfig.service_data);
|
hass.callService(domain, service, actionConfig.service_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
forwardHaptic("light");
|
||||||
};
|
};
|
||||||
|
@ -66,15 +66,16 @@ export class HuiCardOptions extends LitElement {
|
|||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
icon="hass:dots-vertical"
|
icon="hass:dots-vertical"
|
||||||
slot="dropdown-trigger"
|
slot="dropdown-trigger"
|
||||||
|
aria-label="More options"
|
||||||
></paper-icon-button>
|
></paper-icon-button>
|
||||||
<paper-listbox slot="dropdown-content">
|
<paper-listbox slot="dropdown-content">
|
||||||
<paper-item @click="${this._moveCard}"
|
<paper-item @click="${this._moveCard}">
|
||||||
>${this.hass!.localize(
|
${this.hass!.localize(
|
||||||
"ui.panel.lovelace.editor.edit_card.move"
|
"ui.panel.lovelace.editor.edit_card.move"
|
||||||
)}</paper-item
|
)}</paper-item
|
||||||
>
|
>
|
||||||
<paper-item @click="${this._deleteCard}"
|
<paper-item @click="${this._deleteCard}">
|
||||||
>${this.hass!.localize(
|
${this.hass!.localize(
|
||||||
"ui.panel.lovelace.editor.edit_card.delete"
|
"ui.panel.lovelace.editor.edit_card.delete"
|
||||||
)}</paper-item
|
)}</paper-item
|
||||||
>
|
>
|
||||||
|
55
src/panels/lovelace/components/hui-unavailable.ts
Normal file
55
src/panels/lovelace/components/hui-unavailable.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import {
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
} from "lit-element";
|
||||||
|
|
||||||
|
@customElement("hui-unavailable")
|
||||||
|
export class HuiUnavailable extends LitElement {
|
||||||
|
@property() public text?: string;
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html`
|
||||||
|
<div class="disabled-overlay">
|
||||||
|
<div>${this.text}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
.disabled-overlay {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: var(--state-icon-unavailable-color);
|
||||||
|
opacity: 0.5;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled-overlay div {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
font-size: 50px;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
-ms-transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-unavailable": HuiUnavailable;
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ export interface EntityConfig {
|
|||||||
type?: string;
|
type?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
image?: string;
|
||||||
}
|
}
|
||||||
export interface DividerConfig {
|
export interface DividerConfig {
|
||||||
type: "divider";
|
type: "divider";
|
||||||
|
@ -568,7 +568,12 @@ class HUIRoot extends LitElement {
|
|||||||
unusedEntities.hass = this.hass!;
|
unusedEntities.hass = this.hass!;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
root.style.background = this.config.background || "";
|
if (this.config.background) {
|
||||||
|
unusedEntities.style.setProperty(
|
||||||
|
"--lovelace-background",
|
||||||
|
this.config.background
|
||||||
|
);
|
||||||
|
}
|
||||||
root.append(unusedEntities);
|
root.append(unusedEntities);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -597,8 +602,13 @@ class HUIRoot extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
view.hass = this.hass;
|
view.hass = this.hass;
|
||||||
root.style.background =
|
|
||||||
viewConfig.background || this.config.background || "";
|
const configBackground = viewConfig.background || this.config.background;
|
||||||
|
|
||||||
|
if (configBackground) {
|
||||||
|
view.style.setProperty("--lovelace-background", configBackground);
|
||||||
|
}
|
||||||
|
|
||||||
root.append(view);
|
root.append(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,9 @@ export class HuiUnusedEntities extends LitElement {
|
|||||||
private renderStyle(): TemplateResult {
|
private renderStyle(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
|
:host {
|
||||||
|
background: var(--lovelace-background);
|
||||||
|
}
|
||||||
#root {
|
#root {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -24,6 +24,7 @@ import { showEditCardDialog } from "./editor/card-editor/show-edit-card-dialog";
|
|||||||
import { HuiErrorCard } from "./cards/hui-error-card";
|
import { HuiErrorCard } from "./cards/hui-error-card";
|
||||||
|
|
||||||
import { computeRTL } from "../../common/util/compute_rtl";
|
import { computeRTL } from "../../common/util/compute_rtl";
|
||||||
|
import { processConfigEntities } from "./common/process-config-entities";
|
||||||
|
|
||||||
let editCodeLoaded = false;
|
let editCodeLoaded = false;
|
||||||
|
|
||||||
@ -120,6 +121,7 @@ export class HUIView extends LitElement {
|
|||||||
padding: 4px 4px 0;
|
padding: 4px 4px 0;
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
background: var(--lovelace-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
#badges {
|
#badges {
|
||||||
@ -262,10 +264,15 @@ export class HUIView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const elements: HUIView["_badges"] = [];
|
const elements: HUIView["_badges"] = [];
|
||||||
for (const entityId of config.badges) {
|
const badges = processConfigEntities(config.badges);
|
||||||
|
for (const badge of badges) {
|
||||||
const element = document.createElement("ha-state-label-badge");
|
const element = document.createElement("ha-state-label-badge");
|
||||||
|
const entityId = badge.entity;
|
||||||
element.hass = this.hass;
|
element.hass = this.hass;
|
||||||
element.state = this.hass!.states[entityId];
|
element.state = this.hass!.states[entityId];
|
||||||
|
element.name = badge.name;
|
||||||
|
element.icon = badge.icon;
|
||||||
|
element.image = badge.image;
|
||||||
elements.push({ element, entityId });
|
elements.push({ element, entityId });
|
||||||
root.appendChild(element);
|
root.appendChild(element);
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
css,
|
css,
|
||||||
CSSResult,
|
CSSResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
|
|
||||||
import { EntityRow, CastConfig } from "../entity-rows/types";
|
import { EntityRow, CastConfig } from "../entity-rows/types";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
@ -45,6 +46,11 @@ class HuiCastRow extends LitElement implements EntityRow {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const active =
|
||||||
|
this._castManager &&
|
||||||
|
this._castManager.status &&
|
||||||
|
this._config.view === this._castManager.status.lovelacePath;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-icon .icon="${this._config.icon}"></ha-icon>
|
<ha-icon .icon="${this._config.icon}"></ha-icon>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
@ -68,8 +74,8 @@ class HuiCastRow extends LitElement implements EntityRow {
|
|||||||
<google-cast-launcher></google-cast-launcher>
|
<google-cast-launcher></google-cast-launcher>
|
||||||
<mwc-button
|
<mwc-button
|
||||||
@click=${this._sendLovelace}
|
@click=${this._sendLovelace}
|
||||||
.unelevated=${this._castManager.status &&
|
class=${classMap({ inactive: !Boolean(active) })}
|
||||||
this._config.view === this._castManager.status.lovelacePath}
|
.unelevated=${active}
|
||||||
.disabled=${!this._castManager.status}
|
.disabled=${!this._castManager.status}
|
||||||
>
|
>
|
||||||
SHOW
|
SHOW
|
||||||
@ -124,7 +130,6 @@ class HuiCastRow extends LitElement implements EntityRow {
|
|||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
overflow: visible;
|
|
||||||
}
|
}
|
||||||
ha-icon {
|
ha-icon {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
@ -143,7 +148,6 @@ class HuiCastRow extends LitElement implements EntityRow {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.controls {
|
.controls {
|
||||||
margin-right: -0.57em;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@ -154,6 +158,9 @@ class HuiCastRow extends LitElement implements EntityRow {
|
|||||||
height: 24px;
|
height: 24px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
}
|
}
|
||||||
|
.inactive {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,6 +281,7 @@
|
|||||||
"weather": {
|
"weather": {
|
||||||
"clear-night": "Clear, night",
|
"clear-night": "Clear, night",
|
||||||
"cloudy": "Cloudy",
|
"cloudy": "Cloudy",
|
||||||
|
"exceptional": "Exceptional",
|
||||||
"fog": "Fog",
|
"fog": "Fog",
|
||||||
"hail": "Hail",
|
"hail": "Hail",
|
||||||
"lightning": "Lightning",
|
"lightning": "Lightning",
|
||||||
@ -493,7 +494,8 @@
|
|||||||
"common": {
|
"common": {
|
||||||
"loading": "Loading",
|
"loading": "Loading",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"save": "Save"
|
"save": "Save",
|
||||||
|
"successfully_saved": "Successfully saved"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"entity": {
|
"entity": {
|
||||||
|
7
src/util/toast-saved-success.ts
Normal file
7
src/util/toast-saved-success.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { showToast } from "./toast";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export const showSaveSuccessToast = (el: HTMLElement, hass: HomeAssistant) =>
|
||||||
|
showToast(el, {
|
||||||
|
message: hass!.localize("ui.common.successfully_saved"),
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user