mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 03:36:44 +00:00
Add basic more info for lawn mower (#17601)
* Add basic more info for lawn mower * Change buttons layout
This commit is contained in:
parent
ac7c354bfc
commit
5f015ac9af
@ -11,10 +11,12 @@ export type LocalizeKeys =
|
||||
| `ui.card.alarm_control_panel.${string}`
|
||||
| `ui.card.weather.attributes.${string}`
|
||||
| `ui.card.weather.cardinal_direction.${string}`
|
||||
| `ui.card.lawn_mower.actions.${string}`
|
||||
| `ui.components.calendar.event.rrule.${string}`
|
||||
| `ui.components.logbook.${string}`
|
||||
| `ui.components.selectors.file.${string}`
|
||||
| `ui.dialogs.entity_registry.editor.${string}`
|
||||
| `ui.dialogs.more_info_control.lawn_mower.${string}`
|
||||
| `ui.dialogs.more_info_control.vacuum.${string}`
|
||||
| `ui.dialogs.quick-bar.commands.${string}`
|
||||
| `ui.dialogs.unhealthy.reason.${string}`
|
||||
|
91
src/components/ha-lawn_mower-state.ts
Normal file
91
src/components/ha-lawn_mower-state.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import "@material/mwc-button";
|
||||
import { CSSResultGroup, LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import {
|
||||
LawnMowerEntity,
|
||||
LawnMowerEntityFeature,
|
||||
LawnMowerEntityState,
|
||||
} from "../data/lawn_mower";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
type LawnMowerAction = {
|
||||
action: string;
|
||||
service: string;
|
||||
feature: LawnMowerEntityFeature;
|
||||
};
|
||||
|
||||
const LAWN_MOWER_ACTIONS: Partial<
|
||||
Record<LawnMowerEntityState, LawnMowerAction>
|
||||
> = {
|
||||
mowing: {
|
||||
action: "dock",
|
||||
service: "dock",
|
||||
feature: LawnMowerEntityFeature.DOCK,
|
||||
},
|
||||
docked: {
|
||||
action: "start_mowing",
|
||||
service: "start_mowing",
|
||||
feature: LawnMowerEntityFeature.START_MOWING,
|
||||
},
|
||||
paused: {
|
||||
action: "resume_mowing",
|
||||
service: "start_mowing",
|
||||
feature: LawnMowerEntityFeature.START_MOWING,
|
||||
},
|
||||
};
|
||||
|
||||
@customElement("ha-lawn_mower-state")
|
||||
class HaLawnMowerState extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj!: LawnMowerEntity;
|
||||
|
||||
public render() {
|
||||
const state = this.stateObj.state;
|
||||
const action = LAWN_MOWER_ACTIONS[state];
|
||||
|
||||
if (action && supportsFeature(this.stateObj, action.feature)) {
|
||||
return html`
|
||||
<mwc-button @click=${this.callService} .service=${action.service}>
|
||||
${this.hass.localize(`ui.card.lawn_mower.actions.${action.action}`)}
|
||||
</mwc-button>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<mwc-button disabled>
|
||||
${this.hass.formatEntityState(this.stateObj)}
|
||||
</mwc-button>
|
||||
`;
|
||||
}
|
||||
|
||||
callService(ev) {
|
||||
ev.stopPropagation();
|
||||
const stateObj = this.stateObj;
|
||||
const service = ev.target.service;
|
||||
this.hass.callService("lawn_mower", service, {
|
||||
entity_id: stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
mwc-button {
|
||||
top: 3px;
|
||||
height: 37px;
|
||||
margin-right: -0.57em;
|
||||
}
|
||||
mwc-button[disabled] {
|
||||
background-color: transparent;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-lawn_mower-state": HaLawnMowerState;
|
||||
}
|
||||
}
|
42
src/data/lawn_mower.ts
Normal file
42
src/data/lawn_mower.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import {
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE } from "./entity";
|
||||
|
||||
export type LawnMowerEntityState = "paused" | "mowing" | "docked" | "error";
|
||||
|
||||
export const enum LawnMowerEntityFeature {
|
||||
START_MOWING = 1,
|
||||
PAUSE = 2,
|
||||
DOCK = 4,
|
||||
}
|
||||
|
||||
interface LawnMowerEntityAttributes extends HassEntityAttributeBase {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface LawnMowerEntity extends HassEntityBase {
|
||||
attributes: LawnMowerEntityAttributes;
|
||||
}
|
||||
|
||||
export function canStartMowing(stateObj: LawnMowerEntity): boolean {
|
||||
if (stateObj.state === UNAVAILABLE) {
|
||||
return false;
|
||||
}
|
||||
return stateObj.state !== "mowing";
|
||||
}
|
||||
|
||||
export function canPause(stateObj: LawnMowerEntity): boolean {
|
||||
if (stateObj.state === UNAVAILABLE) {
|
||||
return false;
|
||||
}
|
||||
return stateObj.state !== "paused";
|
||||
}
|
||||
|
||||
export function canDock(stateObj: LawnMowerEntity): boolean {
|
||||
if (stateObj.state === UNAVAILABLE) {
|
||||
return false;
|
||||
}
|
||||
return stateObj.state !== "docked";
|
||||
}
|
@ -46,6 +46,7 @@ export const DOMAINS_WITH_MORE_INFO = [
|
||||
"image",
|
||||
"input_boolean",
|
||||
"input_datetime",
|
||||
"lawn_mower",
|
||||
"light",
|
||||
"lock",
|
||||
"media_player",
|
||||
|
213
src/dialogs/more-info/controls/more-info-lawn_mower.ts
Normal file
213
src/dialogs/more-info/controls/more-info-lawn_mower.ts
Normal file
@ -0,0 +1,213 @@
|
||||
import { mdiHomeMapMarker, mdiPause, mdiPlay } from "@mdi/js";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
|
||||
import "../../../components/entity/ha-battery-icon";
|
||||
import "../../../components/ha-icon-button";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import {
|
||||
EntityRegistryDisplayEntry,
|
||||
findBatteryChargingEntity,
|
||||
findBatteryEntity,
|
||||
} from "../../../data/entity_registry";
|
||||
import {
|
||||
LawnMowerEntity,
|
||||
LawnMowerEntityFeature,
|
||||
} from "../../../data/lawn_mower";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
interface LawnMowerCommand {
|
||||
translationKey: string;
|
||||
icon: string;
|
||||
serviceName: string;
|
||||
isVisible: (stateObj: LawnMowerEntity) => boolean;
|
||||
}
|
||||
|
||||
const LAWN_MOWER_COMMANDS: LawnMowerCommand[] = [
|
||||
{
|
||||
translationKey: "start_mowing",
|
||||
icon: mdiPlay,
|
||||
serviceName: "start_mowing",
|
||||
isVisible: (stateObj) =>
|
||||
supportsFeature(stateObj, LawnMowerEntityFeature.START_MOWING),
|
||||
},
|
||||
{
|
||||
translationKey: "pause",
|
||||
icon: mdiPause,
|
||||
serviceName: "pause",
|
||||
isVisible: (stateObj) =>
|
||||
supportsFeature(stateObj, LawnMowerEntityFeature.PAUSE),
|
||||
},
|
||||
{
|
||||
translationKey: "dock",
|
||||
icon: mdiHomeMapMarker,
|
||||
serviceName: "dock",
|
||||
isVisible: (stateObj) =>
|
||||
supportsFeature(stateObj, LawnMowerEntityFeature.DOCK),
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("more-info-lawn_mower")
|
||||
class MoreInfoLawnMower extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public stateObj?: LawnMowerEntity;
|
||||
|
||||
protected render() {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const stateObj = this.stateObj;
|
||||
|
||||
return html`
|
||||
${stateObj.state !== UNAVAILABLE
|
||||
? html` <div class="flex-horizontal">
|
||||
<div>
|
||||
<span class="status-subtitle"
|
||||
>${this.hass!.localize(
|
||||
"ui.dialogs.more_info_control.lawn_mower.activity"
|
||||
)}:
|
||||
</span>
|
||||
<span>
|
||||
<strong>
|
||||
${computeStateDisplay(
|
||||
this.hass.localize,
|
||||
stateObj,
|
||||
this.hass.locale,
|
||||
this.hass.config,
|
||||
this.hass.entities
|
||||
)}
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
${this._renderBattery()}
|
||||
</div>`
|
||||
: nothing}
|
||||
${LAWN_MOWER_COMMANDS.some((item) => item.isVisible(stateObj))
|
||||
? html`
|
||||
<div>
|
||||
<p></p>
|
||||
<div class="status-subtitle">
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.more_info_control.lawn_mower.commands"
|
||||
)}
|
||||
</div>
|
||||
<div class="flex-horizontal space-around">
|
||||
${LAWN_MOWER_COMMANDS.filter((item) =>
|
||||
item.isVisible(stateObj)
|
||||
).map(
|
||||
(item) => html`
|
||||
<div>
|
||||
<ha-icon-button
|
||||
.path=${item.icon}
|
||||
.entry=${item}
|
||||
@click=${this.callService}
|
||||
.label=${this.hass!.localize(
|
||||
`ui.dialogs.more_info_control.lawn_mower.${item.translationKey}`
|
||||
)}
|
||||
.disabled=${stateObj.state === UNAVAILABLE}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private _deviceEntities = memoizeOne(
|
||||
(
|
||||
deviceId: string,
|
||||
entities: HomeAssistant["entities"]
|
||||
): EntityRegistryDisplayEntry[] => {
|
||||
const entries = Object.values(entities);
|
||||
return entries.filter((entity) => entity.device_id === deviceId);
|
||||
}
|
||||
);
|
||||
|
||||
private _renderBattery() {
|
||||
const stateObj = this.stateObj!;
|
||||
|
||||
const deviceId = this.hass.entities[stateObj.entity_id]?.device_id;
|
||||
|
||||
const entities = deviceId
|
||||
? this._deviceEntities(deviceId, this.hass.entities)
|
||||
: [];
|
||||
|
||||
const batteryEntity = findBatteryEntity(this.hass, entities);
|
||||
const battery = batteryEntity
|
||||
? this.hass.states[batteryEntity.entity_id]
|
||||
: undefined;
|
||||
|
||||
const batteryIsBinary =
|
||||
battery && computeStateDomain(battery) === "binary_sensor";
|
||||
|
||||
// Use device battery entity
|
||||
if (battery && (batteryIsBinary || !isNaN(battery.state as any))) {
|
||||
const batteryChargingEntity = findBatteryChargingEntity(
|
||||
this.hass,
|
||||
entities
|
||||
);
|
||||
const batteryCharging = batteryChargingEntity
|
||||
? this.hass.states[batteryChargingEntity?.entity_id]
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
<div>
|
||||
<span>
|
||||
${batteryIsBinary
|
||||
? ""
|
||||
: `${Number(battery.state).toFixed()}${blankBeforePercent(
|
||||
this.hass.locale
|
||||
)}%`}
|
||||
<ha-battery-icon
|
||||
.hass=${this.hass}
|
||||
.batteryStateObj=${battery}
|
||||
.batteryChargingStateObj=${batteryCharging}
|
||||
></ha-battery-icon>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return nothing;
|
||||
}
|
||||
|
||||
private callService(ev: CustomEvent) {
|
||||
const entry = (ev.target! as any).entry as LawnMowerCommand;
|
||||
this.hass.callService("lawn_mower", entry.serviceName, {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
line-height: 1.5;
|
||||
}
|
||||
.status-subtitle {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.flex-horizontal {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.space-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"more-info-lawn_mower": MoreInfoLawnMower;
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ const LAZY_LOADED_MORE_INFO_CONTROL = {
|
||||
image: () => import("./controls/more-info-image"),
|
||||
input_boolean: () => import("./controls/more-info-input_boolean"),
|
||||
input_datetime: () => import("./controls/more-info-input_datetime"),
|
||||
lawn_mower: () => import("./controls/more-info-lawn_mower"),
|
||||
light: () => import("./controls/more-info-light"),
|
||||
lock: () => import("./controls/more-info-lock"),
|
||||
media_player: () => import("./controls/more-info-media_player"),
|
||||
|
@ -5,15 +5,16 @@ import { stateCardType } from "../common/entity/state_card_type";
|
||||
import "./state-card-alert";
|
||||
import "./state-card-button";
|
||||
import "./state-card-climate";
|
||||
import "./state-card-humidifier";
|
||||
import "./state-card-configurator";
|
||||
import "./state-card-cover";
|
||||
import "./state-card-display";
|
||||
import "./state-card-event";
|
||||
import "./state-card-humidifier";
|
||||
import "./state-card-input_button";
|
||||
import "./state-card-input_number";
|
||||
import "./state-card-input_select";
|
||||
import "./state-card-input_text";
|
||||
import "./state-card-lawn_mower";
|
||||
import "./state-card-lock";
|
||||
import "./state-card-media_player";
|
||||
import "./state-card-number";
|
||||
|
43
src/state-summary/state-card-lawn_mower.ts
Normal file
43
src/state-summary/state-card-lawn_mower.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { CSSResultGroup, LitElement, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../components/entity/state-info";
|
||||
import "../components/ha-lawn_mower-state";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
@customElement("state-card-lawn_mower")
|
||||
class StateCardLawnMower extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public stateObj!: HassEntity;
|
||||
|
||||
@property({ type: Boolean }) public inDialog = false;
|
||||
|
||||
public render() {
|
||||
const stateObj = this.stateObj;
|
||||
return html`
|
||||
<div class="horizontal justified layout">
|
||||
<state-info
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
.inDialog=${this.inDialog}
|
||||
></state-info>
|
||||
<ha-lawn_mower-state
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
></ha-lawn_mower-state>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return haStyle;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"state-card-lawn_mower": StateCardLawnMower;
|
||||
}
|
||||
}
|
@ -139,6 +139,13 @@
|
||||
"drying": "{name} drying",
|
||||
"on_entity": "{name} on"
|
||||
},
|
||||
"lawn_mower": {
|
||||
"actions": {
|
||||
"resume_mowing": "Resume mowing",
|
||||
"start_mowing": "Start mowing",
|
||||
"dock": "Return to dock"
|
||||
}
|
||||
},
|
||||
"light": {
|
||||
"brightness": "Brightness",
|
||||
"color_temperature": "Color temperature",
|
||||
@ -1006,6 +1013,13 @@
|
||||
"target_label": "[%key:ui::dialogs::more_info_control::climate::target_label%]",
|
||||
"target": "[%key:ui::dialogs::more_info_control::climate::target%]"
|
||||
},
|
||||
"lawn_mower": {
|
||||
"activity": "Activity",
|
||||
"commands": "Lawn mower commands:",
|
||||
"start_mowing": "Start mowing",
|
||||
"pause": "Pause",
|
||||
"dock": "Return to dock"
|
||||
},
|
||||
"water_heater": {
|
||||
"target": "[%key:ui::dialogs::more_info_control::climate::target%]"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user