And vacuum commands extra for tile card (#14434)

This commit is contained in:
Paul Bottein 2022-11-29 15:15:47 +01:00 committed by GitHub
parent 566b93ec1f
commit fcfdad3d94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 472 additions and 50 deletions

View File

@ -2,21 +2,63 @@ import {
HassEntityAttributeBase,
HassEntityBase,
} from "home-assistant-js-websocket";
import { UNAVAILABLE } from "./entity";
export const VACUUM_SUPPORT_PAUSE = 4;
export const VACUUM_SUPPORT_STOP = 8;
export const VACUUM_SUPPORT_RETURN_HOME = 16;
export const VACUUM_SUPPORT_FAN_SPEED = 32;
export const VACUUM_SUPPORT_BATTERY = 64;
export const VACUUM_SUPPORT_STATUS = 128;
export const VACUUM_SUPPORT_LOCATE = 512;
export const VACUUM_SUPPORT_CLEAN_SPOT = 1024;
export const VACUUM_SUPPORT_START = 8192;
export type VacuumEntityState =
| "on"
| "off"
| "cleaning"
| "docked"
| "idle"
| "paused"
| "returning"
| "error";
export type VacuumEntity = HassEntityBase & {
attributes: HassEntityAttributeBase & {
battery_level: number;
fan_speed: any;
[key: string]: any;
};
};
export const enum VacuumEntityFeature {
TURN_ON = 1,
TURN_OFF = 2,
PAUSE = 4,
STOP = 8,
RETURN_HOME = 16,
FAN_SPEED = 32,
BATTERY = 64,
STATUS = 128,
SEND_COMMAND = 256,
LOCATE = 512,
CLEAN_SPOT = 1024,
MAP = 2048,
STATE = 4096,
START = 8192,
}
interface VacuumEntityAttributes extends HassEntityAttributeBase {
battery_level?: number;
fan_speed?: any;
[key: string]: any;
}
export interface VacuumEntity extends HassEntityBase {
attributes: VacuumEntityAttributes;
}
export function isCleaning(stateObj: VacuumEntity): boolean {
return ["cleaning", "on"].includes(stateObj.state);
}
export function canStart(stateObj: VacuumEntity): boolean {
if (stateObj.state === UNAVAILABLE) {
return false;
}
return !isCleaning(stateObj);
}
export function canStop(stateObj: VacuumEntity): boolean {
return !["docked", "off"].includes(stateObj.state);
}
export function canReturnHome(stateObj: VacuumEntity): boolean {
if (stateObj.state === UNAVAILABLE) {
return false;
}
return stateObj.state !== "returning";
}

View File

@ -18,18 +18,7 @@ import "../../../components/ha-icon";
import "../../../components/ha-icon-button";
import "../../../components/ha-select";
import { UNAVAILABLE } from "../../../data/entity";
import {
VacuumEntity,
VACUUM_SUPPORT_BATTERY,
VACUUM_SUPPORT_CLEAN_SPOT,
VACUUM_SUPPORT_FAN_SPEED,
VACUUM_SUPPORT_LOCATE,
VACUUM_SUPPORT_PAUSE,
VACUUM_SUPPORT_RETURN_HOME,
VACUUM_SUPPORT_START,
VACUUM_SUPPORT_STATUS,
VACUUM_SUPPORT_STOP,
} from "../../../data/vacuum";
import { VacuumEntity, VacuumEntityFeature } from "../../../data/vacuum";
import { HomeAssistant } from "../../../types";
interface VacuumCommand {
@ -44,7 +33,8 @@ const VACUUM_COMMANDS: VacuumCommand[] = [
translationKey: "start",
icon: mdiPlay,
serviceName: "start",
isVisible: (stateObj) => supportsFeature(stateObj, VACUUM_SUPPORT_START),
isVisible: (stateObj) =>
supportsFeature(stateObj, VacuumEntityFeature.START),
},
{
translationKey: "pause",
@ -52,8 +42,8 @@ const VACUUM_COMMANDS: VacuumCommand[] = [
serviceName: "pause",
isVisible: (stateObj) =>
// We need also to check if Start is supported because if not we show play-pause
supportsFeature(stateObj, VACUUM_SUPPORT_START) &&
supportsFeature(stateObj, VACUUM_SUPPORT_PAUSE),
supportsFeature(stateObj, VacuumEntityFeature.START) &&
supportsFeature(stateObj, VacuumEntityFeature.PAUSE),
},
{
translationKey: "start_pause",
@ -61,34 +51,36 @@ const VACUUM_COMMANDS: VacuumCommand[] = [
serviceName: "start_pause",
isVisible: (stateObj) =>
// If start is supported, we don't show this button
!supportsFeature(stateObj, VACUUM_SUPPORT_START) &&
supportsFeature(stateObj, VACUUM_SUPPORT_PAUSE),
!supportsFeature(stateObj, VacuumEntityFeature.START) &&
supportsFeature(stateObj, VacuumEntityFeature.PAUSE),
},
{
translationKey: "stop",
icon: mdiStop,
serviceName: "stop",
isVisible: (stateObj) => supportsFeature(stateObj, VACUUM_SUPPORT_STOP),
isVisible: (stateObj) =>
supportsFeature(stateObj, VacuumEntityFeature.STOP),
},
{
translationKey: "clean_spot",
icon: mdiTargetVariant,
serviceName: "clean_spot",
isVisible: (stateObj) =>
supportsFeature(stateObj, VACUUM_SUPPORT_CLEAN_SPOT),
supportsFeature(stateObj, VacuumEntityFeature.CLEAN_SPOT),
},
{
translationKey: "locate",
icon: mdiMapMarker,
serviceName: "locate",
isVisible: (stateObj) => supportsFeature(stateObj, VACUUM_SUPPORT_LOCATE),
isVisible: (stateObj) =>
supportsFeature(stateObj, VacuumEntityFeature.LOCATE),
},
{
translationKey: "return_home",
icon: mdiHomeMapMarker,
serviceName: "return_to_base",
isVisible: (stateObj) =>
supportsFeature(stateObj, VACUUM_SUPPORT_RETURN_HOME),
supportsFeature(stateObj, VacuumEntityFeature.RETURN_HOME),
},
];
@ -111,7 +103,7 @@ class MoreInfoVacuum extends LitElement {
return html`
${stateObj.state !== UNAVAILABLE
? html` <div class="flex-horizontal">
${supportsFeature(stateObj, VACUUM_SUPPORT_STATUS)
${supportsFeature(stateObj, VacuumEntityFeature.STATUS)
? html`
<div>
<span class="status-subtitle"
@ -131,7 +123,7 @@ class MoreInfoVacuum extends LitElement {
</div>
`
: ""}
${supportsFeature(stateObj, VACUUM_SUPPORT_BATTERY) &&
${supportsFeature(stateObj, VacuumEntityFeature.BATTERY) &&
stateObj.attributes.battery_level
? html`
<div>
@ -177,7 +169,7 @@ class MoreInfoVacuum extends LitElement {
</div>
`
: ""}
${supportsFeature(stateObj, VACUUM_SUPPORT_FAN_SPEED)
${supportsFeature(stateObj, VacuumEntityFeature.FAN_SPEED)
? html`
<div>
<div class="flex-horizontal">

View File

@ -6,11 +6,13 @@ import {
import "../tile-extra/hui-cover-open-close-tile-extra";
import "../tile-extra/hui-cover-tilt-tile-extra";
import "../tile-extra/hui-light-brightness-tile-extra";
import "../tile-extra/hui-vacuum-commands-tile-extra";
const TYPES: Set<LovelaceTileExtraConfig["type"]> = new Set([
"cover-open-close",
"cover-tilt",
"light-brightness",
"vacuum-commands",
]);
export const createTileExtraElement = (config: LovelaceTileExtraConfig) =>

View File

@ -20,7 +20,10 @@ import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types";
import type { TileCardConfig } from "../../cards/types";
import { LovelaceTileExtraConfig } from "../../tile-extra/types";
import {
LovelaceTileExtraConfig,
LovelaceTileExtraContext,
} from "../../tile-extra/types";
import type { LovelaceCardEditor } from "../../types";
import "../hui-sub-element-editor";
import { actionConfigStruct } from "../structs/action-struct";
@ -129,6 +132,10 @@ export class HuiTileCardEditor
] as const
);
private _context = memoizeOne(
(entity_id?: string): LovelaceTileExtraContext => ({ entity_id })
);
protected render(): TemplateResult {
if (!this.hass || !this._config) {
return html``;
@ -149,6 +156,7 @@ export class HuiTileCardEditor
<hui-sub-element-editor
.hass=${this.hass}
.config=${this._subElementEditorConfig}
.context=${this._context(this._config.entity)}
@go-back=${this._goBack}
@config-changed=${this.subElementChanged}
>

View File

@ -33,6 +33,7 @@ const EXTRAS_TYPE: LovelaceTileExtraConfig["type"][] = [
"cover-open-close",
"cover-tilt",
"light-brightness",
"vacuum-commands",
];
declare global {
@ -227,7 +228,7 @@ export class HuiTileCardExtrasEditor extends LitElement {
let newExtra: LovelaceTileExtraConfig;
if (elClass && elClass.getStubConfig) {
newExtra = await elClass.getStubConfig(this.hass!);
newExtra = await elClass.getStubConfig(this.hass!, this.stateObj);
} else {
newExtra = { type: value } as LovelaceTileExtraConfig;
}

View File

@ -0,0 +1,101 @@
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { LocalizeFunc } from "../../../../common/translations/localize";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types";
import { supportsVacuumCommand } from "../../tile-extra/hui-vacuum-commands-tile-extra";
import {
LovelaceTileExtraContext,
VacuumCommandsTileExtraConfig,
VACUUM_COMMANDS,
} from "../../tile-extra/types";
import type { LovelaceTileExtraEditor } from "../../types";
@customElement("hui-vacuum-commands-tile-extra-editor")
export class HuiVacuumCommandsTileExtraEditor
extends LitElement
implements LovelaceTileExtraEditor
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceTileExtraContext;
@state() private _config?: VacuumCommandsTileExtraConfig;
public setConfig(config: VacuumCommandsTileExtraConfig): void {
this._config = config;
}
private _schema = memoizeOne(
(localize: LocalizeFunc, stateObj?: HassEntity) =>
[
{
name: "commands",
selector: {
select: {
multiple: true,
options: VACUUM_COMMANDS.filter(
(command) =>
stateObj && supportsVacuumCommand(stateObj, command)
).map((command) => ({
value: command,
label: `${localize(
`ui.panel.lovelace.editor.card.tile.extras.types.vacuum-commands.commands_list.${command}`
)}`,
})),
},
},
},
] as const
);
protected render(): TemplateResult {
if (!this.hass || !this._config) {
return html``;
}
const stateObj = this.context?.entity_id
? this.hass.states[this.context?.entity_id]
: undefined;
const schema = this._schema(this.hass.localize, stateObj);
return html`
<ha-form
.hass=${this.hass}
.data=${this._config}
.schema=${schema}
.computeLabel=${this._computeLabelCallback}
@value-changed=${this._valueChanged}
></ha-form>
`;
}
private _valueChanged(ev: CustomEvent): void {
fireEvent(this, "config-changed", { config: ev.detail.value });
}
private _computeLabelCallback = (
schema: SchemaUnion<ReturnType<typeof this._schema>>
) => {
switch (schema.name) {
case "commands":
return this.hass!.localize(
`ui.panel.lovelace.editor.card.tile.extras.types.vacuum-commands.${schema.name}`
);
default:
return this.hass!.localize(
`ui.panel.lovelace.editor.card.generic.${schema.name}`
);
}
};
}
declare global {
interface HTMLElementTagNameMap {
"hui-vacuum-commands-tile-extra-editor": HuiVacuumCommandsTileExtraEditor;
}
}

View File

@ -57,11 +57,13 @@ export interface UIConfigChangedEvent extends Event {
};
}
export abstract class HuiElementEditor<T> extends LitElement {
export abstract class HuiElementEditor<T, C = any> extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public lovelace?: LovelaceConfig;
@property({ attribute: false }) public context?: C;
@state() private _yaml?: string;
@state() private _config?: T;
@ -275,6 +277,9 @@ export abstract class HuiElementEditor<T> extends LitElement {
) {
this._configElement.lovelace = this.lovelace;
}
if (this._configElement && changedProperties.has("context")) {
this._configElement.context = this.context;
}
}
private _handleUIConfigChanged(ev: UIConfigChangedEvent) {
@ -328,6 +333,7 @@ export abstract class HuiElementEditor<T> extends LitElement {
if ("lovelace" in configElement) {
configElement.lovelace = this.lovelace;
}
configElement.context = this.context;
configElement.addEventListener("config-changed", (ev) =>
this._handleUIConfigChanged(ev as UIConfigChangedEvent)
);

View File

@ -25,6 +25,8 @@ export class HuiSubElementEditor extends LitElement {
@property({ attribute: false }) public config!: SubElementEditorConfig;
@property({ attribute: false }) public context?: any;
@state() private _guiModeAvailable = true;
@state() private _guiMode = true;
@ -66,6 +68,7 @@ export class HuiSubElementEditor extends LitElement {
class="editor"
.hass=${this.hass}
.value=${this.config.elementConfig}
.context=${this.context}
@config-changed=${this._handleConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged}
></hui-row-element-editor>
@ -76,6 +79,7 @@ export class HuiSubElementEditor extends LitElement {
class="editor"
.hass=${this.hass}
.value=${this.config.elementConfig}
.context=${this.context}
@config-changed=${this._handleConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged}
></hui-headerfooter-element-editor>
@ -86,6 +90,7 @@ export class HuiSubElementEditor extends LitElement {
class="editor"
.hass=${this.hass}
.value=${this.config.elementConfig}
.context=${this.context}
@config-changed=${this._handleConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged}
></hui-tile-extra-element-editor>

View File

@ -1,11 +1,17 @@
import { customElement } from "lit/decorators";
import { getTileExtraElementClass } from "../../create-element/create-tile-extra-element";
import { LovelaceTileExtraConfig } from "../../tile-extra/types";
import {
LovelaceTileExtraConfig,
LovelaceTileExtraContext,
} from "../../tile-extra/types";
import type { LovelaceTileExtraEditor } from "../../types";
import { HuiElementEditor } from "../hui-element-editor";
@customElement("hui-tile-extra-element-editor")
export class HuiTileExtraElementEditor extends HuiElementEditor<LovelaceTileExtraConfig> {
export class HuiTileExtraElementEditor extends HuiElementEditor<
LovelaceTileExtraConfig,
LovelaceTileExtraContext
> {
protected async getConfigElement(): Promise<
LovelaceTileExtraEditor | undefined
> {

View File

@ -0,0 +1,220 @@
import {
mdiHomeMapMarker,
mdiMapMarker,
mdiPause,
mdiPlay,
mdiPlayPause,
mdiStop,
mdiTargetVariant,
} from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/tile/ha-tile-button";
import { UNAVAILABLE } from "../../../data/entity";
import {
canReturnHome,
canStart,
canStop,
isCleaning,
VacuumEntity,
VacuumEntityFeature,
} from "../../../data/vacuum";
import { HomeAssistant } from "../../../types";
import { LovelaceTileExtra, LovelaceTileExtraEditor } from "../types";
import {
VacuumCommand,
VacuumCommandsTileExtraConfig,
VACUUM_COMMANDS,
} from "./types";
interface VacuumButton {
translationKey: string;
icon: string;
serviceName: string;
disabled?: boolean;
}
export const VACUUM_COMMANDS_FEATURES: Record<
VacuumCommand,
VacuumEntityFeature[]
> = {
start_pause: [VacuumEntityFeature.PAUSE],
stop: [VacuumEntityFeature.STOP],
clean_spot: [VacuumEntityFeature.CLEAN_SPOT],
locate: [VacuumEntityFeature.LOCATE],
return_home: [VacuumEntityFeature.RETURN_HOME],
};
export const supportsVacuumCommand = (
stateObj: HassEntity,
command: VacuumCommand
): boolean =>
VACUUM_COMMANDS_FEATURES[command].some((feature) =>
supportsFeature(stateObj, feature)
);
export const VACUUM_COMMANDS_BUTTONS: Record<
VacuumCommand,
(stateObj: VacuumEntity) => VacuumButton
> = {
start_pause: (stateObj) => {
const startPauseOnly =
!supportsFeature(stateObj, VacuumEntityFeature.START) &&
supportsFeature(stateObj, VacuumEntityFeature.PAUSE);
if (startPauseOnly) {
return {
translationKey: "start_pause",
icon: mdiPlayPause,
serviceName: "start_pause",
};
}
const canPause =
isCleaning(stateObj) &&
supportsFeature(stateObj, VacuumEntityFeature.PAUSE);
return canPause
? {
translationKey: "pause",
icon: mdiPause,
serviceName: "pause",
}
: {
translationKey: "start",
icon: mdiPlay,
serviceName: "start",
disabled: canStart(stateObj),
};
},
stop: (stateObj) => ({
translationKey: "stop",
icon: mdiStop,
serviceName: "stop",
disabled: !canStop(stateObj),
}),
clean_spot: () => ({
translationKey: "clean_spot",
icon: mdiTargetVariant,
serviceName: "clean_spot",
}),
locate: () => ({
translationKey: "locate",
icon: mdiMapMarker,
serviceName: "locate",
}),
return_home: (stateObj) => ({
translationKey: "return_home",
icon: mdiHomeMapMarker,
serviceName: "return_to_base",
disabled: !canReturnHome(stateObj),
}),
};
@customElement("hui-vacuum-commands-tile-extra")
class HuiVacuumCommandTileExtra
extends LitElement
implements LovelaceTileExtra
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public stateObj?: HassEntity;
@state() private _config?: VacuumCommandsTileExtraConfig;
static getStubConfig(
_,
stateObj?: HassEntity
): VacuumCommandsTileExtraConfig {
return {
type: "vacuum-commands",
commands: stateObj
? VACUUM_COMMANDS.filter((c) =>
supportsVacuumCommand(stateObj, c)
).slice(0, 3)
: [],
};
}
public static async getConfigElement(): Promise<LovelaceTileExtraEditor> {
await import(
"../editor/config-elements/hui-vacuum-commands-tile-extra-editor"
);
return document.createElement("hui-vacuum-commands-tile-extra-editor");
}
public setConfig(config: VacuumCommandsTileExtraConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
private _onCommandTap(ev): void {
ev.stopPropagation();
const entry = (ev.target! as any).entry as VacuumButton;
this.hass!.callService("vacuum", entry.serviceName, {
entity_id: this.stateObj!.entity_id,
});
}
protected render(): TemplateResult {
if (!this._config || !this.hass || !this.stateObj) {
return html``;
}
const stateObj = this.stateObj as VacuumEntity;
return html`
<div class="container">
${VACUUM_COMMANDS.filter(
(command) =>
supportsVacuumCommand(stateObj, command) &&
this._config?.commands?.includes(command)
).map((command) => {
const button = VACUUM_COMMANDS_BUTTONS[command](stateObj);
return html`
<ha-tile-button
.entry=${button}
.label=${this.hass!.localize(
// @ts-ignore
`ui.dialogs.more_info_control.vacuum.${button.translationKey}`
)}
@click=${this._onCommandTap}
.disabled=${button.disabled || stateObj.state === UNAVAILABLE}
>
<ha-svg-icon .path=${button.icon}></ha-svg-icon>
</ha-tile-button>
`;
})}
</div>
`;
}
static get styles() {
return css`
.container {
display: flex;
flex-direction: row;
padding: 0 12px 12px 12px;
width: auto;
}
ha-tile-button {
flex: 1;
}
ha-tile-button:not(:last-child) {
margin-right: 12px;
margin-inline-end: 12px;
margin-inline-start: initial;
direction: var(--direction);
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-vacuum-commands-tile-extra": HuiVacuumCommandTileExtra;
}
}

View File

@ -3,7 +3,8 @@ import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import { CoverEntityFeature } from "../../../data/cover";
import { lightSupportsBrightness } from "../../../data/light";
import { LovelaceTileExtraConfig } from "./types";
import { supportsVacuumCommand } from "./hui-vacuum-commands-tile-extra";
import { LovelaceTileExtraConfig, VACUUM_COMMANDS } from "./types";
type TileExtraType = LovelaceTileExtraConfig["type"];
export type SupportsTileExtra = (stateObj: HassEntity) => boolean;
@ -20,9 +21,12 @@ const TILE_EXTRAS_SUPPORT: Record<TileExtraType, SupportsTileExtra> = {
"light-brightness": (stateObj) =>
computeDomain(stateObj.entity_id) === "light" &&
lightSupportsBrightness(stateObj),
"vacuum-commands": (stateObj) =>
computeDomain(stateObj.entity_id) === "vacuum" &&
VACUUM_COMMANDS.some((c) => supportsVacuumCommand(stateObj, c)),
};
const TILE_EXTRAS_EDITABLE: Set<TileExtraType> = new Set([]);
const TILE_EXTRAS_EDITABLE: Set<TileExtraType> = new Set(["vacuum-commands"]);
export const supportsTileExtra = (
stateObj: HassEntity,

View File

@ -10,7 +10,27 @@ export interface LightBrightnessTileExtraConfig {
type: "light-brightness";
}
export const VACUUM_COMMANDS = [
"start_pause",
"stop",
"clean_spot",
"locate",
"return_home",
] as const;
export type VacuumCommand = typeof VACUUM_COMMANDS[number];
export interface VacuumCommandsTileExtraConfig {
type: "vacuum-commands";
commands?: VacuumCommand[];
}
export type LovelaceTileExtraConfig =
| CoverOpenCloseTileExtraConfig
| CoverTiltTileExtraConfig
| LightBrightnessTileExtraConfig;
| LightBrightnessTileExtraConfig
| VacuumCommandsTileExtraConfig;
export type LovelaceTileExtraContext = {
entity_id?: string;
};

View File

@ -88,9 +88,10 @@ export interface LovelaceRowEditor extends LovelaceGenericElementEditor {
setConfig(config: LovelaceRowConfig): void;
}
export interface LovelaceGenericElementEditor extends HTMLElement {
export interface LovelaceGenericElementEditor<C = any> extends HTMLElement {
hass?: HomeAssistant;
lovelace?: LovelaceConfig;
context?: C;
setConfig(config: any): void;
focusYamlEditor?: () => void;
}
@ -104,7 +105,10 @@ export interface LovelaceTileExtra extends HTMLElement {
export interface LovelaceTileExtraConstructor
extends Constructor<LovelaceTileExtra> {
getConfigElement?: () => LovelaceTileExtraEditor;
getStubConfig?: (hass: HomeAssistant) => LovelaceTileExtraConfig;
getStubConfig?: (
hass: HomeAssistant,
stateObj?: HassEntity
) => LovelaceTileExtraConfig;
}
export interface LovelaceTileExtraEditor extends LovelaceGenericElementEditor {

View File

@ -4237,6 +4237,17 @@
},
"light-brightness": {
"label": "Light brightness"
},
"vacuum-commands": {
"label": "Vacuum commands",
"commands": "Commands",
"commands_list": {
"start_pause": "[%key:ui::dialogs::more_info_control::vacuum::start_pause%]",
"stop": "[%key:ui::dialogs::more_info_control::vacuum::stop%]",
"clean_spot": "[%key:ui::dialogs::more_info_control::vacuum::clean_spot%]",
"locate": "[%key:ui::dialogs::more_info_control::vacuum::locate%]",
"return_home": "[%key:ui::dialogs::more_info_control::vacuum::return_home%]"
}
}
}
}