Merge pull request #7174 from home-assistant/dev

This commit is contained in:
Bram Kragten 2020-09-30 17:28:42 +02:00 committed by GitHub
commit 6ef3d091e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 1176 additions and 445 deletions

View File

@ -93,6 +93,8 @@ export class HaEntityPicker extends LitElement {
@property() public entityFilter?: HaEntityPickerEntityFilterFunc; @property() public entityFilter?: HaEntityPickerEntityFilterFunc;
@property({ type: Boolean }) public hideClearIcon = false;
@property({ type: Boolean }) private _opened = false; @property({ type: Boolean }) private _opened = false;
@query("vaadin-combo-box-light") private _comboBox!: HTMLElement; @query("vaadin-combo-box-light") private _comboBox!: HTMLElement;
@ -204,7 +206,7 @@ export class HaEntityPicker extends LitElement {
autocorrect="off" autocorrect="off"
spellcheck="false" spellcheck="false"
> >
${this.value ${this.value && !this.hideClearIcon
? html` ? html`
<ha-icon-button <ha-icon-button
aria-label=${this.hass.localize( aria-label=${this.hass.localize(

View File

@ -12,6 +12,7 @@ import {
} from "lit-element"; } from "lit-element";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { nextRender } from "../common/util/render-status"; import { nextRender } from "../common/util/render-status";
import { getExternalConfig } from "../external_app/external_config";
import type { HomeAssistant } from "../types"; import type { HomeAssistant } from "../types";
type HLSModule = typeof import("hls.js"); type HLSModule = typeof import("hls.js");
@ -93,16 +94,22 @@ class HaHLSPlayer extends LitElement {
} }
private async _getUseExoPlayer(): Promise<boolean> { private async _getUseExoPlayer(): Promise<boolean> {
if (!this.hass!.auth.external || !this.allowExoPlayer) {
return false; return false;
} }
const externalConfig = await getExternalConfig(this.hass!.auth.external);
return externalConfig && externalConfig.hasExoPlayer;
}
private async _startHls(): Promise<void> { private async _startHls(): Promise<void> {
let hls: any;
const videoEl = this._videoEl; const videoEl = this._videoEl;
this._useExoPlayer = await this._getUseExoPlayer(); const playlist_url = this.url.replace("master_playlist", "playlist");
if (!this._useExoPlayer) { const useExoPlayerPromise = this._getUseExoPlayer();
hls = ((await import(/* webpackChunkName: "hls.js" */ "hls.js")) as any) const masterPlaylistPromise = fetch(this.url);
.default as HLSModule;
const hls = ((await import(
/* webpackChunkName: "hls.js" */ "hls.js"
)) as any).default as HLSModule;
let hlsSupported = hls.isSupported(); let hlsSupported = hls.isSupported();
if (!hlsSupported) { if (!hlsSupported) {
@ -116,16 +123,20 @@ class HaHLSPlayer extends LitElement {
); );
return; return;
} }
}
const url = this.url;
this._useExoPlayer = await useExoPlayerPromise;
let hevcRegexp: RegExp;
let masterPlaylist: string;
if (this._useExoPlayer) { if (this._useExoPlayer) {
this._renderHLSExoPlayer(url); hevcRegexp = /CODECS=".*?((hev1)|(hvc1))\..*?"/;
masterPlaylist = await (await masterPlaylistPromise).text();
}
if (this._useExoPlayer && hevcRegexp!.test(masterPlaylist!)) {
this._renderHLSExoPlayer(playlist_url);
} else if (hls.isSupported()) { } else if (hls.isSupported()) {
this._renderHLSPolyfill(videoEl, hls, url); this._renderHLSPolyfill(videoEl, hls, playlist_url);
} else { } else {
this._renderHLSNative(videoEl, url); this._renderHLSNative(videoEl, playlist_url);
} }
} }

View File

@ -31,9 +31,7 @@ interface DeprecatedIcon {
}; };
} }
const mdiDeprecatedIcons: DeprecatedIcon = { const mdiDeprecatedIcons: DeprecatedIcon = {};
scooter: { removeIn: "117", newName: "human-scooter" },
};
const chunks: Chunks = {}; const chunks: Chunks = {};

View File

@ -92,6 +92,7 @@ export interface LovelaceViewElement extends HTMLElement {
index?: number; index?: number;
cards?: Array<LovelaceCard | HuiErrorCard>; cards?: Array<LovelaceCard | HuiErrorCard>;
badges?: LovelaceBadge[]; badges?: LovelaceBadge[];
setConfig(config: LovelaceViewConfig): void;
} }
export interface ShowViewConfig { export interface ShowViewConfig {

View File

@ -136,6 +136,7 @@ export const configSections: { [name: string]: PageNavigation[] } = {
translationKey: "ui.panel.config.users.caption", translationKey: "ui.panel.config.users.caption",
iconPath: mdiBadgeAccountHorizontal, iconPath: mdiBadgeAccountHorizontal,
core: true, core: true,
advancedOnly: true,
}, },
], ],
general: [ general: [

View File

@ -22,6 +22,22 @@ import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url"; import { documentationUrl } from "../../../util/documentation-url";
import { PersonDetailDialogParams } from "./show-dialog-person-detail"; import { PersonDetailDialogParams } from "./show-dialog-person-detail";
import "../../../components/ha-formfield";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
import {
User,
SYSTEM_GROUP_ID_ADMIN,
deleteUser,
SYSTEM_GROUP_ID_USER,
updateUser,
} from "../../../data/user";
import {
showAlertDialog,
showPromptDialog,
showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
import { adminChangePassword } from "../../../data/auth";
import { showAddUserDialog } from "../users/show-dialog-add-user";
const includeDomains = ["device_tracker"]; const includeDomains = ["device_tracker"];
@ -39,6 +55,10 @@ class DialogPersonDetail extends LitElement {
@internalProperty() private _userId?: string; @internalProperty() private _userId?: string;
@internalProperty() private _user?: User;
@internalProperty() private _isAdmin?: boolean;
@internalProperty() private _deviceTrackers!: string[]; @internalProperty() private _deviceTrackers!: string[];
@internalProperty() private _picture!: string | null; @internalProperty() private _picture!: string | null;
@ -64,9 +84,15 @@ class DialogPersonDetail extends LitElement {
this._userId = this._params.entry.user_id || undefined; this._userId = this._params.entry.user_id || undefined;
this._deviceTrackers = this._params.entry.device_trackers || []; this._deviceTrackers = this._params.entry.device_trackers || [];
this._picture = this._params.entry.picture || null; this._picture = this._params.entry.picture || null;
this._user = this._userId
? this._params.users.find((user) => user.id === this._userId)
: undefined;
this._isAdmin = this._user?.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
} else { } else {
this._name = ""; this._name = "";
this._userId = undefined; this._userId = undefined;
this._user = undefined;
this._isAdmin = undefined;
this._deviceTrackers = []; this._deviceTrackers = [];
this._picture = null; this._picture = null;
} }
@ -115,15 +141,37 @@ class DialogPersonDetail extends LitElement {
@change=${this._pictureChanged} @change=${this._pictureChanged}
></ha-picture-upload> ></ha-picture-upload>
<ha-user-picker <ha-formfield
label="${this.hass!.localize( .label=${this.hass!.localize(
"ui.panel.config.person.detail.linked_user" "ui.panel.config.person.detail.allow_login"
)}" )}
.hass=${this.hass} >
.value=${this._userId} <ha-switch
.users=${this._params.users} @change=${this._allowLoginChanged}
@value-changed=${this._userChanged} .disabled=${this._user &&
></ha-user-picker> (this._user.id === this.hass.user?.id ||
this._user.system_generated ||
this._user.is_owner)}
.checked=${this._userId}
></ha-switch>
</ha-formfield>
${this._user
? html`<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.person.detail.admin"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.disabled=${this._user.system_generated ||
this._user.is_owner}
.checked=${this._isAdmin}
@change=${this._adminChanged}
>
</ha-switch>
</ha-formfield>`
: ""}
${this._deviceTrackersAvailable(this.hass) ${this._deviceTrackersAvailable(this.hass)
? html` ? html`
<p> <p>
@ -185,10 +233,21 @@ class DialogPersonDetail extends LitElement {
slot="secondaryAction" slot="secondaryAction"
class="warning" class="warning"
@click="${this._deleteEntry}" @click="${this._deleteEntry}"
.disabled=${this._submitting} .disabled=${(this._user && this._user.is_owner) ||
this._submitting}
> >
${this.hass!.localize("ui.panel.config.person.detail.delete")} ${this.hass!.localize("ui.panel.config.person.detail.delete")}
</mwc-button> </mwc-button>
${this._user && this.hass.user?.is_owner
? html`<mwc-button
slot="secondaryAction"
@click=${this._changePassword}
>
${this.hass.localize(
"ui.panel.config.users.editor.change_password"
)}
</mwc-button>`
: ""}
` `
: html``} : html``}
<mwc-button <mwc-button
@ -213,9 +272,47 @@ class DialogPersonDetail extends LitElement {
this._name = ev.detail.value; this._name = ev.detail.value;
} }
private _userChanged(ev: PolymerChangedEvent<string>) { private async _adminChanged(ev): Promise<void> {
this._error = undefined; this._isAdmin = ev.target.checked;
this._userId = ev.detail.value; }
private async _allowLoginChanged(ev): Promise<void> {
const target = ev.target;
if (target.checked) {
target.checked = false;
showAddUserDialog(this, {
userAddedCallback: async (user?: User) => {
if (user) {
target.checked = true;
this._user = user;
this._userId = user.id;
this._isAdmin = user.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
this._params?.refreshUsers();
}
},
name: this._name,
});
} else if (this._userId) {
if (
!(await showConfirmationDialog(this, {
text: this.hass!.localize(
"ui.panel.config.person.detail.confirm_delete_user",
"name",
this._name
),
confirmText: this.hass!.localize(
"ui.panel.config.person.detail.delete"
),
dismissText: this.hass!.localize("ui.common.cancel"),
}))
) {
target.checked = true;
return;
}
await deleteUser(this.hass, this._userId);
this._params?.refreshUsers();
this._userId = undefined;
}
} }
private _deviceTrackersChanged(ev: PolymerChangedEvent<string[]>) { private _deviceTrackersChanged(ev: PolymerChangedEvent<string[]>) {
@ -228,9 +325,70 @@ class DialogPersonDetail extends LitElement {
this._picture = (ev.target as HaPictureUpload).value; this._picture = (ev.target as HaPictureUpload).value;
} }
private async _changePassword() {
if (!this._user) {
return;
}
const credential = this._user.credentials.find(
(cred) => cred.type === "homeassistant"
);
if (!credential) {
showAlertDialog(this, {
title: "No Home Assistant credentials found.",
});
return;
}
const newPassword = await showPromptDialog(this, {
title: this.hass.localize("ui.panel.config.users.editor.change_password"),
inputType: "password",
inputLabel: this.hass.localize(
"ui.panel.config.users.editor.new_password"
),
});
if (!newPassword) {
return;
}
const confirmPassword = await showPromptDialog(this, {
title: this.hass.localize("ui.panel.config.users.editor.change_password"),
inputType: "password",
inputLabel: this.hass.localize(
"ui.panel.config.users.add_user.password_confirm"
),
});
if (!confirmPassword) {
return;
}
if (newPassword !== confirmPassword) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.users.add_user.password_not_match"
),
});
return;
}
await adminChangePassword(this.hass, this._user.id, newPassword);
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.users.editor.password_changed"
),
});
}
private async _updateEntry() { private async _updateEntry() {
this._submitting = true; this._submitting = true;
try { try {
if (
(this._userId && this._name !== this._params!.entry?.name) ||
this._isAdmin !== this._user?.group_ids.includes(SYSTEM_GROUP_ID_ADMIN)
) {
await updateUser(this.hass!, this._userId!, {
name: this._name.trim(),
group_ids: [
this._isAdmin ? SYSTEM_GROUP_ID_ADMIN : SYSTEM_GROUP_ID_USER,
],
});
this._params?.refreshUsers();
}
const values: PersonMutableParams = { const values: PersonMutableParams = {
name: this._name.trim(), name: this._name.trim(),
device_trackers: this._deviceTrackers, device_trackers: this._deviceTrackers,
@ -254,6 +412,9 @@ class DialogPersonDetail extends LitElement {
this._submitting = true; this._submitting = true;
try { try {
if (await this._params!.removeEntry()) { if (await this._params!.removeEntry()) {
if (this._params!.entry!.user_id) {
deleteUser(this.hass, this._params!.entry!.user_id);
}
this._params = undefined; this._params = undefined;
} }
} finally { } finally {
@ -275,6 +436,10 @@ class DialogPersonDetail extends LitElement {
ha-picture-upload { ha-picture-upload {
display: block; display: block;
} }
ha-formfield {
display: block;
padding: 16px 0;
}
ha-user-picker { ha-user-picker {
margin-top: 16px; margin-top: 16px;
} }

View File

@ -1,6 +1,7 @@
import { mdiPlus } from "@mdi/js"; import { mdiPlus } from "@mdi/js";
import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import "@material/mwc-fab";
import { import {
css, css,
CSSResult, CSSResult,
@ -223,6 +224,9 @@ class HaConfigPerson extends LitElement {
return false; return false;
} }
}, },
refreshUsers: () => {
this._usersLoad = fetchUsers(this.hass!);
},
}); });
} }

View File

@ -5,6 +5,7 @@ import { User } from "../../../data/user";
export interface PersonDetailDialogParams { export interface PersonDetailDialogParams {
entry?: Person; entry?: Person;
users: User[]; users: User[];
refreshUsers: () => void;
createEntry: (values: PersonMutableParams) => Promise<unknown>; createEntry: (values: PersonMutableParams) => Promise<unknown>;
updateEntry: (updates: Partial<PersonMutableParams>) => Promise<unknown>; updateEntry: (updates: Partial<PersonMutableParams>) => Promise<unknown>;
removeEntry: () => Promise<boolean>; removeEntry: () => Promise<boolean>;

View File

@ -50,15 +50,24 @@ export class DialogAddUser extends LitElement {
@internalProperty() private _isAdmin?: boolean; @internalProperty() private _isAdmin?: boolean;
@internalProperty() private _allowChangeName = true;
public showDialog(params: AddUserDialogParams) { public showDialog(params: AddUserDialogParams) {
this._params = params; this._params = params;
this._name = ""; this._name = this._params.name || "";
this._username = ""; this._username = "";
this._password = ""; this._password = "";
this._passwordConfirm = ""; this._passwordConfirm = "";
this._isAdmin = false; this._isAdmin = false;
this._error = undefined; this._error = undefined;
this._loading = false; this._loading = false;
if (this._params.name) {
this._allowChangeName = false;
this._maybePopulateUsername();
} else {
this._allowChangeName = true;
}
} }
protected firstUpdated(changedProperties: PropertyValues) { protected firstUpdated(changedProperties: PropertyValues) {
@ -84,10 +93,13 @@ export class DialogAddUser extends LitElement {
> >
<div> <div>
${this._error ? html` <div class="error">${this._error}</div> ` : ""} ${this._error ? html` <div class="error">${this._error}</div> ` : ""}
<paper-input ${this._allowChangeName
? html` <paper-input
class="name" class="name"
name="name" name="name"
.label=${this.hass.localize("ui.panel.config.users.add_user.name")} .label=${this.hass.localize(
"ui.panel.config.users.add_user.name"
)}
.value=${this._name} .value=${this._name}
required required
auto-validate auto-validate
@ -95,8 +107,8 @@ export class DialogAddUser extends LitElement {
.errorMessage=${this.hass.localize("ui.common.error_required")} .errorMessage=${this.hass.localize("ui.common.error_required")}
@value-changed=${this._handleValueChanged} @value-changed=${this._handleValueChanged}
@blur=${this._maybePopulateUsername} @blur=${this._maybePopulateUsername}
></paper-input> ></paper-input>`
: ""}
<paper-input <paper-input
class="username" class="username"
name="username" name="username"

View File

@ -47,7 +47,7 @@ class DialogUserDetail extends LitElement {
this._params = params; this._params = params;
this._error = undefined; this._error = undefined;
this._name = params.entry.name || ""; this._name = params.entry.name || "";
this._isAdmin = params.entry.group_ids[0] === SYSTEM_GROUP_ID_ADMIN; this._isAdmin = params.entry.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
await this.updateComplete; await this.updateComplete;
} }
@ -112,7 +112,7 @@ class DialogUserDetail extends LitElement {
.dir=${computeRTLDirection(this.hass)} .dir=${computeRTLDirection(this.hass)}
> >
<ha-switch <ha-switch
.disabled=${user.system_generated} .disabled=${user.system_generated || user.is_owner}
.checked=${this._isAdmin} .checked=${this._isAdmin}
@change=${this._adminChanged} @change=${this._adminChanged}
> >

View File

@ -3,6 +3,7 @@ import { User } from "../../../data/user";
export interface AddUserDialogParams { export interface AddUserDialogParams {
userAddedCallback: (user: User) => void; userAddedCallback: (user: User) => void;
name?: string;
} }
export const loadAddUserDialog = () => export const loadAddUserDialog = () =>

View File

@ -244,8 +244,7 @@ class HaLogbook extends LitElement {
line-height: 2em; line-height: 2em;
padding: 8px 16px; padding: 8px 16px;
box-sizing: border-box; box-sizing: border-box;
border-top: 1px solid border-top: 1px solid var(--divider-color);
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
} }
.entry.no-entity, .entry.no-entity,

View File

@ -3,8 +3,8 @@ import {
CSSResult, CSSResult,
customElement, customElement,
html, html,
LitElement,
internalProperty, internalProperty,
LitElement,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
@ -13,19 +13,23 @@ import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_elemen
import { computeDomain } from "../../../common/entity/compute_domain"; import { computeDomain } from "../../../common/entity/compute_domain";
import "../../../components/ha-card"; import "../../../components/ha-card";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { computeCardSize } from "../common/compute-card-size";
import { findEntities } from "../common/find-entites"; import { findEntities } from "../common/find-entites";
import { processConfigEntities } from "../common/process-config-entities"; import { processConfigEntities } from "../common/process-config-entities";
import "../components/hui-entities-toggle"; import "../components/hui-entities-toggle";
import { createHeaderFooterElement } from "../create-element/create-header-footer-element"; import { createHeaderFooterElement } from "../create-element/create-header-footer-element";
import { createRowElement } from "../create-element/create-row-element"; import { createRowElement } from "../create-element/create-row-element";
import { LovelaceRow } from "../entity-rows/types"; import {
EntityConfig,
LovelaceRow,
LovelaceRowConfig,
} from "../entity-rows/types";
import { import {
LovelaceCard, LovelaceCard,
LovelaceCardEditor, LovelaceCardEditor,
LovelaceHeaderFooter, LovelaceHeaderFooter,
} from "../types"; } from "../types";
import { EntitiesCardConfig, EntitiesCardEntityConfig } from "./types"; import { EntitiesCardConfig } from "./types";
import { computeCardSize } from "../common/compute-card-size";
@customElement("hui-entities-card") @customElement("hui-entities-card")
class HuiEntitiesCard extends LitElement implements LovelaceCard { class HuiEntitiesCard extends LitElement implements LovelaceCard {
@ -57,7 +61,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
private _hass?: HomeAssistant; private _hass?: HomeAssistant;
private _configEntities?: EntitiesCardEntityConfig[]; private _configEntities?: LovelaceRowConfig[];
private _showHeaderToggle?: boolean; private _showHeaderToggle?: boolean;
@ -115,7 +119,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
// Default value is show toggle if we can at least toggle 2 entities. // Default value is show toggle if we can at least toggle 2 entities.
let toggleable = 0; let toggleable = 0;
for (const rowConf of entities) { for (const rowConf of entities) {
if (!rowConf.entity) { if (!("entity" in rowConf)) {
continue; continue;
} }
toggleable += Number(DOMAINS_TOGGLE.has(computeDomain(rowConf.entity))); toggleable += Number(DOMAINS_TOGGLE.has(computeDomain(rowConf.entity)));
@ -188,7 +192,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
? html` ? html`
<ha-icon <ha-icon
class="icon" class="icon"
.icon="${this._config.icon}" .icon=${this._config.icon}
></ha-icon> ></ha-icon>
` `
: ""} : ""}
@ -198,10 +202,10 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
? html`` ? html``
: html` : html`
<hui-entities-toggle <hui-entities-toggle
.hass="${this._hass}" .hass=${this._hass}
.entities="${this._configEntities!.map( .entities=${(this._configEntities!.filter(
(conf) => conf.entity (conf) => "type" in conf
)}" ) as EntityConfig[]).map((conf) => conf.entity)}
></hui-entities-toggle> ></hui-entities-toggle>
`} `}
</div> </div>
@ -285,20 +289,20 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
`; `;
} }
private renderEntity(entityConf: EntitiesCardEntityConfig): TemplateResult { private renderEntity(entityConf: LovelaceRowConfig): TemplateResult {
const element = createRowElement( const element = createRowElement(
this._config!.state_color !("type" in entityConf) && this._config!.state_color
? { ? ({
state_color: true, state_color: true,
...entityConf, ...(entityConf as EntityConfig),
} } as EntityConfig)
: entityConf : entityConf
); );
if (this._hass) { if (this._hass) {
element.hass = this._hass; element.hass = this._hass;
} }
return html` <div>${element}</div> `; return html`<div>${element}</div>`;
} }
} }

View File

@ -3,7 +3,11 @@ import { FullCalendarView } from "../../../types";
import { Condition } from "../common/validate-condition"; import { Condition } from "../common/validate-condition";
import { HuiImage } from "../components/hui-image"; import { HuiImage } from "../components/hui-image";
import { LovelaceElementConfig } from "../elements/types"; import { LovelaceElementConfig } from "../elements/types";
import { EntityConfig, EntityFilterEntityConfig } from "../entity-rows/types"; import {
EntityConfig,
EntityFilterEntityConfig,
LovelaceRowConfig,
} from "../entity-rows/types";
import { LovelaceHeaderFooterConfig } from "../header-footer/types"; import { LovelaceHeaderFooterConfig } from "../header-footer/types";
export interface AlarmPanelCardConfig extends LovelaceCardConfig { export interface AlarmPanelCardConfig extends LovelaceCardConfig {
@ -60,7 +64,7 @@ export interface EntitiesCardConfig extends LovelaceCardConfig {
type: "entities"; type: "entities";
show_header_toggle?: boolean; show_header_toggle?: boolean;
title?: string; title?: string;
entities: Array<EntitiesCardEntityConfig | string>; entities: Array<LovelaceRowConfig | string>;
theme?: string; theme?: string;
icon?: string; icon?: string;
header?: LovelaceHeaderFooterConfig; header?: LovelaceHeaderFooterConfig;

View File

@ -56,6 +56,7 @@ export function hasConfigOrEntitiesChanged(
return entities.some( return entities.some(
(entity) => (entity) =>
"entity" in entity &&
oldHass.states[entity.entity] !== element.hass!.states[entity.entity] oldHass.states[entity.entity] !== element.hass!.states[entity.entity]
); );
} }

View File

@ -1,8 +1,10 @@
// Parse array of entity objects from config // Parse array of entity objects from config
import { isValidEntityId } from "../../../common/entity/valid_entity_id"; import { isValidEntityId } from "../../../common/entity/valid_entity_id";
import { EntityConfig } from "../entity-rows/types"; import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types";
export const processConfigEntities = <T extends EntityConfig>( export const processConfigEntities = <
T extends EntityConfig | LovelaceRowConfig
>(
entities: Array<T | string> entities: Array<T | string>
): T[] => { ): T[] => {
if (!entities || !Array.isArray(entities)) { if (!entities || !Array.isArray(entities)) {
@ -24,7 +26,7 @@ export const processConfigEntities = <T extends EntityConfig>(
if (typeof entityConf === "string") { if (typeof entityConf === "string") {
config = { entity: entityConf } as T; config = { entity: entityConf } as T;
} else if (typeof entityConf === "object" && !Array.isArray(entityConf)) { } else if (typeof entityConf === "object" && !Array.isArray(entityConf)) {
if (!entityConf.entity) { if (!("entity" in entityConf)) {
throw new Error( throw new Error(
`Entity object at position ${index} is missing entity field.` `Entity object at position ${index} is missing entity field.`
); );
@ -34,9 +36,11 @@ export const processConfigEntities = <T extends EntityConfig>(
throw new Error(`Invalid entity specified at position ${index}.`); throw new Error(`Invalid entity specified at position ${index}.`);
} }
if (!isValidEntityId(config.entity)) { if (!isValidEntityId((config as EntityConfig).entity!)) {
throw new Error( throw new Error(
`Invalid entity ID at position ${index}: ${config.entity}` `Invalid entity ID at position ${index}: ${
(config as EntityConfig).entity
}`
); );
} }

View File

@ -16,6 +16,7 @@ import {
LovelaceCard, LovelaceCard,
LovelaceCardConstructor, LovelaceCardConstructor,
LovelaceHeaderFooter, LovelaceHeaderFooter,
LovelaceRowConstructor,
} from "../types"; } from "../types";
const TIMEOUT = 2000; const TIMEOUT = 2000;
@ -39,7 +40,7 @@ interface CreateElementConfigTypes {
row: { row: {
config: LovelaceRowConfig; config: LovelaceRowConfig;
element: LovelaceRow; element: LovelaceRow;
constructor: unknown; constructor: LovelaceRowConstructor;
}; };
"header-footer": { "header-footer": {
config: LovelaceHeaderFooterConfig; config: LovelaceHeaderFooterConfig;

View File

@ -4,11 +4,14 @@ import "../entity-rows/hui-script-entity-row";
import "../entity-rows/hui-sensor-entity-row"; import "../entity-rows/hui-sensor-entity-row";
import "../entity-rows/hui-text-entity-row"; import "../entity-rows/hui-text-entity-row";
import "../entity-rows/hui-toggle-entity-row"; import "../entity-rows/hui-toggle-entity-row";
import { EntityConfig } from "../entity-rows/types"; import { LovelaceRowConfig } from "../entity-rows/types";
import "../special-rows/hui-attribute-row"; import "../special-rows/hui-attribute-row";
import "../special-rows/hui-button-row"; import "../special-rows/hui-button-row";
import "../special-rows/hui-call-service-row"; import "../special-rows/hui-call-service-row";
import { createLovelaceElement } from "./create-element-base"; import {
createLovelaceElement,
getLovelaceElementClass,
} from "./create-element-base";
const ALWAYS_LOADED_TYPES = new Set([ const ALWAYS_LOADED_TYPES = new Set([
"media-player-entity", "media-player-entity",
@ -74,7 +77,7 @@ const DOMAIN_TO_ELEMENT_TYPE = {
weather: "weather", weather: "weather",
}; };
export const createRowElement = (config: EntityConfig) => export const createRowElement = (config: LovelaceRowConfig) =>
createLovelaceElement( createLovelaceElement(
"row", "row",
config, config,
@ -83,3 +86,12 @@ export const createRowElement = (config: EntityConfig) =>
DOMAIN_TO_ELEMENT_TYPE, DOMAIN_TO_ELEMENT_TYPE,
undefined undefined
); );
export const getRowElementClass = (type: string) => {
return getLovelaceElementClass(
type,
"row",
ALWAYS_LOADED_TYPES,
LAZY_LOAD_TYPES
);
};

View File

@ -1,42 +1,42 @@
import { mdiHelpCircle } from "@mdi/js";
import deepFreeze from "deep-freeze"; import deepFreeze from "deep-freeze";
import { import {
css, css,
CSSResultArray, CSSResultArray,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty, PropertyValues,
query, query,
TemplateResult, TemplateResult,
PropertyValues,
} from "lit-element"; } from "lit-element";
import { mdiHelpCircle } from "@mdi/js";
import { fireEvent } from "../../../../common/dom/fire_event";
import { haStyleDialog } from "../../../../resources/styles";
import { showSaveSuccessToast } from "../../../../util/toast-saved-success";
import { addCard, replaceCard } from "../config-util";
import { getCardDocumentationURL } from "../get-card-documentation-url";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../../types";
import type { GUIModeChangedEvent } from "../types";
import type { ConfigChangedEvent, HuiCardEditor } from "./hui-card-editor";
import type { EditCardDialogParams } from "./show-edit-card-dialog";
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
import type { HASSDomEvent } from "../../../../common/dom/fire_event"; import type { HASSDomEvent } from "../../../../common/dom/fire_event";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/ha-circular-progress";
import "../../../../components/ha-dialog";
import "../../../../components/ha-header-bar";
import type { import type {
LovelaceCardConfig, LovelaceCardConfig,
LovelaceViewConfig, LovelaceViewConfig,
} from "../../../../data/lovelace"; } from "../../../../data/lovelace";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import "./hui-card-editor"; import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import { showSaveSuccessToast } from "../../../../util/toast-saved-success";
import { addCard, replaceCard } from "../config-util";
import { getCardDocumentationURL } from "../get-card-documentation-url";
import "../hui-element-editor";
import type {
ConfigChangedEvent,
HuiElementEditor,
} from "../hui-element-editor";
import type { GUIModeChangedEvent } from "../types";
import "./hui-card-preview"; import "./hui-card-preview";
import "../../../../components/ha-dialog"; import type { EditCardDialogParams } from "./show-edit-card-dialog";
import "../../../../components/ha-header-bar";
import "../../../../components/ha-circular-progress";
declare global { declare global {
// for fire event // for fire event
@ -65,7 +65,7 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
@internalProperty() private _guiModeAvailable? = true; @internalProperty() private _guiModeAvailable? = true;
@query("hui-card-editor") private _cardEditorEl?: HuiCardEditor; @query("hui-element-editor") private _cardEditorEl?: HuiElementEditor;
@internalProperty() private _GUImode = true; @internalProperty() private _GUImode = true;
@ -183,14 +183,14 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
</div> </div>
<div class="content"> <div class="content">
<div class="element-editor"> <div class="element-editor">
<hui-card-editor <hui-element-editor
.hass=${this.hass} .hass=${this.hass}
.lovelace=${this._params.lovelaceConfig} .lovelace=${this._params.lovelaceConfig}
.value=${this._cardConfig} .value=${this._cardConfig}
@config-changed=${this._handleConfigChanged} @config-changed=${this._handleConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged} @GUImode-changed=${this._handleGUIModeChanged}
@editor-save=${this._save} @editor-save=${this._save}
></hui-card-editor> ></hui-element-editor>
</div> </div>
<div class="element-preview"> <div class="element-preview">
<hui-card-preview <hui-card-preview
@ -364,6 +364,8 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
@media all and (min-width: 850px) { @media all and (min-width: 850px) {
ha-dialog { ha-dialog {
--mdc-dialog-min-width: 845px; --mdc-dialog-min-width: 845px;
--dialog-surface-top: 40px;
--mdc-dialog-max-height: calc(100% - 72px);
} }
} }
@ -402,6 +404,9 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
ha-dialog { ha-dialog {
--mdc-dialog-max-width: calc(100% - 32px); --mdc-dialog-max-width: calc(100% - 32px);
--mdc-dialog-min-width: 1000px; --mdc-dialog-min-width: 1000px;
--dialog-surface-position: fixed;
--dialog-surface-top: 40px;
--mdc-dialog-max-height: calc(100% - 72px);
} }
.content { .content {

View File

@ -1,7 +1,6 @@
import { html } from "lit-element"; import { css } from "lit-element";
export const configElementStyle = html` export const configElementStyle = css`
<style>
ha-switch { ha-switch {
padding: 16px 0; padding: 16px 0;
} }
@ -15,5 +14,4 @@ export const configElementStyle = html`
.suffix { .suffix {
margin: 0 8px; margin: 0 8px;
} }
</style>
`; `;

View File

@ -3,14 +3,15 @@ import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { import {
css, css,
CSSResult, CSSResultArray,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { array, assert, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-icon"; import "../../../../components/ha-icon";
@ -20,7 +21,6 @@ import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { assert, object, string, optional, array } from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -68,7 +68,6 @@ export class HuiAlarmPanelCardEditor extends LitElement
const states = ["arm_home", "arm_away", "arm_night", "arm_custom_bypass"]; const states = ["arm_home", "arm_away", "arm_night", "arm_custom_bypass"];
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -128,8 +127,10 @@ export class HuiAlarmPanelCardEditor extends LitElement
`; `;
} }
static get styles(): CSSResult { static get styles(): CSSResultArray {
return css` return [
configElementStyle,
css`
.states { .states {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -143,7 +144,8 @@ export class HuiAlarmPanelCardEditor extends LitElement
ha-icon { ha-icon {
padding-top: 12px; padding-top: 12px;
} }
`; `,
];
} }
private _stateRemoved(ev: EntitiesEditorEvent): void { private _stateRemoved(ev: EntitiesEditorEvent): void {

View File

@ -1,5 +1,6 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -109,7 +110,6 @@ export class HuiButtonCardEditor extends LitElement
const dir = computeRTLDirection(this.hass!); const dir = computeRTLDirection(this.hass!);
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -293,6 +293,10 @@ export class HuiButtonCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: newConfig }); fireEvent(this, "config-changed", { config: newConfig });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,4 +1,5 @@
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -68,7 +69,6 @@ export class HuiCalendarCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<div class="side-by-side"> <div class="side-by-side">
<paper-input <paper-input
@ -174,6 +174,10 @@ export class HuiCalendarCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -2,28 +2,30 @@ import "@polymer/paper-tabs";
import "@polymer/paper-tabs/paper-tab"; import "@polymer/paper-tabs/paper-tab";
import { import {
css, css,
CSSResult, CSSResultArray,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
query, query,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { any, array, assert, object, optional, string } from "superstruct";
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event"; import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { LovelaceConfig } from "../../../../data/lovelace"; import { LovelaceCardConfig, LovelaceConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { ConditionalCardConfig } from "../../cards/types"; import { ConditionalCardConfig } from "../../cards/types";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import {
ConfigChangedEvent,
HuiCardEditor,
} from "../card-editor/hui-card-editor";
import "../card-editor/hui-card-picker"; import "../card-editor/hui-card-picker";
import "../hui-element-editor";
import type {
ConfigChangedEvent,
HuiElementEditor,
} from "../hui-element-editor";
import { GUIModeChangedEvent } from "../types"; import { GUIModeChangedEvent } from "../types";
import { string, any, object, optional, array, assert } from "superstruct"; import { configElementStyle } from "./config-elements-style";
const conditionStruct = object({ const conditionStruct = object({
entity: string(), entity: string(),
@ -51,7 +53,7 @@ export class HuiConditionalCardEditor extends LitElement
@internalProperty() private _cardTab = false; @internalProperty() private _cardTab = false;
@query("hui-card-editor") private _cardEditorEl?: HuiCardEditor; @query("hui-element-editor") private _cardEditorEl?: HuiElementEditor;
public setConfig(config: ConditionalCardConfig): void { public setConfig(config: ConditionalCardConfig): void {
assert(config, cardConfigStruct); assert(config, cardConfigStruct);
@ -106,13 +108,13 @@ export class HuiConditionalCardEditor extends LitElement
)}</mwc-button )}</mwc-button
> >
</div> </div>
<hui-card-editor <hui-element-editor
.hass=${this.hass} .hass=${this.hass}
.value=${this._config.card} .value=${this._config.card}
.lovelace=${this.lovelace} .lovelace=${this.lovelace}
@config-changed=${this._handleCardChanged} @config-changed=${this._handleCardChanged}
@GUImode-changed=${this._handleGUIModeChanged} @GUImode-changed=${this._handleGUIModeChanged}
></hui-card-editor> ></hui-element-editor>
` `
: html` : html`
<hui-card-picker <hui-card-picker
@ -227,7 +229,10 @@ export class HuiConditionalCardEditor extends LitElement
if (!this._config) { if (!this._config) {
return; return;
} }
this._config = { ...this._config, card: ev.detail.config }; this._config = {
...this._config,
card: ev.detail.config as LovelaceCardConfig,
};
this._guiModeAvailable = ev.detail.guiModeAvailable; this._guiModeAvailable = ev.detail.guiModeAvailable;
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
@ -292,8 +297,10 @@ export class HuiConditionalCardEditor extends LitElement
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult { static get styles(): CSSResultArray {
return css` return [
configElementStyle,
css`
paper-tabs { paper-tabs {
--paper-tabs-selection-bar-color: var(--primary-color); --paper-tabs-selection-bar-color: var(--primary-color);
--paper-tab-ink: var(--primary-color); --paper-tab-ink: var(--primary-color);
@ -337,7 +344,8 @@ export class HuiConditionalCardEditor extends LitElement
.gui-mode-button { .gui-mode-button {
margin-right: auto; margin-right: auto;
} }
`; `,
];
} }
} }

View File

@ -2,11 +2,14 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { import {
css,
CSSResultArray,
customElement, customElement,
html, html,
internalProperty, internalProperty,
LitElement, LitElement,
property, property,
query,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { import {
@ -18,7 +21,7 @@ import {
string, string,
union, union,
} from "superstruct"; } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/entity/state-badge"; import "../../../../components/entity/state-badge";
import "../../../../components/ha-card"; import "../../../../components/ha-card";
@ -26,22 +29,33 @@ import "../../../../components/ha-formfield";
import "../../../../components/ha-icon"; import "../../../../components/ha-icon";
import "../../../../components/ha-switch"; import "../../../../components/ha-switch";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { import { EntitiesCardConfig } from "../../cards/types";
EntitiesCardConfig,
EntitiesCardEntityConfig,
} from "../../cards/types";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { LovelaceRowConfig } from "../../entity-rows/types";
import { headerFooterConfigStructs } from "../../header-footer/types"; import { headerFooterConfigStructs } from "../../header-footer/types";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import "../hui-detail-editor-base";
import { HuiElementEditor } from "../hui-element-editor";
import "../hui-entities-card-row-editor"; import "../hui-entities-card-row-editor";
import { processEditorEntities } from "../process-editor-entities"; import { processEditorEntities } from "../process-editor-entities";
import { import {
EditorTarget, EditorTarget,
entitiesConfigStruct, entitiesConfigStruct,
EntitiesEditorEvent, EntitiesEditorEvent,
GUIModeChangedEvent,
} from "../types"; } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
interface EditRowEvent {
index: number;
}
declare global {
interface HASSDomEvents {
"edit-row": EditRowEvent;
}
}
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
title: optional(union([string(), boolean()])), title: optional(union([string(), boolean()])),
@ -60,7 +74,17 @@ export class HuiEntitiesCardEditor extends LitElement
@internalProperty() private _config?: EntitiesCardConfig; @internalProperty() private _config?: EntitiesCardConfig;
@internalProperty() private _configEntities?: EntitiesCardEntityConfig[]; @internalProperty() private _configEntities?: LovelaceRowConfig[];
@internalProperty() private _editRowConfig?: LovelaceRowConfig;
@internalProperty() private _editRowIndex?: number;
@internalProperty() private _editRowGuiModeAvailable? = true;
@internalProperty() private _editRowGuiMode? = true;
@query("hui-element-editor") private _cardEditorEl?: HuiElementEditor;
public setConfig(config: EntitiesCardConfig): void { public setConfig(config: EntitiesCardConfig): void {
assert(config, cardConfigStruct); assert(config, cardConfigStruct);
@ -81,8 +105,32 @@ export class HuiEntitiesCardEditor extends LitElement
return html``; return html``;
} }
if (this._editRowConfig) {
return html`
<hui-detail-editor-base
.hass=${this.hass}
.guiModeAvailable=${this._editRowGuiModeAvailable}
.guiMode=${this._editRowGuiMode}
@toggle-gui-mode=${this._toggleMode}
@go-back=${this._goBack}
>
<span slot="title"
>${this.hass.localize(
"ui.panel.lovelace.editor.card.entities.entity_row_editor"
)}</span
>
<hui-element-editor
.hass=${this.hass}
.value=${this._editRowConfig}
elementType="row"
@config-changed=${this._handleEntityRowConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged}
></hui-element-editor>
</hui-detail-editor-base>
`;
}
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -127,11 +175,11 @@ export class HuiEntitiesCardEditor extends LitElement
</ha-formfield> </ha-formfield>
</div> </div>
</div> </div>
<hui-entities-card-row-editor <hui-entities-card-row-editor
.hass=${this.hass} .hass=${this.hass}
.entities=${this._configEntities} .entities=${this._configEntities}
@entities-changed=${this._valueChanged} @entities-changed=${this._valueChanged}
@edit-row=${this._editRow}
></hui-entities-card-row-editor> ></hui-entities-card-row-editor>
`; `;
} }
@ -169,6 +217,65 @@ export class HuiEntitiesCardEditor extends LitElement
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
private _editRow(ev: HASSDomEvent<EditRowEvent>): void {
this._editRowIndex = ev.detail.index;
this._editRowConfig = this._configEntities![this._editRowIndex];
}
private _goBack(): void {
this._editRowIndex = undefined;
this._editRowConfig = undefined;
this._editRowGuiModeAvailable = true;
this._editRowGuiMode = true;
}
private _toggleMode(): void {
this._cardEditorEl?.toggleMode();
}
private _handleEntityRowConfigChanged(ev: CustomEvent): void {
ev.stopPropagation();
const value = ev.detail.config as LovelaceRowConfig;
this._editRowGuiModeAvailable = ev.detail.guiModeAvailable;
const newConfigEntities = this._configEntities!.concat();
if (!value) {
newConfigEntities.splice(this._editRowIndex!, 1);
this._goBack();
} else {
newConfigEntities[this._editRowIndex!] = value;
}
this._editRowConfig = value;
this._config = { ...this._config!, entities: newConfigEntities };
this._configEntities = processEditorEntities(this._config!.entities);
fireEvent(this, "config-changed", { config: this._config! });
}
private _handleGUIModeChanged(ev: HASSDomEvent<GUIModeChangedEvent>): void {
ev.stopPropagation();
this._editRowGuiMode = ev.detail.guiMode;
this._editRowGuiModeAvailable = ev.detail.guiModeAvailable;
}
static get styles(): CSSResultArray {
return [
configElementStyle,
css`
.edit-entity-row-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 18px;
}
`,
];
}
} }
declare global { declare global {

View File

@ -1,5 +1,6 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -75,7 +76,6 @@ export class HuiEntityCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -186,6 +186,10 @@ export class HuiEntityCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,7 +1,7 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
css, css,
CSSResult, CSSResultArray,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -81,7 +81,6 @@ export class HuiGaugeCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -197,8 +196,10 @@ export class HuiGaugeCardEditor extends LitElement
`; `;
} }
static get styles(): CSSResult { static get styles(): CSSResultArray {
return css` return [
configElementStyle,
css`
.severity { .severity {
display: none; display: none;
width: 100%; width: 100%;
@ -213,7 +214,8 @@ export class HuiGaugeCardEditor extends LitElement
ha-switch[checked] ~ .severity { ha-switch[checked] ~ .severity {
display: flex; display: flex;
} }
`; `,
];
} }
private _toggleSeverity(ev: EntitiesEditorEvent): void { private _toggleSeverity(ev: EntitiesEditorEvent): void {

View File

@ -0,0 +1,173 @@
import "@polymer/paper-input/paper-input";
import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { assert } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeDomain } from "../../../../common/entity/compute_domain";
import { stateIcon } from "../../../../common/entity/state_icon";
import "../../../../components/ha-formfield";
import "../../../../components/ha-icon-input";
import "../../../../components/ha-switch";
import { HomeAssistant } from "../../../../types";
import { EntitiesCardEntityConfig } from "../../cards/types";
import "../../components/hui-action-editor";
import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor";
import { LovelaceRowEditor } from "../../types";
import {
EditorTarget,
entitiesConfigStruct,
EntitiesEditorEvent,
} from "../types";
import { configElementStyle } from "./config-elements-style";
const SecondaryInfoValues: { [key: string]: { domains?: string[] } } = {
"entity-id": {},
"last-changed": {},
"last-triggered": { domains: ["automation", "script"] },
position: { domains: ["cover"] },
"tilt-position": { domains: ["cover"] },
brightness: { domains: ["light"] },
};
@customElement("hui-generic-entity-row-editor")
export class HuiGenericEntityRowEditor extends LitElement
implements LovelaceRowEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@internalProperty() private _config?: EntitiesCardEntityConfig;
public setConfig(config: EntitiesCardEntityConfig): void {
assert(config, entitiesConfigStruct);
this._config = config;
}
get _entity(): string {
return this._config!.entity || "";
}
get _name(): string {
return this._config!.name || "";
}
get _icon(): string {
return this._config!.icon || "";
}
get _secondary_info(): string {
return this._config!.secondary_info || "";
}
protected render(): TemplateResult {
if (!this.hass || !this._config) {
return html``;
}
const domain = computeDomain(this._config.entity);
return html`
<div class="card-config">
<ha-entity-picker
allow-custom-entity
.hass=${this.hass}
.value=${this._config.entity}
.configValue=${"entity"}
@change=${this._valueChanged}
></ha-entity-picker>
<div class="side-by-side">
<paper-input
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.card.generic.name"
)}
.value=${this._config.name}
.configValue=${"name"}
@value-changed=${this._valueChanged}
></paper-input>
<ha-icon-input
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.card.generic.icon"
)}
.value=${this._config.icon}
.placeholder=${stateIcon(this.hass!.states[this._config.entity])}
.configValue=${"icon"}
@value-changed=${this._valueChanged}
></ha-icon-input>
</div>
<paper-dropdown-menu .label=${"Secondary Info"}>
<paper-listbox
slot="dropdown-content"
attr-for-selected="value"
.selected=${this._config.secondary_info || "none"}
.configValue=${"secondary_info"}
@iron-select=${this._valueChanged}
>
<paper-item value=""
>${this.hass!.localize(
"ui.panel.lovelace.editor.card.entities.secondary_info_values.none"
)}</paper-item
>
${Object.keys(SecondaryInfoValues).map((info) => {
if (
!("domains" in SecondaryInfoValues[info]) ||
("domains" in SecondaryInfoValues[info] &&
SecondaryInfoValues[info].domains!.includes(domain))
) {
return html`
<paper-item .value=${info}
>${this.hass!.localize(
`ui.panel.lovelace.editor.card.entities.secondary_info_values.${info}`
)}</paper-item
>
`;
}
return "";
})}
</paper-listbox>
</paper-dropdown-menu>
</div>
`;
}
private _valueChanged(ev: EntitiesEditorEvent): void {
if (!this._config || !this.hass) {
return;
}
const target = ev.target! as EditorTarget;
const value = target.value || ev.detail?.item?.value;
if (this[`_${target.configValue}`] === value) {
return;
}
if (target.configValue) {
if (value === "" || !value) {
this._config = { ...this._config };
delete this._config[target.configValue!];
} else {
this._config = {
...this._config,
[target.configValue!]: value,
};
}
}
fireEvent(this, "config-changed", { config: this._config });
}
static get styles(): CSSResult {
return configElementStyle;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-generic-entity-row-editor": HuiGenericEntityRowEditor;
}
}

View File

@ -2,19 +2,31 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import {
array,
assert,
boolean,
number,
object,
optional,
string,
union,
} from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/entity/state-badge"; import "../../../../components/entity/state-badge";
import "../../../../components/ha-card"; import "../../../../components/ha-card";
import "../../../../components/ha-formfield";
import "../../../../components/ha-icon"; import "../../../../components/ha-icon";
import "../../../../components/ha-switch"; import "../../../../components/ha-switch";
import "../../../../components/ha-formfield";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { ConfigEntity, GlanceCardConfig } from "../../cards/types"; import { ConfigEntity, GlanceCardConfig } from "../../cards/types";
import "../../components/hui-entity-editor"; import "../../components/hui-entity-editor";
@ -27,17 +39,6 @@ import {
EntitiesEditorEvent, EntitiesEditorEvent,
} from "../types"; } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import {
string,
union,
object,
optional,
number,
boolean,
assert,
array,
} from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -102,7 +103,6 @@ export class HuiGlanceCardEditor extends LitElement
const dir = computeRTLDirection(this.hass!); const dir = computeRTLDirection(this.hass!);
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -234,6 +234,10 @@ export class HuiGlanceCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,31 +1,32 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import {
array,
assert,
number,
object,
optional,
string,
union,
} from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { HistoryGraphCardConfig } from "../../cards/types"; import { HistoryGraphCardConfig } from "../../cards/types";
import { EntityId } from "../../common/structs/is-entity-id";
import "../../components/hui-entity-editor"; import "../../components/hui-entity-editor";
import { EntityConfig } from "../../entity-rows/types"; import { EntityConfig } from "../../entity-rows/types";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { processEditorEntities } from "../process-editor-entities"; import { processEditorEntities } from "../process-editor-entities";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import {
assert,
union,
optional,
string,
object,
array,
number,
} from "superstruct";
import { EntityId } from "../../common/structs/is-entity-id";
const entitiesConfigStruct = union([ const entitiesConfigStruct = union([
object({ object({
@ -80,7 +81,6 @@ export class HuiHistoryGraphCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -156,6 +156,10 @@ export class HuiHistoryGraphCardEditor extends LitElement
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,12 +1,14 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { assert, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
@ -15,7 +17,6 @@ import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { string, object, optional, assert } from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -56,7 +57,6 @@ export class HuiHumidifierCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -110,6 +110,10 @@ export class HuiHumidifierCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,19 +1,20 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { assert, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { IframeCardConfig } from "../../cards/types"; import { IframeCardConfig } from "../../cards/types";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { string, assert, object, optional } from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -52,7 +53,6 @@ export class HuiIframeCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -110,6 +110,10 @@ export class HuiIframeCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,5 +1,6 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -84,7 +85,6 @@ export class HuiLightCardEditor extends LitElement
]; ];
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -183,6 +183,10 @@ export class HuiLightCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,15 +1,27 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
css, css,
CSSResult, CSSResultArray,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import {
array,
assert,
boolean,
number,
object,
optional,
string,
} from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/ha-formfield";
import "../../../../components/ha-switch";
import { PolymerChangedEvent } from "../../../../polymer-types"; import { PolymerChangedEvent } from "../../../../polymer-types";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { MapCardConfig } from "../../cards/types"; import { MapCardConfig } from "../../cards/types";
@ -23,19 +35,7 @@ import {
entitiesConfigStruct, entitiesConfigStruct,
EntitiesEditorEvent, EntitiesEditorEvent,
} from "../types"; } from "../types";
import "../../../../components/ha-switch";
import "../../../../components/ha-formfield";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import {
string,
optional,
object,
number,
boolean,
array,
assert,
} from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -94,7 +94,6 @@ export class HuiMapCardEditor extends LitElement implements LovelaceCardEditor {
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -216,12 +215,15 @@ export class HuiMapCardEditor extends LitElement implements LovelaceCardEditor {
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult { static get styles(): CSSResultArray {
return css` return [
configElementStyle,
css`
.geo_location_sources { .geo_location_sources {
padding-left: 20px; padding-left: 20px;
} }
`; `,
];
} }
} }

View File

@ -1,13 +1,15 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import "@polymer/paper-input/paper-textarea"; import "@polymer/paper-input/paper-textarea";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { assert, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { MarkdownCardConfig } from "../../cards/types"; import { MarkdownCardConfig } from "../../cards/types";
@ -15,7 +17,6 @@ import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { string, assert, object, optional } from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -54,7 +55,6 @@ export class HuiMarkdownCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -116,6 +116,10 @@ export class HuiMarkdownCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,5 +1,6 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -62,7 +63,6 @@ export class HuiPictureCardEditor extends LitElement
const actions = ["navigate", "url", "call-service", "none"]; const actions = ["navigate", "url", "call-service", "none"];
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -133,6 +133,10 @@ export class HuiPictureCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -3,6 +3,7 @@ import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -108,7 +109,6 @@ export class HuiPictureEntityCardEditor extends LitElement
const dir = computeRTLDirection(this.hass!); const dir = computeRTLDirection(this.hass!);
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -293,6 +293,10 @@ export class HuiPictureEntityCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -3,6 +3,7 @@ import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -111,7 +112,6 @@ export class HuiPictureGlanceCardEditor extends LitElement
const views = ["auto", "live"]; const views = ["auto", "live"];
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -257,6 +257,10 @@ export class HuiPictureGlanceCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,12 +1,14 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { assert, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-icon"; import "../../../../components/ha-icon";
@ -16,7 +18,6 @@ import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { assert, object, string, optional } from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -57,7 +58,6 @@ export class HuiPlantStatusCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -113,6 +113,10 @@ export class HuiPlantStatusCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -3,6 +3,7 @@ import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -90,7 +91,6 @@ export class HuiSensorCardEditor extends LitElement
const graphs = ["line", "none"]; const graphs = ["line", "none"];
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -238,6 +238,10 @@ export class HuiSensorCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,4 +1,3 @@
import "../../../../components/ha-icon-button";
import "@polymer/paper-tabs"; import "@polymer/paper-tabs";
import "@polymer/paper-tabs/paper-tab"; import "@polymer/paper-tabs/paper-tab";
import { import {
@ -6,24 +5,26 @@ import {
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
query, query,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { any, array, assert, object, optional, string } from "superstruct";
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event"; import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
import { LovelaceConfig } from "../../../../data/lovelace"; import "../../../../components/ha-icon-button";
import { LovelaceCardConfig, LovelaceConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { StackCardConfig } from "../../cards/types"; import { StackCardConfig } from "../../cards/types";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import {
ConfigChangedEvent,
HuiCardEditor,
} from "../card-editor/hui-card-editor";
import "../card-editor/hui-card-picker"; import "../card-editor/hui-card-picker";
import "../hui-element-editor";
import type {
ConfigChangedEvent,
HuiElementEditor,
} from "../hui-element-editor";
import { GUIModeChangedEvent } from "../types"; import { GUIModeChangedEvent } from "../types";
import { assert, object, string, array, any, optional } from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -46,7 +47,7 @@ export class HuiStackCardEditor extends LitElement
@internalProperty() private _guiModeAvailable? = true; @internalProperty() private _guiModeAvailable? = true;
@query("hui-card-editor") private _cardEditorEl?: HuiCardEditor; @query("hui-element-editor") private _cardEditorEl?: HuiElementEditor;
public setConfig(config: Readonly<StackCardConfig>): void { public setConfig(config: Readonly<StackCardConfig>): void {
assert(config, cardConfigStruct); assert(config, cardConfigStruct);
@ -128,13 +129,13 @@ export class HuiStackCardEditor extends LitElement
></ha-icon-button> ></ha-icon-button>
</div> </div>
<hui-card-editor <hui-element-editor
.hass=${this.hass} .hass=${this.hass}
.value=${this._config.cards[selected]} .value=${this._config.cards[selected]}
.lovelace=${this.lovelace} .lovelace=${this.lovelace}
@config-changed=${this._handleConfigChanged} @config-changed=${this._handleConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged} @GUImode-changed=${this._handleGUIModeChanged}
></hui-card-editor> ></hui-element-editor>
` `
: html` : html`
<hui-card-picker <hui-card-picker
@ -164,7 +165,7 @@ export class HuiStackCardEditor extends LitElement
return; return;
} }
const cards = [...this._config.cards]; const cards = [...this._config.cards];
cards[this._selectedCard] = ev.detail.config; cards[this._selectedCard] = ev.detail.config as LovelaceCardConfig;
this._config = { ...this._config, cards }; this._config = { ...this._config, cards };
this._guiModeAvailable = ev.detail.guiModeAvailable; this._guiModeAvailable = ev.detail.guiModeAvailable;
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });

View File

@ -1,12 +1,14 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { assert, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
@ -15,7 +17,6 @@ import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { object, string, optional, assert } from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -56,7 +57,6 @@ export class HuiThermostatCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -110,6 +110,10 @@ export class HuiThermostatCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,23 +1,24 @@
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { assert, boolean, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-switch";
import "../../../../components/ha-formfield"; import "../../../../components/ha-formfield";
import "../../../../components/ha-switch";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { WeatherForecastCardConfig } from "../../cards/types"; import { WeatherForecastCardConfig } from "../../cards/types";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { object, string, optional, boolean, assert } from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -68,7 +69,6 @@ export class HuiWeatherForecastCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -151,6 +151,10 @@ export class HuiWeatherForecastCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -0,0 +1,86 @@
import "@material/mwc-button";
import "@material/mwc-icon-button";
import { mdiArrowLeft } from "@mdi/js";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-svg-icon";
import { HomeAssistant } from "../../../types";
declare global {
interface HASSDomEvents {
"go-back": undefined;
"toggle-gui-mode": undefined;
}
}
@customElement("hui-detail-editor-base")
export class HuiDetailEditorBase extends LitElement {
public hass!: HomeAssistant;
@property({ type: Boolean }) public guiModeAvailable? = true;
@property({ type: Boolean }) public guiMode? = true;
protected render(): TemplateResult {
return html`
<div class="header">
<div class="back-title">
<mwc-icon-button @click=${this._goBack}>
<ha-svg-icon .path=${mdiArrowLeft}></ha-svg-icon>
</mwc-icon-button>
<slot name="title"></slot>
</div>
<mwc-button
slot="secondaryAction"
class="gui-mode-button"
.disabled=${!this.guiModeAvailable}
@click=${this._toggleMode}
>
${this.hass.localize(
this.guiMode
? "ui.panel.lovelace.editor.edit_card.show_code_editor"
: "ui.panel.lovelace.editor.edit_card.show_visual_editor"
)}
</mwc-button>
</div>
<slot></slot>
`;
}
private _goBack(): void {
fireEvent(this, "go-back");
}
private _toggleMode(): void {
fireEvent(this, "toggle-gui-mode");
}
static get styles(): CSSResult {
return css`
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
.back-title {
display: flex;
align-items: center;
font-size: 18px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-detail-editor-base": HuiDetailEditorBase;
}
}

View File

@ -11,26 +11,33 @@ import {
query, query,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { computeRTL } from "../../../../common/util/compute_rtl"; import { computeRTL } from "../../../common/util/compute_rtl";
import { deepEqual } from "../../../../common/util/deep-equal"; import { deepEqual } from "../../../common/util/deep-equal";
import "../../../../components/ha-circular-progress"; import "../../../components/ha-circular-progress";
import "../../../../components/ha-code-editor"; import "../../../components/ha-code-editor";
import type { HaCodeEditor } from "../../../../components/ha-code-editor"; import type { HaCodeEditor } from "../../../components/ha-code-editor";
import type { import type {
LovelaceCardConfig, LovelaceCardConfig,
LovelaceConfig, LovelaceConfig,
} from "../../../../data/lovelace"; } from "../../../data/lovelace";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../types";
import { handleStructError } from "../../common/structs/handle-errors"; import { handleStructError } from "../common/structs/handle-errors";
import { getCardElementClass } from "../../create-element/create-card-element"; import { getCardElementClass } from "../create-element/create-card-element";
import type { LovelaceRowConfig } from "../../entity-rows/types"; import { getRowElementClass } from "../create-element/create-row-element";
import type { LovelaceCardEditor } from "../../types"; import type { LovelaceRowConfig } from "../entity-rows/types";
import { GUISupportError } from "../gui-support-error"; import type {
import type { GUIModeChangedEvent } from "../types"; LovelaceCardConstructor,
LovelaceCardEditor,
LovelaceRowConstructor,
LovelaceRowEditor,
} from "../types";
import "./config-elements/hui-generic-entity-row-editor";
import { GUISupportError } from "./gui-support-error";
import { GUIModeChangedEvent } from "./types";
export interface ConfigChangedEvent { export interface ConfigChangedEvent {
config: LovelaceCardConfig; config: LovelaceCardConfig | LovelaceRowConfig;
error?: string; error?: string;
guiModeAvailable?: boolean; guiModeAvailable?: boolean;
} }
@ -47,21 +54,27 @@ declare global {
export interface UIConfigChangedEvent extends Event { export interface UIConfigChangedEvent extends Event {
detail: { detail: {
config: LovelaceCardConfig; config: LovelaceCardConfig | LovelaceRowConfig;
}; };
} }
@customElement("hui-card-editor") const GENERIC_ROW_TYPE = "generic-row";
export class HuiCardEditor extends LitElement {
@customElement("hui-element-editor")
export class HuiElementEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public lovelace?: LovelaceConfig; @property({ attribute: false }) public lovelace?: LovelaceConfig;
@property() public elementType: "row" | "card" = "card";
@internalProperty() private _yaml?: string; @internalProperty() private _yaml?: string;
@internalProperty() private _config?: LovelaceCardConfig; @internalProperty() private _config?: LovelaceCardConfig | LovelaceRowConfig;
@internalProperty() private _configElement?: LovelaceCardEditor; @internalProperty() private _configElement?:
| LovelaceCardEditor
| LovelaceRowEditor;
@internalProperty() private _configElType?: string; @internalProperty() private _configElType?: string;
@ -95,11 +108,11 @@ export class HuiCardEditor extends LitElement {
this._setConfig(); this._setConfig();
} }
public get value(): LovelaceCardConfig | undefined { public get value(): LovelaceCardConfig | LovelaceRowConfig | undefined {
return this._config; return this._config;
} }
public set value(config: LovelaceCardConfig | undefined) { public set value(config: LovelaceCardConfig | LovelaceRowConfig | undefined) {
if (this._config && deepEqual(config, this._config)) { if (this._config && deepEqual(config, this._config)) {
return; return;
} }
@ -220,7 +233,11 @@ export class HuiCardEditor extends LitElement {
if (this._configElement && changedProperties.has("hass")) { if (this._configElement && changedProperties.has("hass")) {
this._configElement.hass = this.hass; this._configElement.hass = this.hass;
} }
if (this._configElement && changedProperties.has("lovelace")) { if (
this._configElement &&
"lovelace" in this._configElement &&
changedProperties.has("lovelace")
) {
this._configElement.lovelace = this.lovelace; this._configElement.lovelace = this.lovelace;
} }
} }
@ -244,37 +261,61 @@ export class HuiCardEditor extends LitElement {
return; return;
} }
const cardType = this.value.type; let type: string;
if (
this.elementType === "row" &&
!this.value.type &&
"entity" in this.value
) {
type = GENERIC_ROW_TYPE;
} else {
type = this.value.type!;
}
let configElement = this._configElement; let configElement = this._configElement;
try { try {
this._error = undefined; this._error = undefined;
this._warnings = undefined; this._warnings = undefined;
if (this._configElType !== cardType) { if (this._configElType !== type) {
// If the card type has changed, we need to load a new GUI editor // If the type has changed, we need to load a new GUI editor
if (!this.value.type) { if (!type) {
throw new Error("No card type defined"); throw new Error(`No ${this.elementType} type defined`);
} }
const elClass = await getCardElementClass(cardType); let elClass:
| LovelaceCardConstructor
| LovelaceRowConstructor
| undefined;
if (this.elementType === "card") {
elClass = await getCardElementClass(type);
} else if (this.elementType === "row" && type !== GENERIC_ROW_TYPE) {
elClass = await getRowElementClass(type);
}
this._loading = true; this._loading = true;
// Check if a GUI editor exists // Check if a GUI editor exists
if (elClass && elClass.getConfigElement) { if (elClass && elClass.getConfigElement) {
configElement = await elClass.getConfigElement(); configElement = await elClass.getConfigElement();
} else if (this.elementType === "row" && type === GENERIC_ROW_TYPE) {
configElement = document.createElement(
"hui-generic-entity-row-editor"
);
} else { } else {
configElement = undefined; configElement = undefined;
throw new GUISupportError( throw new GUISupportError(`No visual editor available for: ${type}`);
`No visual editor available for: ${cardType}`
);
} }
this._configElement = configElement; this._configElement = configElement;
this._configElType = cardType; this._configElType = type;
// Perform final setup // Perform final setup
this._configElement.hass = this.hass; this._configElement.hass = this.hass;
if ("lovelace" in this._configElement) {
this._configElement.lovelace = this.lovelace; this._configElement.lovelace = this.lovelace;
}
this._configElement.addEventListener("config-changed", (ev) => this._configElement.addEventListener("config-changed", (ev) =>
this._handleUIConfigChanged(ev as UIConfigChangedEvent) this._handleUIConfigChanged(ev as UIConfigChangedEvent)
); );
@ -282,6 +323,7 @@ export class HuiCardEditor extends LitElement {
// Setup GUI editor and check that it can handle the current config // Setup GUI editor and check that it can handle the current config
try { try {
// @ts-ignore
this._configElement!.setConfig(this.value); this._configElement!.setConfig(this.value);
} catch (err) { } catch (err) {
throw new GUISupportError( throw new GUISupportError(
@ -340,6 +382,6 @@ export class HuiCardEditor extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"hui-card-editor": HuiCardEditor; "hui-element-editor": HuiElementEditor;
} }
} }

View File

@ -1,4 +1,5 @@
import { mdiClose, mdiDrag } from "@mdi/js"; import "@material/mwc-icon-button";
import { mdiClose, mdiDrag, mdiPencil } from "@mdi/js";
import { import {
css, css,
CSSResult, CSSResult,
@ -19,7 +20,7 @@ import Sortable, {
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/entity/ha-entity-picker"; import "../../../components/entity/ha-entity-picker";
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker"; import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon";
import { sortableStyles } from "../../../resources/ha-sortable-style"; import { sortableStyles } from "../../../resources/ha-sortable-style";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types"; import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types";
@ -85,26 +86,38 @@ export class HuiEntitiesCardRowEditor extends LitElement {
)}</span )}</span
> >
</div> </div>
<mwc-icon-button
aria-label=${this.hass!.localize(
"ui.components.entity.entity-picker.clear"
)}
.index=${index}
@click=${this._removeSpecialRow}
>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
</div> </div>
` `
: html` : html`
<ha-entity-picker <ha-entity-picker
allow-custom-entity allow-custom-entity
hideClearIcon
.hass=${this.hass} .hass=${this.hass}
.value=${(entityConf as EntityConfig).entity} .value=${(entityConf as EntityConfig).entity}
.index=${index} .index=${index}
@value-changed=${this._valueChanged} @value-changed=${this._valueChanged}
></ha-entity-picker> ></ha-entity-picker>
`} `}
<mwc-icon-button
aria-label=${this.hass!.localize(
"ui.components.entity.entity-picker.clear"
)}
class="remove-icon"
.index=${index}
@click=${this._removeRow}
>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
<mwc-icon-button
aria-label=${this.hass!.localize(
"ui.components.entity.entity-picker.edit"
)}
class="edit-icon"
.index=${index}
@click=${this._editRow}
>
<ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
</mwc-icon-button>
</div> </div>
`; `;
}) })
@ -164,7 +177,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
animation: 150, animation: 150,
fallbackClass: "sortable-fallback", fallbackClass: "sortable-fallback",
handle: ".handle", handle: ".handle",
onEnd: async (evt: SortableEvent) => this._entityMoved(evt), onEnd: async (evt: SortableEvent) => this._rowMoved(evt),
}); });
} }
@ -180,7 +193,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
fireEvent(this, "entities-changed", { entities: newConfigEntities }); fireEvent(this, "entities-changed", { entities: newConfigEntities });
} }
private _entityMoved(ev: SortableEvent): void { private _rowMoved(ev: SortableEvent): void {
if (ev.oldIndex === ev.newIndex) { if (ev.oldIndex === ev.newIndex) {
return; return;
} }
@ -192,7 +205,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
fireEvent(this, "entities-changed", { entities: newEntities }); fireEvent(this, "entities-changed", { entities: newEntities });
} }
private _removeSpecialRow(ev: CustomEvent): void { private _removeRow(ev: CustomEvent): void {
const index = (ev.currentTarget as any).index; const index = (ev.currentTarget as any).index;
const newConfigEntities = this.entities!.concat(); const newConfigEntities = this.entities!.concat();
@ -218,6 +231,12 @@ export class HuiEntitiesCardRowEditor extends LitElement {
fireEvent(this, "entities-changed", { entities: newConfigEntities }); fireEvent(this, "entities-changed", { entities: newConfigEntities });
} }
private _editRow(ev: CustomEvent): void {
fireEvent(this, "edit-row", {
index: (ev.currentTarget as any).index,
});
}
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
sortableStyles, sortableStyles,
@ -226,13 +245,16 @@ export class HuiEntitiesCardRowEditor extends LitElement {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.entity .handle { .entity .handle {
padding-right: 8px; padding-right: 8px;
cursor: move; cursor: move;
} }
.entity ha-entity-picker { .entity ha-entity-picker {
flex-grow: 1; flex-grow: 1;
} }
.special-row { .special-row {
height: 60px; height: 60px;
font-size: 16px; font-size: 16px;
@ -247,7 +269,8 @@ export class HuiEntitiesCardRowEditor extends LitElement {
flex-direction: column; flex-direction: column;
} }
.special-row mwc-icon-button { .remove-icon,
.edit-icon {
--mdc-icon-button-size: 36px; --mdc-icon-button-size: 36px;
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }

View File

@ -1,5 +1,6 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
LitElement, LitElement,
@ -35,7 +36,6 @@ export class HuiLovelaceEditor extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label=${this.hass.localize( .label=${this.hass.localize(
@ -71,6 +71,10 @@ export class HuiLovelaceEditor extends LitElement {
fireEvent(this, "lovelace-config-changed", { config: newConfig }); fireEvent(this, "lovelace-config-changed", { config: newConfig });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -2,6 +2,7 @@ import {
any, any,
array, array,
boolean, boolean,
number,
object, object,
optional, optional,
string, string,
@ -13,8 +14,6 @@ import {
LovelaceViewConfig, LovelaceViewConfig,
ShowViewConfig, ShowViewConfig,
} from "../../../data/lovelace"; } from "../../../data/lovelace";
import { EntityId } from "../common/structs/is-entity-id";
import { Icon } from "../common/structs/is-icon";
import { EntityConfig } from "../entity-rows/types"; import { EntityConfig } from "../entity-rows/types";
export interface YamlChangedEvent extends Event { export interface YamlChangedEvent extends Event {
@ -51,6 +50,7 @@ export interface ConfigError {
export interface EntitiesEditorEvent { export interface EntitiesEditorEvent {
detail?: { detail?: {
entities?: EntityConfig[]; entities?: EntityConfig[];
item?: any;
}; };
target?: EventTarget; target?: EventTarget;
} }
@ -95,19 +95,19 @@ const buttonEntitiesRowConfigStruct = object({
const castEntitiesRowConfigStruct = object({ const castEntitiesRowConfigStruct = object({
type: string(), type: string(),
view: string(), view: union([string(), number()]),
dashboard: optional(string()), dashboard: optional(string()),
name: optional(string()), name: optional(string()),
icon: optional(string()), icon: optional(string()),
hide_if_unavailable: optional(string()), hide_if_unavailable: optional(boolean()),
}); });
const callServiceEntitiesRowConfigStruct = object({ const callServiceEntitiesRowConfigStruct = object({
type: string(), type: string(),
name: string(), name: string(),
service: string(),
icon: optional(string()), icon: optional(string()),
action_name: optional(string()), action_name: optional(string()),
service: string(),
service_data: optional(any()), service_data: optional(any()),
}); });
@ -150,7 +150,7 @@ const buttonsEntitiesRowConfigStruct = object({
image: optional(string()), image: optional(string()),
name: optional(string()), name: optional(string()),
}), }),
EntityId, string(),
]) ])
), ),
}); });
@ -166,9 +166,9 @@ const attributeEntitiesRowConfigStruct = object({
export const entitiesConfigStruct = union([ export const entitiesConfigStruct = union([
object({ object({
entity: EntityId, entity: string(),
name: optional(string()), name: optional(string()),
icon: optional(Icon), icon: optional(string()),
image: optional(string()), image: optional(string()),
secondary_info: optional(string()), secondary_info: optional(string()),
format: optional(string()), format: optional(string()),
@ -177,7 +177,7 @@ export const entitiesConfigStruct = union([
hold_action: optional(actionConfigStruct), hold_action: optional(actionConfigStruct),
double_tap_action: optional(actionConfigStruct), double_tap_action: optional(actionConfigStruct),
}), }),
EntityId, string(),
buttonEntitiesRowConfigStruct, buttonEntitiesRowConfigStruct,
castEntitiesRowConfigStruct, castEntitiesRowConfigStruct,
conditionalEntitiesRowConfigStruct, conditionalEntitiesRowConfigStruct,

View File

@ -1,25 +1,25 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
css, css,
CSSResult, CSSResultArray,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { slugify } from "../../../../common/string/slugify"; import { slugify } from "../../../../common/string/slugify";
import "../../../../components/ha-switch"; import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/ha-formfield"; import "../../../../components/ha-formfield";
import "../../../../components/ha-icon-input"; import "../../../../components/ha-icon-input";
import "../../../../components/ha-switch";
import { LovelaceViewConfig } from "../../../../data/lovelace"; import { LovelaceViewConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { configElementStyle } from "../config-elements/config-elements-style"; import { configElementStyle } from "../config-elements/config-elements-style";
import { EditorTarget } from "../types"; import { EditorTarget } from "../types";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
declare global { declare global {
interface HASSDomEvents { interface HASSDomEvents {
@ -84,7 +84,6 @@ export class HuiViewEditor extends LitElement {
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -182,13 +181,16 @@ export class HuiViewEditor extends LitElement {
fireEvent(this, "view-config-changed", { config }); fireEvent(this, "view-config-changed", { config });
} }
static get styles(): CSSResult { static get styles(): CSSResultArray {
return css` return [
configElementStyle,
css`
.panel { .panel {
color: var(--secondary-text-color); color: var(--secondary-text-color);
display: block; display: block;
} }
`; `,
];
} }
} }

View File

@ -5,7 +5,6 @@ import {
LitElement, LitElement,
property, property,
internalProperty, internalProperty,
PropertyValues,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { domainToName } from "../../data/integration"; import { domainToName } from "../../data/integration";
@ -46,8 +45,6 @@ class LovelacePanel extends LitElement {
@property() public route?: Route; @property() public route?: Route;
@internalProperty() private _columns?: number;
@property() @property()
private _state?: "loading" | "loaded" | "error" | "yaml-editor" = "loading"; private _state?: "loading" | "loaded" | "error" | "yaml-editor" = "loading";
@ -55,8 +52,6 @@ class LovelacePanel extends LitElement {
@internalProperty() private lovelace?: Lovelace; @internalProperty() private lovelace?: Lovelace;
private mqls?: MediaQueryList[];
private _ignoreNextUpdateEvent = false; private _ignoreNextUpdateEvent = false;
private _fetchConfigOnConnect = false; private _fetchConfigOnConnect = false;
@ -105,7 +100,6 @@ class LovelacePanel extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.lovelace=${this.lovelace} .lovelace=${this.lovelace}
.route=${this.route} .route=${this.route}
.columns=${this._columns}
.narrow=${this.narrow} .narrow=${this.narrow}
@config-refresh=${this._forceFetchConfig} @config-refresh=${this._forceFetchConfig}
></hui-root> ></hui-root>
@ -144,25 +138,6 @@ class LovelacePanel extends LitElement {
`; `;
} }
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (changedProps.has("narrow")) {
this._updateColumns();
return;
}
if (!changedProps.has("hass")) {
return;
}
const oldHass = changedProps.get("hass") as this["hass"];
if (oldHass && this.hass!.dockedSidebar !== oldHass.dockedSidebar) {
this._updateColumns();
}
}
protected firstUpdated() { protected firstUpdated() {
this._fetchConfig(false); this._fetchConfig(false);
if (!this._unsubUpdates) { if (!this._unsubUpdates) {
@ -174,13 +149,6 @@ class LovelacePanel extends LitElement {
this._fetchConfig(false); this._fetchConfig(false);
} }
}); });
this._updateColumns = this._updateColumns.bind(this);
this.mqls = [300, 600, 900, 1200].map((width) => {
const mql = matchMedia(`(min-width: ${width}px)`);
mql.addListener(this._updateColumns);
return mql;
});
this._updateColumns();
} }
private async _regenerateConfig() { private async _regenerateConfig() {
@ -201,19 +169,6 @@ class LovelacePanel extends LitElement {
this._state = "loaded"; this._state = "loaded";
} }
private _updateColumns() {
const matchColumns = this.mqls!.reduce(
(cols, mql) => cols + Number(mql.matches),
0
);
// Do -1 column if the menu is docked and open
this._columns = Math.max(
1,
matchColumns -
Number(!this.narrow && this.hass!.dockedSidebar === "docked")
);
}
private _lovelaceChanged() { private _lovelaceChanged() {
if (this._ignoreNextUpdateEvent) { if (this._ignoreNextUpdateEvent) {
this._ignoreNextUpdateEvent = false; this._ignoreNextUpdateEvent = false;

View File

@ -4,6 +4,7 @@ import {
LovelaceConfig, LovelaceConfig,
} from "../../data/lovelace"; } from "../../data/lovelace";
import { Constructor, HomeAssistant } from "../../types"; import { Constructor, HomeAssistant } from "../../types";
import { LovelaceRow, LovelaceRowConfig } from "./entity-rows/types";
import { LovelaceHeaderFooterConfig } from "./header-footer/types"; import { LovelaceHeaderFooterConfig } from "./header-footer/types";
declare global { declare global {
@ -48,6 +49,10 @@ export interface LovelaceCardConstructor extends Constructor<LovelaceCard> {
getConfigElement?: () => LovelaceCardEditor; getConfigElement?: () => LovelaceCardEditor;
} }
export interface LovelaceRowConstructor extends Constructor<LovelaceRow> {
getConfigElement?: () => LovelaceRowEditor;
}
export interface LovelaceHeaderFooter extends HTMLElement { export interface LovelaceHeaderFooter extends HTMLElement {
hass?: HomeAssistant; hass?: HomeAssistant;
getCardSize(): number | Promise<number>; getCardSize(): number | Promise<number>;
@ -60,3 +65,9 @@ export interface LovelaceCardEditor extends HTMLElement {
setConfig(config: LovelaceCardConfig): void; setConfig(config: LovelaceCardConfig): void;
refreshYamlEditor?: (focus: boolean) => void; refreshYamlEditor?: (focus: boolean) => void;
} }
export interface LovelaceRowEditor extends HTMLElement {
hass?: HomeAssistant;
setConfig(config: LovelaceRowConfig): void;
refreshYamlEditor?: (focus: boolean) => void;
}

View File

@ -98,11 +98,13 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
} }
protected firstUpdated(): void { protected firstUpdated(): void {
this._updateColumns = this._updateColumns.bind(this);
this._mqls = [300, 600, 900, 1200].map((width) => { this._mqls = [300, 600, 900, 1200].map((width) => {
const mql = matchMedia(`(min-width: ${width}px)`); const mql = matchMedia(`(min-width: ${width}px)`);
mql.addEventListener("change", this._updateColumns); mql.addEventListener("change", this._updateColumns);
return mql; return mql;
}); });
this._updateColumns();
} }
protected updated(changedProperties: PropertyValues): void { protected updated(changedProperties: PropertyValues): void {
@ -130,11 +132,14 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
} }
} }
const oldLovelace = changedProperties.get("lovelace") as Lovelace; const oldLovelace = changedProperties.get("lovelace") as
| Lovelace
| undefined;
if ( if (
(changedProperties.has("lovelace") && (
oldLovelace?.config !== this.lovelace?.config || oldLovelace?.config !== this.lovelace?.config ||
oldLovelace?.editMode !== this.lovelace?.editMode || oldLovelace?.editMode !== this.lovelace?.editMode)) ||
changedProperties.has("_columns") changedProperties.has("_columns")
) { ) {
this._createColumns(); this._createColumns();
@ -237,6 +242,9 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
} }
private _updateColumns() { private _updateColumns() {
if (!this._mqls) {
return;
}
const matchColumns = this._mqls!.reduce( const matchColumns = this._mqls!.reduce(
(cols, mql) => cols + Number(mql.matches), (cols, mql) => cols + Number(mql.matches),
0 0
@ -260,6 +268,8 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
margin-left: 4px;
margin-right: 4px;
} }
.column { .column {
@ -288,11 +298,6 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
} }
@media (max-width: 500px) { @media (max-width: 500px) {
:host {
padding-left: 0;
padding-right: 0;
}
.column > * { .column > * {
margin-left: 0; margin-left: 0;
margin-right: 0; margin-right: 0;

View File

@ -57,7 +57,13 @@ export class PanelView extends LitElement implements LovelaceViewElement {
); );
} }
const oldLovelace = changedProperties.get("lovelace") as Lovelace; if (!changedProperties.has("lovelace")) {
return;
}
const oldLovelace = changedProperties.get("lovelace") as
| Lovelace
| undefined;
if ( if (
oldLovelace?.config !== this.lovelace?.config || oldLovelace?.config !== this.lovelace?.config ||

View File

@ -86,6 +86,7 @@ export const derivedStyles = {
"mdc-radio-disabled-color": "var(--disabled-text-color)", "mdc-radio-disabled-color": "var(--disabled-text-color)",
"mdc-tab-text-label-color-default": "var(--primary-text-color)", "mdc-tab-text-label-color-default": "var(--primary-text-color)",
"mdc-button-disabled-ink-color": "var(--disabled-text-color)", "mdc-button-disabled-ink-color": "var(--disabled-text-color)",
"mdc-dialog-scroll-divider-color": "var(--divider-color)",
}; };
export const haStyle = css` export const haStyle = css`

View File

@ -1656,7 +1656,10 @@
"device_tracker_pick": "Pick device to track", "device_tracker_pick": "Pick device to track",
"delete": "Delete", "delete": "Delete",
"create": "Create", "create": "Create",
"update": "Update" "update": "Update",
"confirm_delete_user": "Are you sure you want to delete the user account for {name}? You can still track the user, but the person will no longer be able to login.",
"admin": "[%key:ui::panel::config::users::editor::admin%]",
"allow_login": "Allow person to login"
} }
}, },
"zone": { "zone": {
@ -1676,7 +1679,7 @@
"new_zone": "New Zone", "new_zone": "New Zone",
"name": "Name", "name": "Name",
"icon": "Icon", "icon": "Icon",
"icon_error_msg": "Icon should be in the format prefix:iconname, for example: mdi:home", "icon_error_msg": "Icon should be in the format ''prefix:iconname'', for example: ''mdi:home''",
"radius": "Radius", "radius": "Radius",
"latitude": "Latitude", "latitude": "Latitude",
"longitude": "Longitude", "longitude": "Longitude",
@ -2328,6 +2331,16 @@
"description": "The Entities card is the most common type of card. It groups items together into lists.", "description": "The Entities card is the most common type of card. It groups items together into lists.",
"special_row": "special row", "special_row": "special row",
"edit_special_row": "Edit row using the code editor", "edit_special_row": "Edit row using the code editor",
"entity_row_editor": "Entity Row Editor",
"secondary_info_values": {
"none": "No Secondary Info",
"entity-id": "Entity ID",
"last-changed": "Last Changed",
"last-triggered": "Last Triggered",
"position": "Position",
"tilt-position": "Tilt Position",
"brightness": "Brightness"
},
"entity_row": { "entity_row": {
"divider": "Divider", "divider": "Divider",
"call-service": "Call Service", "call-service": "Call Service",