Fix mwc-list/menu actions (#6442)

* Fix mwc-list/menu actions

Fix double actions when using request-selected

* Update ha-button-menu.ts

* Automation menu styling

* Update src/panels/lovelace/hui-root.ts

Co-authored-by: Zack Arnett <arnett.zackary@gmail.com>

* Move

Co-authored-by: Zack Arnett <arnett.zackary@gmail.com>
This commit is contained in:
Bram Kragten 2020-07-21 23:22:19 +02:00 committed by GitHub
parent e08c10315e
commit 4404a1173b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 353 additions and 227 deletions

View File

@ -25,6 +25,7 @@ import { HomeAssistant, Route } from "../../../src/types";
import { showRepositoriesDialog } from "../dialogs/repositories/show-dialog-repositories"; import { showRepositoriesDialog } from "../dialogs/repositories/show-dialog-repositories";
import { supervisorTabs } from "../hassio-tabs"; import { supervisorTabs } from "../hassio-tabs";
import "./hassio-addon-repository"; import "./hassio-addon-repository";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => { const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
if (a.slug === "local") { if (a.slug === "local") {
@ -97,14 +98,18 @@ class HassioAddonStore extends LitElement {
.tabs=${supervisorTabs} .tabs=${supervisorTabs}
> >
<span slot="header">Add-on store</span> <span slot="header">Add-on store</span>
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon"> <ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
@action=${this._handleAction}
>
<mwc-icon-button slot="trigger" alt="menu"> <mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon> <ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
<mwc-list-item @request-selected=${this._manageRepositories}> <mwc-list-item>
Repositories Repositories
</mwc-list-item> </mwc-list-item>
<mwc-list-item @request-selected=${this.refreshData}> <mwc-list-item>
Reload Reload
</mwc-list-item> </mwc-list-item>
</ha-button-menu> </ha-button-menu>
@ -143,6 +148,17 @@ class HassioAddonStore extends LitElement {
this._loadData(); this._loadData();
} }
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._manageRepositories();
break;
case 1:
this.refreshData();
break;
}
}
private apiCalled(ev) { private apiCalled(ev) {
if (ev.detail.success) { if (ev.detail.success) {
this._loadData(); this._loadData();

View File

@ -0,0 +1,14 @@
import {
RequestSelectedDetail,
ListItem,
} from "@material/mwc-list/mwc-list-item";
export const shouldHandleRequestSelectedEvent = (
ev: CustomEvent<RequestSelectedDetail>
): boolean => {
if (!ev.detail.selected && ev.detail.source !== "property") {
return false;
}
(ev.target as ListItem).selected = false;
return true;
};

View File

@ -18,14 +18,30 @@ import "./ha-icon-button";
export class HaButtonMenu extends LitElement { export class HaButtonMenu extends LitElement {
@property() public corner: Corner = "TOP_START"; @property() public corner: Corner = "TOP_START";
@property({ type: Boolean }) public multi = false;
@property({ type: Boolean }) public activatable = false;
@query("mwc-menu") private _menu?: Menu; @query("mwc-menu") private _menu?: Menu;
public get items() {
return this._menu?.items;
}
public get selected() {
return this._menu?.selected;
}
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<div @click=${this._handleClick}> <div @click=${this._handleClick}>
<slot name="trigger"></slot> <slot name="trigger"></slot>
</div> </div>
<mwc-menu .corner=${this.corner}> <mwc-menu
.corner=${this.corner}
.multi=${this.multi}
.activatable=${this.activatable}
>
<slot></slot> <slot></slot>
</mwc-menu> </mwc-menu>
`; `;

View File

@ -18,6 +18,7 @@ import "@polymer/paper-input/paper-input";
import "@material/mwc-list/mwc-list"; import "@material/mwc-list/mwc-list";
import "./date-range-picker"; import "./date-range-picker";
import { computeRTLDirection } from "../common/util/compute_rtl"; import { computeRTLDirection } from "../common/util/compute_rtl";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
export interface DateRangePickerRanges { export interface DateRangePickerRanges {
[key: string]: [Date, Date]; [key: string]: [Date, Date];
@ -85,15 +86,9 @@ export class HaDateRangePicker extends LitElement {
class="date-range-ranges" class="date-range-ranges"
.dir=${this._rtlDirection} .dir=${this._rtlDirection}
> >
<mwc-list @request-selected=${this._setDateRange}> <mwc-list @action=${this._setDateRange} activatable>
${Object.entries(this.ranges).map( ${Object.keys(this.ranges).map(
([name, dates]) => html`<mwc-list-item (name) => html`<mwc-list-item>
.activated=${this.startDate.getTime() ===
dates[0].getTime() &&
this.endDate.getTime() === dates[1].getTime()}
.startDate=${dates[0]}
.endDate=${dates[1]}
>
${name} ${name}
</mwc-list-item>` </mwc-list-item>`
)} )}
@ -124,12 +119,10 @@ export class HaDateRangePicker extends LitElement {
); );
} }
private _setDateRange(ev: Event) { private _setDateRange(ev: CustomEvent<ActionDetail>) {
const target = ev.target as any; const dateRange = Object.values(this.ranges!)[ev.detail.index];
const startDate = target.startDate;
const endDate = target.endDate;
const dateRangePicker = this._dateRangePicker; const dateRangePicker = this._dateRangePicker;
dateRangePicker.clickRange([startDate, endDate]); dateRangePicker.clickRange(dateRange);
dateRangePicker.clickedApply(); dateRangePicker.clickedApply();
} }

View File

@ -29,8 +29,9 @@ import "./types/ha-automation-action-event";
import "./types/ha-automation-action-scene"; import "./types/ha-automation-action-scene";
import "./types/ha-automation-action-service"; import "./types/ha-automation-action-service";
import "./types/ha-automation-action-wait_template"; import "./types/ha-automation-action-wait_template";
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
import { handleStructError } from "../../../lovelace/common/structs/handle-errors"; import { handleStructError } from "../../../lovelace/common/structs/handle-errors";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import { haStyle } from "../../../../resources/styles";
const OPTIONS = [ const OPTIONS = [
"condition", "condition",
@ -134,17 +135,14 @@ export default class HaAutomationActionRow extends LitElement {
</mwc-icon-button> </mwc-icon-button>
` `
: ""} : ""}
<ha-button-menu corner="BOTTOM_START"> <ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<mwc-icon-button <mwc-icon-button
slot="trigger" slot="trigger"
.title=${this.hass.localize("ui.common.menu")} .title=${this.hass.localize("ui.common.menu")}
.label=${this.hass.localize("ui.common.overflow_menu")} .label=${this.hass.localize("ui.common.overflow_menu")}
><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon> ><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
<mwc-list-item <mwc-list-item .disabled=${!this._uiModeAvailable}>
@request-selected=${this._switchYamlMode}
.disabled=${!this._uiModeAvailable}
>
${yamlMode ${yamlMode
? this.hass.localize( ? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui" "ui.panel.config.automation.editor.edit_ui"
@ -158,7 +156,7 @@ export default class HaAutomationActionRow extends LitElement {
"ui.panel.config.automation.editor.actions.duplicate" "ui.panel.config.automation.editor.actions.duplicate"
)} )}
</mwc-list-item> </mwc-list-item>
<mwc-list-item @request-selected=${this._onDelete}> <mwc-list-item>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete" "ui.panel.config.automation.editor.actions.delete"
)} )}
@ -177,21 +175,20 @@ export default class HaAutomationActionRow extends LitElement {
: ""} : ""}
${yamlMode ${yamlMode
? html` ? html`
<div style="margin-right: 24px;"> ${selected === -1
${selected === -1 ? html`
? html` ${this.hass.localize(
${this.hass.localize( "ui.panel.config.automation.editor.actions.unsupported_action",
"ui.panel.config.automation.editor.actions.unsupported_action", "action",
"action", type
type )}
)} `
` : ""}
: ""} <h2>Edit in YAML</h2>
<ha-yaml-editor <ha-yaml-editor
.defaultValue=${this.action} .defaultValue=${this.action}
@value-changed=${this._onYamlChange} @value-changed=${this._onYamlChange}
></ha-yaml-editor> ></ha-yaml-editor>
</div>
` `
: html` : html`
<paper-dropdown-menu-light <paper-dropdown-menu-light
@ -243,6 +240,19 @@ export default class HaAutomationActionRow extends LitElement {
fireEvent(this, "move-action", { direction: "down" }); fireEvent(this, "move-action", { direction: "down" });
} }
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._switchYamlMode();
break;
case 1:
break;
case 2:
this._onDelete();
break;
}
}
private _onDelete() { private _onDelete() {
showConfirmationDialog(this, { showConfirmationDialog(this, {
text: this.hass.localize( text: this.hass.localize(
@ -288,40 +298,34 @@ export default class HaAutomationActionRow extends LitElement {
fireEvent(this, "value-changed", { value: ev.detail.value }); fireEvent(this, "value-changed", { value: ev.detail.value });
} }
private _switchYamlMode(ev: CustomEvent<RequestSelectedDetail>) { private _switchYamlMode() {
if (ev.detail.source !== "interaction") {
return;
}
this._yamlMode = !this._yamlMode; this._yamlMode = !this._yamlMode;
} }
static get styles(): CSSResult { static get styles(): CSSResult[] {
return css` return [
.card-menu { haStyle,
position: absolute; css`
top: 0; .card-menu {
right: 0; float: right;
z-index: 3; z-index: 3;
--mdc-theme-text-primary-on-background: var(--primary-text-color); --mdc-theme-text-primary-on-background: var(--primary-text-color);
} }
.rtl .card-menu { .rtl .card-menu {
right: auto; float: left;
left: 0; }
} mwc-list-item[disabled] {
ha-button-menu { --mdc-theme-text-primary-on-background: var(--disabled-text-color);
margin: 8px; }
} .warning {
mwc-list-item[disabled] { color: var(--warning-color);
--mdc-theme-text-primary-on-background: var(--disabled-text-color); margin-bottom: 8px;
} }
.warning { .warning ul {
color: var(--warning-color); margin: 4px 0;
margin-bottom: 8px; }
} `,
.warning ul { ];
margin: 4px 0;
}
`;
} }
} }

View File

@ -2,7 +2,13 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
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 type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox"; import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
import { customElement, html, LitElement, property } from "lit-element"; import {
customElement,
html,
LitElement,
property,
CSSResult,
} from "lit-element";
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-card"; import "../../../../components/ha-card";
@ -19,6 +25,7 @@ import "./types/ha-automation-condition-sun";
import "./types/ha-automation-condition-template"; import "./types/ha-automation-condition-template";
import "./types/ha-automation-condition-time"; import "./types/ha-automation-condition-time";
import "./types/ha-automation-condition-zone"; import "./types/ha-automation-condition-zone";
import { haStyle } from "../../../../resources/styles";
const OPTIONS = [ const OPTIONS = [
"device", "device",
@ -47,21 +54,20 @@ export default class HaAutomationConditionEditor extends LitElement {
return html` return html`
${yamlMode ${yamlMode
? html` ? html`
<div style="margin-right: 24px;"> ${selected === -1
${selected === -1 ? html`
? html` ${this.hass.localize(
${this.hass.localize( "ui.panel.config.automation.editor.conditions.unsupported_condition",
"ui.panel.config.automation.editor.conditions.unsupported_condition", "condition",
"condition", this.condition.condition
this.condition.condition )}
)} `
` : ""}
: ""} <h2>Edit in YAML</h2>
<ha-yaml-editor <ha-yaml-editor
.defaultValue=${this.condition} .defaultValue=${this.condition}
@value-changed=${this._onYamlChange} @value-changed=${this._onYamlChange}
></ha-yaml-editor> ></ha-yaml-editor>
</div>
` `
: html` : html`
<paper-dropdown-menu-light <paper-dropdown-menu-light
@ -123,6 +129,10 @@ export default class HaAutomationConditionEditor extends LitElement {
} }
fireEvent(this, "value-changed", { value: ev.detail.value }); fireEvent(this, "value-changed", { value: ev.detail.value });
} }
static get styles(): CSSResult {
return haStyle;
}
} }
declare global { declare global {

View File

@ -18,7 +18,7 @@ import { Condition } from "../../../../data/automation";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import "./ha-automation-condition-editor"; import "./ha-automation-condition-editor";
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item"; import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
export interface ConditionElement extends LitElement { export interface ConditionElement extends LitElement {
condition: Condition; condition: Condition;
@ -65,14 +65,14 @@ export default class HaAutomationConditionRow extends LitElement {
<ha-card> <ha-card>
<div class="card-content"> <div class="card-content">
<div class="card-menu"> <div class="card-menu">
<ha-button-menu corner="BOTTOM_START"> <ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<mwc-icon-button <mwc-icon-button
.title=${this.hass.localize("ui.common.menu")} .title=${this.hass.localize("ui.common.menu")}
.label=${this.hass.localize("ui.common.overflow_menu")} .label=${this.hass.localize("ui.common.overflow_menu")}
slot="trigger" slot="trigger"
><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon ><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon
></mwc-icon-button> ></mwc-icon-button>
<mwc-list-item @request-selected=${this._switchYamlMode}> <mwc-list-item>
${this._yamlMode ${this._yamlMode
? this.hass.localize( ? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui" "ui.panel.config.automation.editor.edit_ui"
@ -86,7 +86,7 @@ export default class HaAutomationConditionRow extends LitElement {
"ui.panel.config.automation.editor.actions.duplicate" "ui.panel.config.automation.editor.actions.duplicate"
)} )}
</mwc-list-item> </mwc-list-item>
<mwc-list-item @request-selected=${this._onDelete}> <mwc-list-item>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete" "ui.panel.config.automation.editor.actions.delete"
)} )}
@ -103,6 +103,19 @@ export default class HaAutomationConditionRow extends LitElement {
`; `;
} }
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._switchYamlMode();
break;
case 1:
break;
case 2:
this._onDelete();
break;
}
}
private _onDelete() { private _onDelete() {
showConfirmationDialog(this, { showConfirmationDialog(this, {
text: this.hass.localize( text: this.hass.localize(
@ -116,28 +129,19 @@ export default class HaAutomationConditionRow extends LitElement {
}); });
} }
private _switchYamlMode(ev: CustomEvent<RequestSelectedDetail>) { private _switchYamlMode() {
if (ev.detail.source !== "interaction") {
return;
}
this._yamlMode = !this._yamlMode; this._yamlMode = !this._yamlMode;
} }
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
.card-menu { .card-menu {
position: absolute; float: right;
top: 0;
right: 0;
z-index: 3; z-index: 3;
--mdc-theme-text-primary-on-background: var(--primary-text-color); --mdc-theme-text-primary-on-background: var(--primary-text-color);
} }
.rtl .card-menu { .rtl .card-menu {
right: auto; float: left;
left: 0;
}
ha-button-menu {
margin: 8px;
} }
mwc-list-item[disabled] { mwc-list-item[disabled] {
--mdc-theme-text-primary-on-background: var(--disabled-text-color); --mdc-theme-text-primary-on-background: var(--disabled-text-color);

View File

@ -34,7 +34,8 @@ import "./types/ha-automation-trigger-time";
import "./types/ha-automation-trigger-time_pattern"; import "./types/ha-automation-trigger-time_pattern";
import "./types/ha-automation-trigger-webhook"; import "./types/ha-automation-trigger-webhook";
import "./types/ha-automation-trigger-zone"; import "./types/ha-automation-trigger-zone";
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item"; import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import { haStyle } from "../../../../resources/styles";
const OPTIONS = [ const OPTIONS = [
"device", "device",
@ -94,17 +95,14 @@ export default class HaAutomationTriggerRow extends LitElement {
<ha-card> <ha-card>
<div class="card-content"> <div class="card-content">
<div class="card-menu"> <div class="card-menu">
<ha-button-menu corner="BOTTOM_START"> <ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<mwc-icon-button <mwc-icon-button
slot="trigger" slot="trigger"
.title=${this.hass.localize("ui.common.menu")} .title=${this.hass.localize("ui.common.menu")}
.label=${this.hass.localize("ui.common.overflow_menu")} .label=${this.hass.localize("ui.common.overflow_menu")}
><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon ><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon
></mwc-icon-button> ></mwc-icon-button>
<mwc-list-item <mwc-list-item .disabled=${selected === -1}>
@request-selected=${this._switchYamlMode}
.disabled=${selected === -1}
>
${yamlMode ${yamlMode
? this.hass.localize( ? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui" "ui.panel.config.automation.editor.edit_ui"
@ -118,7 +116,7 @@ export default class HaAutomationTriggerRow extends LitElement {
"ui.panel.config.automation.editor.actions.duplicate" "ui.panel.config.automation.editor.actions.duplicate"
)} )}
</mwc-list-item> </mwc-list-item>
<mwc-list-item @request-selected=${this._onDelete}> <mwc-list-item>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete" "ui.panel.config.automation.editor.actions.delete"
)} )}
@ -127,21 +125,20 @@ export default class HaAutomationTriggerRow extends LitElement {
</div> </div>
${yamlMode ${yamlMode
? html` ? html`
<div style="margin-right: 24px;"> ${selected === -1
${selected === -1 ? html`
? html` ${this.hass.localize(
${this.hass.localize( "ui.panel.config.automation.editor.triggers.unsupported_platform",
"ui.panel.config.automation.editor.triggers.unsupported_platform", "platform",
"platform", this.trigger.platform
this.trigger.platform )}
)} `
` : ""}
: ""} <h2>Edit in YAML</h2>
<ha-yaml-editor <ha-yaml-editor
.defaultValue=${this.trigger} .defaultValue=${this.trigger}
@value-changed=${this._onYamlChange} @value-changed=${this._onYamlChange}
></ha-yaml-editor> ></ha-yaml-editor>
</div>
` `
: html` : html`
<paper-dropdown-menu-light <paper-dropdown-menu-light
@ -178,6 +175,19 @@ export default class HaAutomationTriggerRow extends LitElement {
`; `;
} }
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._switchYamlMode();
break;
case 1:
break;
case 2:
this._onDelete();
break;
}
}
private _onDelete() { private _onDelete() {
showConfirmationDialog(this, { showConfirmationDialog(this, {
text: this.hass.localize( text: this.hass.localize(
@ -219,33 +229,27 @@ export default class HaAutomationTriggerRow extends LitElement {
fireEvent(this, "value-changed", { value: ev.detail.value }); fireEvent(this, "value-changed", { value: ev.detail.value });
} }
private _switchYamlMode(ev: CustomEvent<RequestSelectedDetail>) { private _switchYamlMode() {
if (ev.detail.source !== "interaction") {
return;
}
this._yamlMode = !this._yamlMode; this._yamlMode = !this._yamlMode;
} }
static get styles(): CSSResult { static get styles(): CSSResult[] {
return css` return [
.card-menu { haStyle,
position: absolute; css`
top: 0; .card-menu {
right: 0; float: right;
z-index: 3; z-index: 3;
--mdc-theme-text-primary-on-background: var(--primary-text-color); --mdc-theme-text-primary-on-background: var(--primary-text-color);
} }
.rtl .card-menu { .rtl .card-menu {
right: auto; float: left;
left: 0; }
} mwc-list-item[disabled] {
ha-button-menu { --mdc-theme-text-primary-on-background: var(--disabled-text-color);
margin: 8px; }
} `,
mwc-list-item[disabled] { ];
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
}
`;
} }
} }

View File

@ -57,6 +57,7 @@ import {
showEntityEditorDialog, showEntityEditorDialog,
} from "./show-dialog-entity-editor"; } from "./show-dialog-entity-editor";
import { mdiFilterVariant } from "@mdi/js"; import { mdiFilterVariant } from "@mdi/js";
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
export interface StateEntity extends EntityRegistryEntry { export interface StateEntity extends EntityRegistryEntry {
readonly?: boolean; readonly?: boolean;
@ -449,7 +450,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
> >
</div>` </div>`
: ""} : ""}
<ha-button-menu corner="BOTTOM_START"> <ha-button-menu corner="BOTTOM_START" multi>
<mwc-icon-button <mwc-icon-button
slot="trigger" slot="trigger"
.label=${this.hass!.localize( .label=${this.hass!.localize(
@ -464,6 +465,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
<mwc-list-item <mwc-list-item
@request-selected="${this._showDisabledChanged}" @request-selected="${this._showDisabledChanged}"
graphic="control" graphic="control"
.selected=${this._showDisabled}
> >
<ha-checkbox <ha-checkbox
slot="graphic" slot="graphic"
@ -476,6 +478,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
<mwc-list-item <mwc-list-item
@request-selected="${this._showRestoredChanged}" @request-selected="${this._showRestoredChanged}"
graphic="control" graphic="control"
.selected=${this._showUnavailable}
> >
<ha-checkbox <ha-checkbox
slot="graphic" slot="graphic"
@ -488,6 +491,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
<mwc-list-item <mwc-list-item
@request-selected="${this._showReadOnlyChanged}" @request-selected="${this._showReadOnlyChanged}"
graphic="control" graphic="control"
.selected=${this._showReadOnly}
> >
<ha-checkbox <ha-checkbox
slot="graphic" slot="graphic"
@ -579,16 +583,25 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
} }
} }
private _showDisabledChanged() { private _showDisabledChanged(ev: CustomEvent<RequestSelectedDetail>) {
this._showDisabled = !this._showDisabled; if (ev.detail.source !== "property") {
return;
}
this._showDisabled = ev.detail.selected;
} }
private _showRestoredChanged() { private _showRestoredChanged(ev: CustomEvent<RequestSelectedDetail>) {
this._showUnavailable = !this._showUnavailable; if (ev.detail.source !== "property") {
return;
}
this._showUnavailable = ev.detail.selected;
} }
private _showReadOnlyChanged() { private _showReadOnlyChanged(ev: CustomEvent<RequestSelectedDetail>) {
this._showReadOnly = !this._showReadOnly; if (ev.detail.source !== "property") {
return;
}
this._showReadOnly = ev.detail.selected;
} }
private _handleSearchChange(ev: CustomEvent) { private _handleSearchChange(ev: CustomEvent) {

View File

@ -276,7 +276,11 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
</div> </div>
` `
: ""} : ""}
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon"> <ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
@action=${this._toggleShowIgnored}
>
<mwc-icon-button <mwc-icon-button
.title=${this.hass.localize("ui.common.menu")} .title=${this.hass.localize("ui.common.menu")}
.label=${this.hass.localize("ui.common.overflow_menu")} .label=${this.hass.localize("ui.common.overflow_menu")}
@ -284,7 +288,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
> >
<ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon> <ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
<mwc-list-item @request-selected=${this._toggleShowIgnored}> <mwc-list-item>
${this.hass.localize( ${this.hass.localize(
this._showIgnored this._showIgnored
? "ui.panel.config.integrations.ignore.hide_ignored" ? "ui.panel.config.integrations.ignore.hide_ignored"

View File

@ -28,6 +28,7 @@ import { haStyle } from "../../../resources/styles";
import "../../../components/ha-icon-next"; import "../../../components/ha-icon-next";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { mdiDotsVertical, mdiOpenInNew } from "@mdi/js"; import { mdiDotsVertical, mdiOpenInNew } from "@mdi/js";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
export interface ConfigEntryUpdatedEvent { export interface ConfigEntryUpdatedEvent {
entry: ConfigEntry; entry: ConfigEntry;
@ -223,7 +224,7 @@ export class HaIntegrationCard extends LitElement {
` `
: ""} : ""}
</div> </div>
<ha-button-menu corner="BOTTOM_START"> <ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<mwc-icon-button <mwc-icon-button
.title=${this.hass.localize("ui.common.menu")} .title=${this.hass.localize("ui.common.menu")}
.label=${this.hass.localize("ui.common.overflow_menu")} .label=${this.hass.localize("ui.common.overflow_menu")}
@ -231,7 +232,7 @@ export class HaIntegrationCard extends LitElement {
> >
<ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon> <ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
<mwc-list-item @request-selected=${this._showSystemOptions}> <mwc-list-item>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.integrations.config_entry.system_options" "ui.panel.config.integrations.config_entry.system_options"
)} )}
@ -240,7 +241,6 @@ export class HaIntegrationCard extends LitElement {
? "" ? ""
: html` : html`
<a <a
class="documentation"
href=${this.manifest.documentation} href=${this.manifest.documentation}
rel="noreferrer" rel="noreferrer"
target="_blank" target="_blank"
@ -255,10 +255,7 @@ export class HaIntegrationCard extends LitElement {
</mwc-list-item> </mwc-list-item>
</a> </a>
`} `}
<mwc-list-item <mwc-list-item class="warning">
class="warning"
@request-selected=${this._removeIntegration}
>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.integrations.config_entry.delete" "ui.panel.config.integrations.config_entry.delete"
)} )}
@ -308,32 +305,27 @@ export class HaIntegrationCard extends LitElement {
showOptionsFlowDialog(this, ev.target.closest("ha-card").configEntry); showOptionsFlowDialog(this, ev.target.closest("ha-card").configEntry);
} }
private _showSystemOptions(ev) { private _handleAction(ev: CustomEvent<ActionDetail>) {
showConfigEntrySystemOptionsDialog(this, { const configEntry = ((ev.target as HTMLElement).closest("ha-card") as any)
entry: ev.target.closest("ha-card").configEntry, .configEntry;
}); switch (ev.detail.index) {
} case 0:
this._showSystemOptions(configEntry);
private async _editEntryName(ev) { break;
const configEntry = ev.target.closest("ha-card").configEntry; case 1:
const newName = await showPromptDialog(this, { this._removeIntegration(configEntry);
title: this.hass.localize("ui.panel.config.integrations.rename_dialog"), break;
defaultValue: configEntry.title,
inputLabel: this.hass.localize(
"ui.panel.config.integrations.rename_input_label"
),
});
if (newName === null) {
return;
} }
const newEntry = await updateConfigEntry(this.hass, configEntry.entry_id, {
title: newName,
});
fireEvent(this, "entry-updated", { entry: newEntry });
} }
private async _removeIntegration(ev) { private _showSystemOptions(configEntry: ConfigEntry) {
const entryId = ev.target.closest("ha-card").configEntry.entry_id; showConfigEntrySystemOptionsDialog(this, {
entry: configEntry,
});
}
private async _removeIntegration(configEntry: ConfigEntry) {
const entryId = configEntry.entry_id;
const confirmed = await showConfirmationDialog(this, { const confirmed = await showConfirmationDialog(this, {
text: this.hass.localize( text: this.hass.localize(
@ -357,6 +349,24 @@ export class HaIntegrationCard extends LitElement {
}); });
} }
private async _editEntryName(ev) {
const configEntry = ev.target.closest("ha-card").configEntry;
const newName = await showPromptDialog(this, {
title: this.hass.localize("ui.panel.config.integrations.rename_dialog"),
defaultValue: configEntry.title,
inputLabel: this.hass.localize(
"ui.panel.config.integrations.rename_input_label"
),
});
if (newName === null) {
return;
}
const newEntry = await updateConfigEntry(this.hass, configEntry.entry_id, {
title: newName,
});
fireEvent(this, "entry-updated", { entry: newEntry });
}
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
@ -389,9 +399,6 @@ export class HaIntegrationCard extends LitElement {
align-items: center; align-items: center;
padding-right: 5px; padding-right: 5px;
} }
.card-actions .documentation {
color: var(--primary-text-color);
}
.group-header { .group-header {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -20,6 +20,7 @@ import { confDeleteCard } from "../editor/delete-card";
import { Lovelace, LovelaceCard } from "../types"; import { Lovelace, LovelaceCard } from "../types";
import { computeCardSize } from "../common/compute-card-size"; import { computeCardSize } from "../common/compute-card-size";
import { mdiDotsVertical, mdiArrowDown, mdiArrowUp } from "@mdi/js"; import { mdiDotsVertical, mdiArrowDown, mdiArrowUp } from "@mdi/js";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
@customElement("hui-card-options") @customElement("hui-card-options")
export class HuiCardOptions extends LitElement { export class HuiCardOptions extends LitElement {
@ -65,7 +66,7 @@ export class HuiCardOptions extends LitElement {
?disabled=${this.path![1] === 0} ?disabled=${this.path![1] === 0}
><ha-svg-icon path=${mdiArrowUp}></ha-svg-icon ><ha-svg-icon path=${mdiArrowUp}></ha-svg-icon
></mwc-icon-button> ></mwc-icon-button>
<ha-button-menu corner="BOTTOM_START"> <ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<mwc-icon-button <mwc-icon-button
slot="trigger" slot="trigger"
aria-label=${this.hass!.localize( aria-label=${this.hass!.localize(
@ -78,20 +79,17 @@ export class HuiCardOptions extends LitElement {
<ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon> <ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
<mwc-list-item @request-selected=${this._moveCard}> <mwc-list-item>
${this.hass!.localize( ${this.hass!.localize(
"ui.panel.lovelace.editor.edit_card.move" "ui.panel.lovelace.editor.edit_card.move"
)}</mwc-list-item )}</mwc-list-item
> >
<mwc-list-item @request-selected=${this._duplicateCard} <mwc-list-item
>${this.hass!.localize( >${this.hass!.localize(
"ui.panel.lovelace.editor.edit_card.duplicate" "ui.panel.lovelace.editor.edit_card.duplicate"
)}</mwc-list-item )}</mwc-list-item
> >
<mwc-list-item <mwc-list-item class="delete-item">
class="delete-item"
@request-selected=${this._deleteCard}
>
${this.hass!.localize( ${this.hass!.localize(
"ui.panel.lovelace.editor.edit_card.delete" "ui.panel.lovelace.editor.edit_card.delete"
)}</mwc-list-item )}</mwc-list-item
@ -150,6 +148,20 @@ export class HuiCardOptions extends LitElement {
`; `;
} }
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._moveCard();
break;
case 1:
this._duplicateCard();
break;
case 2:
this._deleteCard();
break;
}
}
private _duplicateCard(): void { private _duplicateCard(): void {
const path = this.path!; const path = this.path!;
const cardConfig = this.lovelace!.config.views[path[0]].cards![path[1]]; const cardConfig = this.lovelace!.config.views[path[0]].cards![path[1]];

View File

@ -58,6 +58,8 @@ import type { Lovelace } from "./types";
import "./views/hui-panel-view"; import "./views/hui-panel-view";
import type { HUIPanelView } from "./views/hui-panel-view"; import type { HUIPanelView } from "./views/hui-panel-view";
import { HUIView } from "./views/hui-view"; import { HUIView } from "./views/hui-view";
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
import { shouldHandleRequestSelectedEvent } from "../../common/mwc/handle-request-selected-event";
class HUIRoot extends LitElement { class HUIRoot extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -133,14 +135,19 @@ class HUIRoot extends LitElement {
<ha-svg-icon path=${mdiPencil}></ha-svg-icon> <ha-svg-icon path=${mdiPencil}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
</div> </div>
<mwc-icon-button <a
title="${this.hass!.localize( href="https://www.home-assistant.io/lovelace/"
"ui.panel.lovelace.menu.help" rel="noreferrer"
)}" target="_blank"
@click="${this._handleHelp}"
> >
<ha-svg-icon path=${mdiHelpCircle}></ha-svg-icon> <mwc-icon-button
</mwc-icon-button> title="${this.hass!.localize(
"ui.panel.lovelace.menu.help"
)}"
>
<ha-svg-icon path=${mdiHelpCircle}></ha-svg-icon>
</mwc-icon-button>
</a>
<ha-button-menu corner="BOTTOM_START"> <ha-button-menu corner="BOTTOM_START">
<mwc-icon-button <mwc-icon-button
slot="trigger" slot="trigger"
@ -167,9 +174,7 @@ class HUIRoot extends LitElement {
)} )}
</mwc-list-item> </mwc-list-item>
`} `}
<mwc-list-item <mwc-list-item @request-selected="${this._handleRawEditor}">
@request-selected="${this.lovelace!.enableFullEditMode}"
>
${this.hass!.localize( ${this.hass!.localize(
"ui.panel.lovelace.editor.menu.raw_editor" "ui.panel.lovelace.editor.menu.raw_editor"
)} )}
@ -251,7 +256,7 @@ class HUIRoot extends LitElement {
aria-label=${this.hass!.localize( aria-label=${this.hass!.localize(
"ui.panel.lovelace.menu.configure_ui" "ui.panel.lovelace.menu.configure_ui"
)} )}
@request-selected=${this._editModeEnable} @request-selected=${this._handleEnableEditMode}
> >
${this.hass!.localize( ${this.hass!.localize(
"ui.panel.lovelace.menu.configure_ui" "ui.panel.lovelace.menu.configure_ui"
@ -259,14 +264,19 @@ class HUIRoot extends LitElement {
</mwc-list-item> </mwc-list-item>
` `
: ""} : ""}
<mwc-list-item <a
aria-label=${this.hass!.localize( href="https://www.home-assistant.io/lovelace/"
"ui.panel.lovelace.menu.help" rel="noreferrer"
)} target="_blank"
@request-selected=${this._handleHelp}
> >
${this.hass!.localize("ui.panel.lovelace.menu.help")} <mwc-list-item
</mwc-list-item> aria-label=${this.hass!.localize(
"ui.panel.lovelace.menu.help"
)}
>
${this.hass!.localize("ui.panel.lovelace.menu.help")}
</mwc-list-item>
</a>
</ha-button-menu> </ha-button-menu>
</app-toolbar> </app-toolbar>
`} `}
@ -476,11 +486,17 @@ class HUIRoot extends LitElement {
return this.shadowRoot!.getElementById("view") as HTMLDivElement; return this.shadowRoot!.getElementById("view") as HTMLDivElement;
} }
private _handleRefresh(): void { private _handleRefresh(ev: CustomEvent<RequestSelectedDetail>): void {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
fireEvent(this, "config-refresh"); fireEvent(this, "config-refresh");
} }
private _handleReloadResources(): void { private _handleReloadResources(ev: CustomEvent<RequestSelectedDetail>): void {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
this.hass.callService("lovelace", "reload_resources"); this.hass.callService("lovelace", "reload_resources");
showConfirmationDialog(this, { showConfirmationDialog(this, {
title: this.hass!.localize( title: this.hass!.localize(
@ -493,7 +509,17 @@ class HUIRoot extends LitElement {
}); });
} }
private _handleUnusedEntities(): void { private _handleRawEditor(ev: CustomEvent<RequestSelectedDetail>): void {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
this.lovelace!.enableFullEditMode();
}
private _handleUnusedEntities(ev: CustomEvent<RequestSelectedDetail>): void {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
navigate(this, `${this.route?.prefix}/hass-unused-entities`); navigate(this, `${this.route?.prefix}/hass-unused-entities`);
} }
@ -501,17 +527,20 @@ class HUIRoot extends LitElement {
showVoiceCommandDialog(this); showVoiceCommandDialog(this);
} }
private _handleHelp(): void { private _handleEnableEditMode(ev: CustomEvent<RequestSelectedDetail>): void {
window.open("https://www.home-assistant.io/lovelace/", "_blank"); if (!shouldHandleRequestSelectedEvent(ev)) {
} return;
}
private _editModeEnable(): void {
if (this._yamlMode) { if (this._yamlMode) {
showAlertDialog(this, { showAlertDialog(this, {
text: "The edit UI is not available when in YAML mode.", text: "The edit UI is not available when in YAML mode.",
}); });
return; return;
} }
this._enableEditMode();
}
private _enableEditMode(): void {
this.lovelace!.setEditMode(true); this.lovelace!.setEditMode(true);
} }
@ -616,7 +645,7 @@ class HUIRoot extends LitElement {
const viewConfig = this.config.views[viewIndex]; const viewConfig = this.config.views[viewIndex];
if (!viewConfig) { if (!viewConfig) {
this._editModeEnable(); this._enableEditMode();
return; return;
} }

View File

@ -83,14 +83,14 @@ class HaPanelShoppingList extends LocalizeMixin(PolymerElement) {
icon="hass:microphone" icon="hass:microphone"
on-click="_showVoiceCommandDialog" on-click="_showVoiceCommandDialog"
></ha-icon-button> ></ha-icon-button>
<ha-button-menu corner="BOTTOM_START"> <ha-button-menu corner="BOTTOM_START" on-action="_clearCompleted">
<ha-icon-button <ha-icon-button
icon="hass:dots-vertical" icon="hass:dots-vertical"
label="Menu" label="Menu"
slot="trigger" slot="trigger"
> >
</ha-icon-button> </ha-icon-button>
<mwc-list-item on-request-selected="_clearCompleted"> <mwc-list-item>
[[localize('ui.panel.shopping-list.clear_completed')]] [[localize('ui.panel.shopping-list.clear_completed')]]
</mwc-list-item> </mwc-list-item>
</ha-button-menu> </ha-button-menu>